Applying ObjectAnimator with a Complete Example
The other issue is that you need a new ObjectAnimator for every view you want to modify since it doesn’t support simultaneous changes of several objects. However, starting from API 23, it is possible to use PropertyValuesHolder and Keyframe in resource files to create more complex animations. See here for more details.
Anyway, it worth to say that ObjectAnimator is widely used in AnimatedVectorDrawable to animate SVG due to its ability to animate a property of any type. In my opinion, in any other case, there are better solutions for animations.
Usage:
ObjectAnimator animatorX =
ObjectAnimator.ofFloat(targetView, "property", property values);
ValueAnimator.AnimatorUpdateListener mListener;
animatorX.addUpdateListener(mListener);
AnimatorUpdateListener anim2Listener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
edtTxt.invalidate();
}
};
animatorX.start();
One other issue is that we can add a listener to this animation. animatorX.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
Toast.makeText(activity, "End!", Toast.LENGTH_LONG).show();
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
Example 1: An EditText is subject to some animations. Please create a new Android Studio project and use Empty template. I create a new Java class Animator1. See below code.
package com.msh_n.myp.customview1; | |
import android.animation.ObjectAnimator; | |
import android.animation.ValueAnimator; | |
import android.animation.ValueAnimator.AnimatorUpdateListener; | |
import android.content.Context; | |
import android.widget.Button; | |
import android.widget.EditText; | |
import android.widget.TextView; | |
public class Animator1 { | |
/** object that is updated upon animation update */ | |
private AnimatorUpdateListener mListener; | |
private EditText txtV; | |
public Animator1(){} | |
public Animator1(AnimatorUpdateListener listener, EditText txt){ | |
this.mListener = listener; | |
txtV = txt; | |
} | |
private ObjectAnimator xAnimator(){ | |
ObjectAnimator animatorX = ObjectAnimator.ofFloat(this.txtV, "textSize", 5, 30); | |
animatorX.setDuration(3000); | |
return animatorX; | |
} | |
public void animateX(){ | |
ObjectAnimator animatorX = xAnimator(); | |
animatorX.addUpdateListener(mListener); | |
animatorX.start(); | |
} | |
} |
Now in activity_main.xml file we add an EditText and a Button.
<?xml version="1.0" encoding="utf-8"?> | |
<android.support.constraint.ConstraintLayout | |
android:animateLayoutChanges="true" | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context="com.msh_n.myp.customview1.MainActivity"> | |
<EditText | |
android:id="@+id/edtTxt" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="8dp" | |
android:text="Hello World!" | |
app:layout_constraintLeft_toLeftOf="parent" | |
app:layout_constraintRight_toRightOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<Button | |
android:id="@+id/button1" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginBottom="8dp" | |
android:text="Click" | |
app:layout_constraintBottom_toBottomOf="parent" /> | |
</android.support.constraint.ConstraintLayout> |
In MainActivity.java we should create an instance of Animator1 and an instance of AnimatorUpdateListener. This file can be completed like below.
package com.msh_n.myp.customview1; | |
import android.animation.ValueAnimator.AnimatorUpdateListener; | |
import android.app.Activity; | |
import android.content.res.Resources; | |
import android.graphics.Bitmap; | |
import android.graphics.BitmapFactory; | |
import android.support.v7.app.AppCompatActivity; | |
import android.os.Bundle; | |
import android.util.DisplayMetrics; | |
import android.view.MotionEvent; | |
import android.view.View; | |
import android.widget.Button; | |
import android.widget.EditText; | |
import android.widget.ImageView; | |
import android.widget.TextView; | |
public class MainActivity extends AppCompatActivity { | |
Button button; | |
Animator1 anim1; | |
EditText edtTxt; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
button = (Button) findViewById(R.id.button1); | |
edtTxt = (EditText) findViewById(R.id.edtTxt); | |
button.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View view) { | |
anim1.animateX(); | |
} | |
}); | |
AnimatorUpdateListener anim2Listener = new AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator valueAnimator) { | |
edtTxt.invalidate(); | |
} | |
}; | |
anim1 = new Animator1(anim2Listener, edtTxt); | |
anim1.animateX(); | |
} | |
} |
Now it is ready to run the app and see how this animation works.
Example 2: Here we want to produce an animation like Example 2 of Animation class which is a circle animating from angle 0 to 360 degree.
Step 1- Create a new project in android studio and select Empty template. Step 2- Follow steps in Custom View in Android Apps to produce a custom view. The PieView class itself can be as follows.
package com.msh_n.myp.customview1; | |
public class PieView extends View { | |
String titleText; | |
int radius; | |
float angle; | |
Paint paintBackground; | |
Paint paintV; | |
Paint paintShadow; | |
Rect rect; | |
int backColor; | |
int viewColor; | |
int height =500; | |
int width=250; | |
int ori; | |
int actionBarHeight; | |
RectF rectCircle; | |
RectF rectOval; | |
public PieView(Context context){ | |
//this(context, null, 0); | |
super(context); | |
} | |
public PieView(Context context, AttributeSet attrs){ | |
//this(context, attrs, 0); | |
super(context, attrs); | |
init(context, attrs); | |
} | |
public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(context, attrs); | |
} | |
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) | |
public PieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){ | |
super(context, attrs, defStyleAttr, defStyleRes); | |
init(context, attrs); | |
} | |
public String getTitleText(){ | |
return titleText; | |
} | |
public void setTitleText(String str){ | |
titleText = str; | |
} | |
public int getRadius(){ | |
return radius; | |
} | |
public float getAngle() { | |
return angle; | |
} | |
public void setAngle(float ang) { | |
this.angle = ang; | |
} | |
protected void init(Context context, @Nullable AttributeSet attrs){ | |
angle = 5; | |
rect = new Rect(); | |
paintBackground = new Paint(Paint.ANTI_ALIAS_FLAG); | |
paintV = new Paint(); | |
paintShadow = new Paint(); | |
ori = ((Activity) context).getResources().getConfiguration().orientation; | |
if(attrs == null){ | |
backColor = getResources().getColor(android.R.color.holo_purple); | |
viewColor = getResources().getColor(android.R.color.holo_green_light); | |
paintBackground.setColor(backColor); | |
paintV.setColor(viewColor); | |
paintShadow.setColor(getResources().getColor(android.R.color.holo_purple)); | |
return; | |
} | |
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, | |
R.styleable.PieView, 0, 0); | |
try { | |
titleText = a.getString(R.styleable.PieView_titleText); | |
backColor = a.getColor(R.styleable.PieView_backgroundColor, | |
getResources().getColor(android.R.color.holo_blue_light)); | |
viewColor = a.getColor(R.styleable.PieView_viewColor, | |
getResources().getColor(android.R.color.holo_orange_dark)); | |
} finally { | |
a.recycle(); | |
} | |
paintBackground.setColor(backColor); | |
paintV.setColor(viewColor); | |
paintShadow.setColor(getResources().getColor(android.R.color.holo_purple)); | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
width = w; | |
height = h; | |
rectCircle = new RectF(width/4, 0, 3*width/4, height/3); | |
rectOval = new | |
RectF(width/4, 0,width/2+width/3, height/3); | |
boolean isWidthLarger = width > height/3; | |
if(isWidthLarger){ | |
radius = height/6; | |
}else{ | |
radius = width/2; | |
} | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ | |
int widthSize = MeasureSpec.getSize(widthMeasureSpec); | |
int heightSize = MeasureSpec.getSize(heightMeasureSpec); | |
int widthMode = MeasureSpec.getMode(widthMeasureSpec); | |
int heightMode = MeasureSpec.getMode(heightMeasureSpec); | |
int desiredWidth = 1000; | |
int desiredHeight = 1500; | |
int width; | |
int height; | |
//width | |
if(widthMode == MeasureSpec.EXACTLY){ | |
//Must be this size | |
width = widthSize; | |
}else if(widthMode == MeasureSpec.AT_MOST){ | |
//Can't be bigger than... | |
width = Math.min(widthSize, desiredWidth); | |
}else{ | |
//Be whatever you want | |
width = desiredWidth; | |
} | |
//height | |
if(heightMode == MeasureSpec.EXACTLY){ | |
//Must be this size | |
height = heightSize; | |
}else if(heightMode == MeasureSpec.AT_MOST){ | |
//Can't be bigger than... | |
height = Math.min(heightSize, desiredHeight); | |
}else{ | |
//Be whatever you want | |
height = desiredHeight; | |
} | |
//MUST CALL THIS- MANDATORY | |
setMeasuredDimension(width, height); | |
} | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
rect.left = 0; | |
rect.top = 0; | |
if(ori == Configuration.ORIENTATION_PORTRAIT) { | |
rect.bottom = height / 3; | |
rect.right = width; | |
RectF rect2 = new RectF(); | |
rect2.left = 0; | |
rect2.right = width; | |
rect2.bottom = height; | |
rect2.top = 0; | |
canvas.drawRect(rect, paintBackground); | |
canvas.drawRect(rectCircle, paintShadow); | |
canvas.drawArc(rectCircle, 0, angle, true, paintV); | |
canvas.drawCircle(width/2, height/6, radius/1.2f, paintShadow); | |
}else if(ori == Configuration.ORIENTATION_LANDSCAPE){ | |
} | |
} | |
} |
Step 2- Modify the activity_main.xml file as below.
<?xml version="1.0" encoding="utf-8"?> | |
<android.support.constraint.ConstraintLayout | |
android:animateLayoutChanges="true" | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context="com.msh_n.myp.customview1.MainActivity"> | |
<com.msh_n.myp.customview1.PieView | |
android:id="@+id/pieView2" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_marginBottom="8dp" | |
android:layout_marginEnd="8dp" | |
android:layout_marginStart="8dp" | |
app:layout_constraintBottom_toTopOf="@+id/button1" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toBottomOf="parent" | |
app:titleText="Foreground color" /> | |
<Button | |
android:id="@+id/button1" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginBottom="8dp" | |
android:layout_marginEnd="8dp" | |
android:layout_marginStart="8dp" | |
android:layout_marginTop="8dp" | |
android:text="Click" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
/> | |
</android.support.constraint.ConstraintLayout> |
Step 3- Go to MainActivity.java and modify it like shown here.
package com.msh_n.myp.customview1; | |
public class MainActivity extends AppCompatActivity { | |
Button button; | |
float newAngle; | |
float oldAngle; | |
ObjectAnimator animatorX; | |
ValueAnimator.AnimatorUpdateListener mListener; | |
PieView pieView; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
button = (Button) findViewById(R.id.button1); | |
pieView = (PieView) findViewById(R.id.pieView2); | |
animatorX = ObjectAnimator.ofFloat(pieView, "angle", 0, 180); | |
animatorX.setDuration(4000); | |
mListener = new ValueAnimator.AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator valueAnimator) { | |
pieView.invalidate(); | |
} | |
}; | |
animatorX.addUpdateListener(mListener); | |
button.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View view) { | |
animatorX.start(); | |
} | |
}); | |
} | |
} |
Now its time to run the app. You probably see one animation like below!
ObjectAnimator is good for animating one or two properties simultaneously. But what if we should animate more than two properties at the same time? Please refer to ViewPropertyAnimator for a good answer!
Comments
Post a Comment