Applying ObjectAnimator with a Complete Example

The ObjectAnimator is a subclass of ValueAnimator and provides support for animating properties on target objects. Animators can be created from either code or resource files Some developers have negative concerns about using this class due to "reflection mechanism" of this approach.

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);

Then we should send an instance of AnimatorUpdateListener to it.

ValueAnimator.AnimatorUpdateListener mListener;
animatorX.addUpdateListener(mListener);

The mListener should override onAnimationUpdate() function.

AnimatorUpdateListener anim2Listener = new AnimatorUpdateListener() {
      @Override
       public void onAnimationUpdate(ValueAnimator valueAnimator) {

           edtTxt.invalidate();
        }
     };

Lastly, the animation starts by the start() function of ObjectAnimator instance.

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) {

     }
   });

Lets see the topic in two examples.

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();
}
}
view raw Animator1.java hosted with ❤ by GitHub

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){
}
}
}
view raw PieView.java hosted with ❤ by GitHub

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