一. 原理 实现动画的原理:直接对对象的属性值进行改变操作,从而实现动画效果
(注意:ObjectAnimator类继承自ValueAnimator类,即底层的动画实现机制是基于ValueAnimator类)
二. 类似ValueAnimator的使用形式 1.Java代码的使用方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ObjectAnimator ofInt (Object target, String propertyName, int ... values) ;ObjectAnimator ofFloat (Object target, String propertyName, float ... values) ;ObjectAnimator ofArgb (Object target, String propertyName, int ... values) ObjectAnimator ofObject (Object target, String propertyName, TypeEvaluator evaluator, Object... values) ObjectAnimator setDuration (long duration) setRepeatCount (int value) void setRepeatMode (@RepeatMode int value) void setStartDelay (long startDelay) void setInterpolator (TimeInterpolator value)
2.Xml的使用方式 ① 在路径 res/animator 的文件夹里创建动画效果.xml文件,其中animator文件夹需要自己去创建。
② 使用AnimatorInflater的public static Animator loadAnimator(Context context, @AnimatorRes int id)方法来加载写好的Xml文件动画。
③ 使用 public void setTarget(@Nullable Object target) ,设置动画对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //初始值 android:valueFrom="" //结束值 android:valueTo="" //值的类型 floatType intType 等 android:valueType="floatType" //属性名称 TranslationX ,Rotation 等 android:propertyName="" //动画持续时间 android:duration="1000" //动画重复次数 infinite是无数次 android:repeatCount="1" //动画重复模式 restart reserve android:repeatMode="restart" //设置插值器 android:interpolator=""
1 2 3 4 5 6 7 8 ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(this ,R.animator.animation); animator.setTarget(view); animator.start();
三. 类似补间动画的 平移 缩放 旋转 透明度 1. 平移 ① 运行效果
② Java 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class TranslateActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_translate); final ObjectAnimator translationX = ObjectAnimator.ofFloat(findViewById(R.id.view), "TranslationX" , 0f , 100f ); translationX.setDuration(1000 ); translationX.setRepeatCount(1 ); translationX.setRepeatMode(ValueAnimator.RESTART); final ObjectAnimator translationY = ObjectAnimator.ofFloat(findViewById(R.id.view), "TranslationY" , 0f , 100f ); translationY.setDuration(1000 ); translationY.setRepeatCount(1 ); translationY.setRepeatMode(ValueAnimator.RESTART); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { translationX.start(); translationY.start(); } }); } }
③ Xml方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android ="http://schemas.android.com/apk/res/android" android:valueFrom ="0" android:valueTo ="100" android:valueType ="floatType" android:propertyName ="TranslationX" android:duration ="1000" android:repeatCount ="1" android:repeatMode ="restart" /> <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android ="http://schemas.android.com/apk/res/android" android:valueFrom ="0" android:valueTo ="100" android:valueType ="floatType" android:propertyName ="TranslationY" android:duration ="1000" android:repeatCount ="1" android:repeatMode ="restart" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class TranslateActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_translate); View view = findViewById(R.id.view); final ObjectAnimator translateX = (ObjectAnimator) AnimatorInflater.loadAnimator(this , R.animator.translatex_animator); final ObjectAnimator translateY = (ObjectAnimator) AnimatorInflater.loadAnimator(this , R.animator.translatey_animator); translateX.setTarget(view); translateY.setTarget(view); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { translateX.start(); translateY.start(); } }); } }
2. 缩放 ① 运行效果
② Java 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class ScaleActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_scale); final View view = findViewById(R.id.view); view.post(new Runnable() { @Override public void run () { view.setPivotX(view.getWidth()); view.setPivotY(view.getHeight()); final ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "ScaleX" , 1.0f , 1.5f ); scaleX.setDuration(1000 ); scaleX.setRepeatCount(1 ); scaleX.setRepeatMode(ValueAnimator.RESTART); final ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "ScaleY" , 1.0f , 1.5f ); scaleY.setDuration(1000 ); scaleY.setRepeatCount(1 ); scaleY.setRepeatMode(ValueAnimator.RESTART); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { scaleX.start(); scaleY.start(); } }); } }); } }
③ Xml方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android ="http://schemas.android.com/apk/res/android" android:valueFrom ="1.0" android:valueTo ="1.5" android:valueType ="floatType" android:propertyName ="ScaleX" android:duration ="1000" android:repeatCount ="1" android:repeatMode ="restart" /> <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android ="http://schemas.android.com/apk/res/android" android:valueFrom ="1.0" android:valueTo ="1.5" android:valueType ="floatType" android:propertyName ="ScaleY" android:duration ="1000" android:repeatCount ="1" android:repeatMode ="restart" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class ScaleActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_scale); final View view = findViewById(R.id.view); final ObjectAnimator scaleX = (ObjectAnimator) AnimatorInflater.loadAnimator(this ,R.animator.scalex_animator); scaleX.setTarget(view); final ObjectAnimator scaleY = (ObjectAnimator) AnimatorInflater.loadAnimator(this ,R.animator.scaley_animator); scaleY.setTarget(view); view.post(new Runnable() { @Override public void run () { view.setPivotX(view.getWidth()); view.setPivotY(view.getHeight()); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { scaleX.start(); scaleY.start(); } }); } }); } }
3. 旋转 ① 运行效果
② Java 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class RotateActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_rotate); final View view = findViewById(R.id.view); view.post(new Runnable() { @Override public void run () { view.setPivotX(view.getWidth()); view.setPivotY(view.getHeight()); final ObjectAnimator rotation = ObjectAnimator.ofFloat(view, "Rotation" , 0.0f , 90.0f ); rotation.setDuration(1000 ); rotation.setRepeatCount(1 ); rotation.setRepeatMode(ValueAnimator.RESTART); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { rotation.start(); } }); } }); } }
③ Xml方式
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android ="http://schemas.android.com/apk/res/android" android:valueFrom ="0" android:valueTo ="90" android:valueType ="floatType" android:propertyName ="Rotation" android:duration ="1000" android:repeatCount ="1" android:repeatMode ="restart" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class RotateActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_rotate); final View view = findViewById(R.id.view); final ObjectAnimator rotate = (ObjectAnimator) AnimatorInflater.loadAnimator(this ,R.animator.rotate_animation); rotate.setTarget(view); view.post(new Runnable() { @Override public void run () { view.setPivotX(view.getWidth()); view.setPivotY(view.getHeight()); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { rotate.start(); } }); } }); } }
4. 透明度 ① 运行效果
② Java 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class AlphaActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_alpha); final ObjectAnimator alpha = ObjectAnimator.ofFloat(findViewById(R.id.view), "Alpha" , 1.0f , 0.5f ); alpha.setDuration(1000 ); alpha.setRepeatCount(1 ); alpha.setRepeatMode(ValueAnimator.RESTART); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { alpha.start(); } }); } }
③ Xml方式
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android ="http://schemas.android.com/apk/res/android" android:valueFrom ="0" android:valueTo ="90" android:valueType ="floatType" android:propertyName ="Rotation" android:duration ="1000" android:repeatCount ="1" android:repeatMode ="restart" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class AlphaActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_alpha); View view = findViewById(R.id.view); final ObjectAnimator alpha = (ObjectAnimator) AnimatorInflater.loadAnimator(this , R.animator.alpha_animator); alpha.setTarget(view); findViewById(R.id.start).setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { alpha.start(); } }); } }
四. ObjectAnimator的实现原理 1. String property 前面的例子中,我们给String property传入alpha、rotation、translationX 和 scaleY 等,还可以传入什么呢?
答案是任意属性值。
①ObjectAnimator类,通过不断控制 值 的变化,再不断 自动 赋给对象的属性,从而实现动画效果。而自动赋给对象的属性的本质是调用该对象属性的 set() & get() 方法进行赋值。
第二个参数传入值的作用是:让ObjectAnimator类 根据传入的属性名去寻找该对象对应属性名的 set() & get() 方法,从而进行对象属性值的赋值。
2. 自动赋值的逻辑 ① 初始化时,如果属性的初始值没有提供,则调用属性的 get() 进行取值;
② 当值变化时,用对象该属性的 set() 方法,从而从而将新的属性值设置给对象属性。
3. 总结 ① ObjectAnimator 类针对的是任意对象 & 任意属性值,并不是单单针对于View对象。
② 如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的 set() & get()。
五. 实例使用 1. 实现步骤 对于属性动画,其拓展性在于:不局限于系统限定的动画,可以自定义动画,即自定义对象的属性,并通过操作自定义的属性从而实现动画。
步骤:
① 为对象设置需要操作属性的 set() & get() 方法。
② 通过实现TypeEvaluator类从而定义属性变化的逻辑。
③ 使用 ObjectAnimator 的 ofObject() 创建动画。
2. 第一种方式添加 set() & get() 方法 通过继承原始类,直接给类加上该属性的 get() & set(),从而实现给对象加上该属性的 get() & set() 。
① 运行效果
② 代码
自定义的估值器 ColorEvaluator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class ColorEvaluator implements TypeEvaluator { private int currentRed; private int currentGreen; private int currentBlue; @Override public Object evaluate (float fraction, Object startValue, Object endValue) { String startColor = (String) startValue; String endColor = (String) endValue; int startRed = Integer.parseInt(startColor.substring(1 ,3 ),16 ); int startGreen = Integer.parseInt(startColor.substring(3 ,5 ),16 ); int startBlue = Integer.parseInt(startColor.substring(5 ,7 ),16 ); int endRed = Integer.parseInt(endColor.substring(1 ,3 ),16 ); int endGreen = Integer.parseInt(endColor.substring(3 ,5 ),16 ); int endBlue = Integer.parseInt(endColor.substring(5 ,7 ),16 ); int redDiff = Math.abs(startRed-endRed); int greenDiff = Math.abs(startGreen-endGreen); int blueDiff = Math.abs(startBlue-endBlue); int colorDiff = redDiff + greenDiff + blueDiff; if (currentRed != endRed){ currentRed = getCurrentColor(startRed,endRed,colorDiff,0 ,fraction); } if (currentGreen != endGreen){ currentGreen = getCurrentColor(startGreen,endGreen,colorDiff,redDiff,fraction); } if (currentBlue != endBlue){ currentBlue = getCurrentColor(startBlue,endBlue,colorDiff,redDiff+greenDiff,fraction); } return "#" + getHexString(currentRed) + getHexString(currentGreen) + getHexString(currentBlue); } public int getCurrentColor (int startColor,int endColor,int colorDiff,int offset,float fraction) { int currentColor; if (startColor >= endColor){ currentColor = (int ) (startColor - (fraction * colorDiff - offset)); }else { currentColor = (int ) (startColor + (fraction * colorDiff - offset)); } return currentColor; } private String getHexString (int value) { String hexString = Integer.toHexString(value); if (hexString.length() == 1 ){ hexString = "0" + hexString; } return hexString; } }
自定义的View MyView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class MyView extends View { public static final float RADIUS = 100f ; private Paint paint; private String color; public MyView (Context context, @Nullable AttributeSet attrs) { super (context, attrs); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.BLUE); } @Override protected void onDraw (Canvas canvas) { canvas.drawCircle(500 ,500 ,RADIUS,paint); } public String getColor () { return color; } public void setColor (String color) { this .color = color; paint.setColor(Color.parseColor(color)); invalidate(); } }
通过对 自定义的 MyView 中的 Color 属性操作,每次自动赋值后都会刷新界面重绘一个圆。
这就是通过继承原始类,直接给类加上该属性的 get() & set()。
2. 第二种方式添加 set() & get() 方法 通过包装原始动画对象,间接给对象加上该属性的 get()& set()。即 用一个类来包装原始对象。
从原理上说,如果想让对象的属性a的动画生效,属性a需要同时满足下面两个条件:
不满足第二条的例子:由于View的setWidth()并不是设置View的宽度,而是设置View的最大宽度和最小宽度的;所以通过setWidth()无法改变控件的宽度;所以对View视图的width做属性动画没有效果。
① 运效果
② 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package swu.xl.property_object_example_two;import androidx.appcompat.app.AppCompatActivity;import android.animation.ObjectAnimator;import android.os.Bundle;import android.view.View;import android.view.ViewGroup;import android.widget.Button;public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); View view = findViewById(R.id.btn); final ViewWrapper viewWrapper = new ViewWrapper(view); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v) { ObjectAnimator.ofInt(viewWrapper,"width" ,300 ,600 ).setDuration(3000 ).start(); } }); } private static class ViewWrapper { private View target; public ViewWrapper (View target) { this .target = target; } public void setWidth (int width) { ViewGroup.LayoutParams layoutParams = target.getLayoutParams(); layoutParams.width = width; target.setLayoutParams(layoutParams); } public int getWidth () { return target.getLayoutParams().width; } } }
通过包装原始动画对象完成View的Width属性动画。本质上是采用了设计模式中的装饰模式,即通过包装类从而扩展对象的功能。
参考文章 Android ObjectAnimator类学习指南:手把手带你学会如何自定义属性动画