在安卓日常开发时经常会用到一些动画知识.像是直播中的图标我们经常用到帧动画,像是平常的加载中,或转盘等操作,我们经常用到补间动画,如果再想实现一些更加复杂的效果,我们可以使用系统给我们提供的属性动画.那么这些动画在我们日常开发中是怎么结合使用的.他们又有哪些特点和区别呢?接下来我将给大家做一个简要的介绍.
动画分类
视图动画(View Animation)
- 作用对象,视图(View)
- 具体分类:
- 补间动画
- 逐帧动画 下面简单这两种动画.
补间动画(Tween Animation)
补间动画公共配置
// 以下参数是4种动画效果的公共属性,即都有的属性
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = 动画插值器,即影响动画的播放速度,下面会详细讲
平移动画(Translate)
// 以下参数是平移动画特有的属性
android:fromXDelta="0" // 视图在水平方向x 移动的起始值
android:toXDelta="500" // 视图在水平方向x 移动的结束值
android:fromYDelta="0" // 视图在竖直方向y 移动的起始值
android:toYDelta="500" // 视图在竖直方向y 移动的结束值
缩放动画(Scale)
// 以下参数是缩放动画特有的属性
android:fromXScale="0.0" // 动画在水平方向X的起始缩放倍数
android:toXScale="2" //动画在水平方向X的结束缩放倍数
android:fromYScale="0.0" //动画开始前在竖直方向Y的起始缩放倍数
android:toYScale="2" //动画在竖直方向Y的结束缩放倍数
上面四个:
0.0表示收缩到没有;1.0表示正常无伸缩,值小于1.0表示收缩;值大于1.0表示放大
// 轴点 = 视图缩放的中心点
android:pivotX="50%" // 缩放轴点的x坐标
android:pivotY="50%" // 缩放轴点的y坐标
// pivotX pivotY,可取值为数字,百分比,或者百分比p
// 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
// 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
// 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
// 两个50%表示动画从自身中间开始,具体如下图
旋转动画(Rotate)
// 以下参数是旋转动画特有的属性
android:duration="1000"
android:fromDegrees="0" // 动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
android:toDegrees="270" // 动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
android:pivotX="50%" // 旋转轴点的x坐标
android:pivotY="0" // 旋转轴点的y坐标
// 轴点 = 视图缩放的中心点
// pivotX pivotY,可取值为数字,百分比,或者百分比p
// 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
// 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
// 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
// 两个50%表示动画从自身中间开始,具体如下图
透明度动画(Alpha)
// 以下参数是透明度动画特有的属性
android:fromAlpha="1.0" // 动画开始时视图的透明度(取值范围: -1 ~ 1)
android:toAlpha="0.0"// 动画结束时视图的透明度(取值范围: -1 ~ 1)
几种动画的综合运用
- 代码
private void startTranslate(){
TranslateAnimation translateAnimation = new TranslateAnimation(0,0,0,600);
translateAnimation.setFillAfter(true);
translateAnimation.setDuration(1000);
translateAnimation.setRepeatCount(4);
translateAnimation.setRepeatMode(Animation.REVERSE);
binding.first.startAnimation(translateAnimation);
}
private void startRotate(){
float pivotX = binding.second.getMeasuredWidth()/2;
float pivotY = binding.second.getMeasuredHeight();
RotateAnimation rotateAnimation = new RotateAnimation(0,180,pivotX,pivotY);
rotateAnimation.setFillAfter(true);
rotateAnimation.setDuration(1000);
rotateAnimation.setRepeatCount(4);
rotateAnimation.setRepeatMode(Animation.REVERSE);
binding.second.startAnimation(rotateAnimation);
}
private void startScale(){
float width = binding.third.getMeasuredWidth();
float height = binding.third.getMeasuredHeight();
ScaleAnimation scaleAnimation = new ScaleAnimation(1,0,1,0,width/2,height/2);
scaleAnimation.setFillAfter(true);
scaleAnimation.setDuration(1000);
scaleAnimation.setRepeatCount(4);
scaleAnimation.setRepeatMode(Animation.REVERSE);
binding.third.startAnimation(scaleAnimation);
}
private void startAlpha(){
AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);
alphaAnimation.setFillAfter(true);
alphaAnimation.setDuration(1000);
alphaAnimation.setRepeatCount(4);
alphaAnimation.setRepeatMode(Animation.REVERSE);
binding.four.startAnimation(alphaAnimation);
}
- 效果
设置视图动画监听器
- public void setAnimationListener(Animation.AnimationListener listener)
- 动画监听器接口源码:
public interface AnimationListener {
void onAnimationStart(Animation var1);//在动画开始播放时调用
void onAnimationEnd(Animation var1);在动画结束播放时调用。
void onAnimationRepeat(Animation var1);//在动画重复播放时调用。
}
注意
假如我们不希望实现所有方法的话我们可以用AnimatorListenerAdapter包装类,减少自己实现无用的方法.
组合动画
有时候我们想让一组动画按照顺序或者同时播放,此时我们把上面的四种动画组合起来就能构成组合动画了.同时我们还能控制是让他们顺序播放还是同时播放
- 代码
private void startAnim(){
//步骤1:创建组合动画对象(设置为true)
AnimationSet setAnimation = new AnimationSet(true);
/**
* 步骤2:设置组合动画的属性
* 因为在下面的旋转动画设置了无限循环(RepeatCount = INFINITE)
* 所以动画不会结束,而是无限循环
* 所以组合动画的下面两行设置是无效的
*/
setAnimation.setRepeatMode(Animation.RESTART);
setAnimation.setRepeatCount(10);// 设置了循环一次,但无效
// 步骤3:逐个创建子动画(方式同单个动画创建方式,此处不作过多描述)
//子动画1:旋转动画
Animation rotate = new RotateAnimation(0,360,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f);
rotate.setDuration(3000);
//rotate.setRepeatMode(Animation.RESTART);
//rotate.setRepeatCount(Animation.INFINITE);
//子动画2:平移动画
Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
TranslateAnimation.RELATIVE_TO_SELF,0
,TranslateAnimation.RELATIVE_TO_SELF,0);
translate.setDuration(3000);
//子动画3:透明度动画
Animation alpha = new AlphaAnimation(1,0);
alpha.setDuration(3000);
//alpha.setStartOffset(3000);
//子动画4:缩放动画
Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f);
scale1.setDuration(3000);
//scale1.setStartOffset(4000);
//步骤4:将创建的子动画添加到组合动画里
setAnimation.addAnimation(alpha);
setAnimation.addAnimation(rotate);
setAnimation.addAnimation(translate);
setAnimation.addAnimation(scale1);
binding.start.startAnimation(setAnimation);
}
- 执行效果
逐帧动画Frame Animation
简单讲解完上面的视图动画,我们看一下今天的重头戏,属性动画.
属性动画
属性动画是在安卓3.0之后提供的一种全新的动画模式.
为什么要使用属性动画?
属性动画的具体介绍
关键类 | 作用 | 备注 |
---|---|---|
ViewPropertyAnimator | 采用对对象操作实现属性动画 | |
ObjectAnimator | 先改变值,然后自动赋值给对象的属性,从而实现动画效果 | 采用getXXX()和getXXX()进行自动赋值 |
ValueAnimator | 先改变值,然后手动赋值给对象的属性,从而实现动画效果 | 本质是按照指定规律操作数值从开始到结束的一种机制 |
AnimatorSet | 实现组合动画的类 |
属性动画的使用
最简单的ViewPropertyAnimator
- 代码:
view.animate().translationX(500);
view.animate().translationX(0);
注意:动画没设置duration默认是300ms执行完毕
默认采用先加速在减速的插值器AccelerateDecelerateInterpolator
- 效果:
- 介绍
- ViewPropertyAnimator它定义在我们所有view的顶级父类View类中.也就是说几乎我们所有的view都能简单的使用此属性动画来达到我们想要的动画效果
- 此Api使用方式的简单性决定了它不能完成一些较为复杂的动画效果
- ViewPropertyAnimator支持的动画效果如下: |View中的方法|功能|对应的ViewProPertyAnimator中的方法| |:-:|:-:|:-:| |setTranslationX()|设置x轴偏移|translationX() translationXBy()| |setTranslationY()|设置y轴偏移|translationY() translationYBy()| |setTranslationZ()|设置z轴偏移|translationZ() translationZBy()| |setX()|设置x轴绝对位置 x像素|x() xBy()| |setY()|设置x轴绝对位置|y() yBy()| |setZ()|设置z轴绝对位置|z() zBy()| |setRotation()|设置平面旋转 x度|rotation() rotationBy()| |setRotationX()|设置沿X轴旋转|rotationX() rotationXBy()| |setRotationY()|设置沿Y轴旋转|rotationY() rotationYBy()| |setScaleX()|设置横向缩放 x倍|scaleX() scaleXBy()| |setScaleY()|设置纵向缩放|scaleY() scaleYBy()| |setAlpha()|设置透明度|alpha() alphaBy()|
ObjectAnimator使用
- 示例代码:
ObjectAnimator animator = ObjectAnimator.ofFloat(binding.object,"rotation",0,360,0,360,0,360,0,360,0);
animator.setDuration(2000);
animator.start();
- 效果:
- 说明:
- 属性动画根据设置的对象及其属性来执行设定的动画
- 参数中的
rotation
在代码中必须对应setRotation(float f)方法系统才能准确识别到.否则动画不会执行. - ObjectAnimator中的ofXXX()类型对应的是setXXX()方法的参数,比如说ofFloat方法生成的ObjectAnimator,起setXXX()方法的参数必须是Float类型的.
- ObjectAnimator中最后一个是可变参数,可以设置多个值,系统会按照设置的值依次执行动画.
- 调用start方法来开始执行动画
- ObjectAnimator支持任意对象的任意属性执行动画效果,但是该属性需要实现getXXX()和setXXX()方法,同时,在set方法内必须调用invalidate();来进行view的重绘.
ValueAnimator使用
ValueAnimator是更加灵活的动画定义方式.它支持任何类型按照特定规律的变化效果.
- 简单代码:
/**
* 启动valueAnimator
*/
private void startValueAnim(){
ValueAnimator animator = ValueAnimator.ofFloat(0,360,0,360,0,360,0,360,0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
binding.value.setRotation((float)animation.getAnimatedValue());
}
});
animator.setDuration(4000);
animator.start();
}
- 效果
- 原理:
使用AnimatorSet安排多个属性动画同时播放
在许多情况下,我们需要根据一个动画开始或结束的时间来播放另一个动画。借助AnimatorSet,我们可以将动画捆绑到一个 AnimatorSet 中,以便指定是同时播放动画、按顺序播放还是在指定的延迟时间后播放。当然我们还可以嵌套 AnimatorSet 对象。来实现更为复杂的效果
- 以下代码段通过以下方式播放相应的 Animator 对象:
- 播放anim1
- 同时播放anim2_1,anim2_2,anim2_3
- 播放anim3
- 播放 anim4
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(anim1).before(anim2_1);
bouncer.play(anim2_1).with(anim2_2);
bouncer.play(anim2_1).with(anim2_3);
bouncer.play(anim3).after(anim2_3);
ValueAnimator anim4 = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(anim4);
animatorSet.start();
另外还有:
- 同时播放两个动画
animatorSet.playTogether(animator1, animator2);
- 两个动画依次执行
animatorSet.playSequentially(animator1, animator2);
PropertyValuesHolder 同一个动画中改变多个属性
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();
PropertyValuesHolders.ofKeyframe() 把同一个属性拆分
// 在 0% 处开始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 时间经过 50% 的时候,动画完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder);
animator.start();
属性动画监听器
public static interface AnimatorListener {
default void onAnimationStart(Animator animation, boolean isReverse) {//isReverse 动画是否反向播放
onAnimationStart(animation);
}
default void onAnimationEnd(Animator animation, boolean isReverse) {//isReverse 动画是否反向播放
onAnimationEnd(animation);
}
void onAnimationStart(Animator animation);//动画开始执行时调用
void onAnimationEnd(Animator animation);//动画执行完成时调用
void onAnimationCancel(Animator animation);//动画执行被取消时调用
void onAnimationRepeat(Animator animation);//动画重复执行时调用
}
- 设置监听器
animator.addListener(new Animator.AnimatorListener(){
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
动画插值器Interpolator
根据时间完成度的百分比 计算当前属性完成度的百分比。比如在线性插值器中,把时间的50%转成动画完成了50%,
注意:插值器不返回动画改变的具体数值,只返回动画改变的百分比.
- 源码
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input 取值范围是[0.0,1.0],代表时间流失百分比
* @return 返会属性改变的百分比.
*/
float getInterpolation(float input);
}
- 用法
animator.setInterpolator(new AccelerateInterpolator());
- 系统提供的动画插值器 |类名|介绍| |-|-| |AccelerateDecelerateInterpolator|先加速后减速| |AccelerateInterpolator|一直加速| |LinearInterpolator|匀速| |DecelerateInterpolator|一直减速| |AnticipateInterpolator|先回拉一下然后在正常执行动画(类似于打弹弓)| |OvershootInterpolator|到达目标点后会超过一些再回弹回来(类似于坐沙发)| |AnticipateOvershootInterpolator|上面两个的结合版,开始回拉一下然后正常执行最后超过目标点回弹| |BounceInterpolator|在目标值处弹跳(类似于打球的效果)| |CycleInterpolator|没用过不知道| |PathInterpolator|自定义动画完成度和时间完成度曲线,这个比较复杂,如果把动画完成度时间完成度绘制成一个坐标轴的话,那么在这个轴上画出的线就是动画执行的速度| |FastOutLinearInInterpolator|加速运动与AccelerateInterpolator相似只不过初始加速会快一些,此插值器曲线公式用的是贝塞尔曲线,而AccelerateInterpolator用的是指数曲线| |FastOutSlowInInterpolator|先加速在减速,与AccelerateDecelerateInterpolator相似,只不过是它用的贝塞尔曲线所以前期加速度快,AccelerateDecelerateInterpolator用的是余弦曲线| |LinearOutSlowInInterpolator|减速运动,比DecelerateInterpolator 初始速度更高|
动画估值器TypeEvaluator
估值器就是根据当前属性改变的百分比来计算改变后的属性值。
插值器决定属性值随时间变化的规律;而具体变化属性数值则交给估值器去计算。
如果要为 Android 系统无法识别的类型添加动画效果,则可以通过实现 TypeEvaluator 接口来创建您自己的评估程序。Android 系统可以识别的类型为 int、float 或颜色,分别由 IntEvaluator、FloatEvaluator 和 ArgbEvaluator 类型评估程序提供支持。
- FloateEvaluator源码
public class FloatEvaluator implements TypeEvaluator<Number> {
/**
* 此方法根据动画完成度,和开始值结束值,来计算最终的动画数值.
*/
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
其中fraction参数是经过插值器处理之后的一个float类型的小数,他的取值范围是0.0-1.0,他代表动画完成的一个百分比. 2. 使用
animator.setEvaluator(new PointFEvaluator());
或
ValueAnimator.ofObject(new PointFEvaluator(),obj1,obj2);
- 自定义TypeEvaluate的一个示例: 假如说我们自定义了一个圆形,圆形的圆心即我们下面定义的Point,现在我们要让这个圆进行抛物轨迹运动,代码如下: BallPoint.java
//用来代表小球中心点
public class BallPoint {
private float pointX;
private float pointY;
public BallPoint(float pointX, float pointY) {
this.pointX = pointX;
this.pointY = pointY;
}
public float getPointX() {
return pointX;
}
public float getPointY() {
return pointY;
}
}
BallView.java
//自定义的一个圆球view
public class BallView extends View {
private Paint circlePaint = new Paint();
private BallPoint point = new BallPoint(0,0);
public BallView(Context context) {
super(context);
}
public BallView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BallView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
circlePaint.setColor(Color.parseColor("#aabbcc"));
circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
public BallPoint getPoint() {
return point;
}
public void setPoint(BallPoint point) {
this.point = point;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#aabbcc"));
canvas.drawCircle(point.getPointX(),point.getPointY(),60,circlePaint);
}
}
BallViewEvaluator.java
/**
* 模拟抛物运动的估值器
*/
public class BallViewEvaluator implements TypeEvaluator<BallPoint> {
@Override
public BallPoint evaluate(float fraction, BallPoint startValue, BallPoint endValue) {
float pointX = startValue.getPointX()+(fraction * endValue.getPointX() - startValue.getPointX());
float pointY = startValue.getPointY()+fraction * fraction * (endValue.getPointY()-startValue.getPointX());
BallPoint point = new BallPoint(pointX, pointY);
return point;
}
}
xml,及动画代码
<com.zhou.animationdemo.BallView
android:id="@+id/ballView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
private void startBallAnim() {
ValueAnimator animator = ValueAnimator.ofObject(new BallViewEvaluator(), new BallPoint(0, 0), new BallPoint(600, 1200)).setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
BallPoint point = (BallPoint) animation.getAnimatedValue();
binding.ballView.setPoint(point);
// ToDo 您也可以在此记录或做相应操作
}
});
animator.start();
}
- 动画效果
创建圆形揭露动画
- 点击时执行代码:
private void showCircleAnim(){
float finalRadius = (float) Math.hypot(binding.view.getWidth(), binding.view.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(binding.view, 0, 0, 0f, finalRadius);
binding.view.setVisibility(View.VISIBLE);
anim.setDuration(800);
anim.start();
}
- 效果:
为布局更新添加动画
- 在容器布局添加代码:
<LinearLayout android:id="@+id/container"
android:animateLayoutChanges="true"
...
/>
- activity中的示例代码(增加view)
private LinearLayout.LayoutParams getLayoutParams(){
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,100);
params.topMargin=30;
return params;
}
int i=0;
private void addView(){
View v = new View(this);
if(i%2==0){
v.setBackgroundColor(Color.parseColor("#00FF00"));
}else{
v.setBackgroundColor(Color.parseColor("#0000FF"));
}
i++;
v.setLayoutParams(getLayoutParams());
binding.viewContainer.addView(v,0);
}
- 效果 增加animateLayoutChanges配置前:
增加animateLayoutChanges配置后:
注
减少子view时与此效果类似,此处不做展示
使用FlingAnimation动画移动view
- 配置
//应用模块的gradle文件中导入支持库,集成androidx可不用此项配置
dependencies {
implementation 'com.android.support:support-dynamic-animation:28.0.0'
}
- 使用
- 获取最大值
maxTransitionX = binding.getRoot().getWidth() - binding.fling.getWidth();
maxTransitionY = binding.getRoot().getHeight() - binding.fling.getHeight();
- 根据当前手势获取当前速度
1. 初始化监听器
private GestureDetector.OnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(velocityX) > Math.abs(velocityY)) {
showFlingAnim(velocityX);
} else {
showFlingAnimY(velocityY);
}
return true;
}
};
2. 初始化手势监听器,并从onTouchListener中获得EventMotion
detector = new GestureDetector(this,listener);
binding.fling.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return detector.onTouchEvent(event);
}
});
- 效果
总结
- 对比ValueAnimator类 & ObjectAnimator 类,其实二者都属于属性动画,本质上都是一致的:先改变值,然后 赋值 给对象的属性从而实现动画效果。
- ValueAnimator 类是先改变值, 然后
手动赋值
给对象的属性从而实现动画;是 间接 对对象属性进行操作 - ObjectAnimator类是先改变值,然后
自动赋值
给对象的属性从而实现动画;是 直接 对对象属性进行操作
- 属性动画类之间关系