View系列:动画

View系列:动画

Android小彩虹2021-08-24 15:49:43320A+A-

本文为过往笔记整理, 在此只作记录,不做严谨的技术分享。

View Animation(视图动画)

最大的特点是:并没有改变目标实际的属性(宽高/位置等)。例如:移动后,点击原来的位置出发点击事件;移动后再旋转,还是回到原来的位置旋转。

Tween Animation(补间动画)

锚点

可以是数值、百分数、百分数p三种样式,比如50、50%、50%p。[不是只有pivotx/y才可以用这3中样式,其它变换的属性也可以]

  • 当为数值时,表示在当前View的左上角,即原点处加上50px,做为起始缩放点;
  • 如果是50%,表示在当前控件的左上角加上自己宽度的50%做为起始点;
  • 如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为起始点x轴坐标(是在目标的左上角原点加上相对于父控件宽度的距离,不是锚点在父控件的那个位置)。

fromX/toX等等类型的数据也可以用上面的3中数据 类型,只不过有的不适合。比如scale用%p就没意义了。养成好习惯,只在锚点的属性上随便用这3中类型,from/to属性分清类型用相应的数值(浮点倍数/角度...)。

从Animation继承的属性
android:duration 动画持续时间,以毫秒为单位 
android:fillAfter 如果设置为true,控件动画结束时,将保持动画最后时的状态
android:fillBefore 如果设置为true,控件动画结束时,还原到开始动画前的状态
android:fillEnabled  与android:fillBefore 效果相同,都是在动画结束时,将控件还原到初始化状态
android:repeatCount 重复次数
android:repeatMode 重复类型,有reverse和restart两个值,reverse表示倒序回放,restart表示重新放一遍,必须与repeatCount一起使用才能看到效果。因为这里的意义是重复的类型,即回放时的动作。
android:interpolator 设定插值器,其实就是指定的动作效果,比如弹跳效果等,不在这小节中讲解,后面会单独列出一单讲解。
scale
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:duration="700" android:fromXScale="50%" //也可以用上面的3中类型 android:fromYScale="50%" android:toXScale="200%" android:toYScale="200%" android:pivotX="0.5" android:pivotY="0.5" android:repeatCount = "2" android:repeatMode = "reverse" android:fillAfter = "true" />
alpha
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromAlpha="0.1" android:toAlpha="1" android:duration="1500" android:repeatMode = "reverse" android:repeatCount = "2" android:fillAfter = "true" >
</alpha>
rotate
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromDegrees="0" android:toDegrees="270" android:pivotX="50%" android:pivotY="50%" android:duration="700" android:repeatMode = "reverse" android:repeatCount = "3" android:fillAfter = "true" >
</rotate>
translate
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:duration="700" android:fillAfter="true" android:fromXDelta="50" android:fromYDelta="50%p" android:repeatCount="3" android:repeatMode="reverse" android:toXDelta="70%p" android:toYDelta="80%p">
</translate>
AnimationSet animSet = new AnimationSet(false);
        Animation scaleAnim = AnimationUtils.loadAnimation(this, R.anim.scale_anim); //资源文件
        Animation rotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotate_anim);
        AlphaAnimation alphaAnim = new AlphaAnimation(0.2f, 1.0f); //代码生成
        //valueType 3中类型的数据(px, 自身%, 父类%p),这里已自身为参照物。
        TranslateAnimation traslateAnim = new TranslateAnimation(
            	Animation.RELATIVE_TO_SELF, 0.2f,
                Animation.RELATIVE_TO_SELF, 3.0f,
                Animation.RELATIVE_TO_SELF, 0f,
                Animation.RELATIVE_TO_SELF, 1.0f);
        animSet.setFillAfter(true);
        animSet.setDuration(2000);
        animSet.setInterpolator(new BounceInterpolator());
        animSet.addAnimation(scaleAnim);
        animSet.addAnimation(rotateAnim);
        animSet.addAnimation(alphaAnim);
        animSet.addAnimation(traslateAnim);
        ivTarget.startAnimation(animSet);
自定义Animation
private class MoveAnimation extends Animation {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            mInterpolatedTime = interpolatedTime;
            invalidate();
        }
    }

Frame Animation(逐帧动画)

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">//false 一直重复执行,true执行一次。
    <item android:duration="200" android:drawable="@drawable/frame_anim_1"/>
    <item android:duration="200" android:drawable="@drawable/frame_anim_2"/>
    <item android:duration="200" android:drawable="@drawable/frame_anim_3"/>
    <item android:duration="200" android:drawable="@drawable/frame_anim_4"/>
    <item android:duration="200" android:drawable="@drawable/frame_anim_4"/>
</animation-list>
//xml中setBackgroud属性用getBackgroud()获取Drawable
//src属性,getDrawable()方法获取Drawable
ImageView mImageViewFilling = (ImageView) findViewById(R.id.imageview_animation_list_filling);
AnimationDrawable animDrawable = ((AnimationDrawable) mImageViewFilling.getBackground());
animDrawable.start();
  代码中添加
  rocketAnimation = new AnimationDrawable();
  rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust1, 200);
  rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust2, 200);
  rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust3, 200);
  rocketImage.setBackground(rocketAnimation);  
  • 需要注意的是,动画的启动需要在view和window建立连接后才可以绘制,比如上面代码是在用户触摸后启动。如果我们需要打开界面就启动动画的话,则可以在Activity的onWindowFocusChanged()方法中启动。

Property Animation(属性动画)

属性动画是指通过改变View属性来实现动画效果,包括:ValueAnimator、ObjectAnimator、TimeAnimator

ValueAnimator

该类主要针对数值进行改变,不对View进行操作

ValueAnimator animator = ValueAnimator.ofInt(0,400);  
animator.setDuration(1000);  
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    @Override  
    public void onAnimationUpdate(ValueAnimator animation) {  
        //拿到监听结果,自己处理。
        int curValue = (int)animation.getAnimatedValue(); 
        tvTextView.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
    }  
});  
animator.setInterpolator(new LinearInterpolator()); 
animator.start();  

监听:

/** * 监听器一:监听动画变化时的实时值 * 添加方法为:public void addUpdateListener(AnimatorUpdateListener listener) */
public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animation);
}
/** * 监听器二:监听动画变化时四个状态 * 添加方法为: public void addListener(AnimatorListener listener) */
public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}


/** * 移除AnimatorUpdateListener */  
void removeUpdateListener(AnimatorUpdateListener listener);  
void removeAllUpdateListeners();  
 /** * 移除AnimatorListener */  
void removeListener(AnimatorListener listener);  
void removeAllListeners();  

ObjectAnimator

ValueAnimator只能对数值进行计算,不能直接操作View,需要我们在监听器中自己去操作控件。这样就有点麻烦了,于是Google在ValueAmimator的基础上又派生出了ObjerctAnimator类,让动画直接与控件关联起来。

 	ObjectAnimator rotateObject = ObjectAnimator.ofFloat(tvPropertyTarget, 
                                                      "Rotation", 
                                                      0, 20, -20, 40, -40, 0);  
    rotateObject.setDuration(2000);   
    rotateObject.start();
setter/getter 属性名

在View中已经实现了一些属性的setter/getter方法,在构造动画时可以直接对控件使用。

  • 要使用一个属性,必须在控件中有对应的setter/getter方法,属性setter/getter方法的命名必须以驼峰方式
  • ObjectAnimator在使用该属性的时候,会把setter/getter和属性第一个字母大写转换后的字段拼接成方法名,通过反射的方式调用该方法传值。 所以,上文中"Rotation/rotation"可以首字母可以大小写都行
//1、透明度:alpha 
public void setAlpha(float alpha) //2、旋转度数:rotation、rotationX、rotationY  public void setRotation(float rotation) //围绕Z轴旋转 public void setRotationX(float rotationX) public void setRotationY(float rotationY) //3、平移:translationX、translationY  public void setTranslationX(float translationX) public void setTranslationY(float translationY) //缩放:scaleX、scaleY  public void setScaleX(float scaleX) public void setScaleY(float scaleY) 

image-20210603130556023

自定义属性做动画
public class PointView extends View {
    private float mRadius = 0;
    public PointView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mRadius == 0) {
            mRadius = 50;
        }
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(150,200,mRadius,paint);
    }
    
    public void setRadius(float radius){
        this.mRadius = radius;
        invalidate();
    }

    public float getRadius(){
        return mRadius;
    }
}     

//radius属性首字母大小写无所谓,最后都是要转成大些的。
ObjectAnimator pointAnim = ObjectAnimator.ofFloat(pointPropertyAnim, 
                                                  "Radius", 
                                                  10, 40, 40, 80, 60, 100, 80, 120,60);
pointAnim.start();

什么时候需要用到get方法呢? 前面构造动画时传入的取值范围都是多个参数,Animator知道是从哪个值变化到哪个值。当只传入一个参数的时候,Animator怎么知道哪里是起点?这时通过get方法找到初始值。 如果没有找到get方法,会用该参数类型的默认初始值复制。如:ofInt方法传入一个值,找不到get方法时,默认给的初始值是Int类型的初始值0.

原理

image-20210603131108900ObjectAnimator的方便之处在于:

ValueAnimator只负责把数值给监听器,ObjectAnimator只负责调用set方法。至于实现,都是靠我们自己或者set中的方法。

插值器

设置动画运行过程中的进度比例,类似匀速变化、加速变化、回弹等

  • 参数input:是一个float类型,它取值范围是0到1,表示当前动画的进度,取0时表示动画刚开始,取1时表示动画结束,取0.5时表示动画中间的位置,其它类推。
  • 返回值:表示当前实际想要显示的进度。取值可以超过1也可以小于0,超过1表示已经超过目标值,小于0表示小于开始位置。(给估值器使用
  • 插值器默认每10ms刷新一次
public class PointInterpolator implements Interpolator {
    /** * input 是实际动画执行的时间比例 0~1 * newInput 你想让动画已经执行的比例 0~1。 * 注意:都是比例,而不是实际的值。 * * setDuration(1000)情况下:前200ms走了3/4的路程比例,后800ms走了1/4的路程比例。 */
    @Override
    public float getInterpolation(float input) {
        if (input <= 0.2) {//后1/4的时间,输出3/4的比例
            float newInput = input*4;
            return newInput;
        }else {//后3/4的时间,输出1/4的比例
            float newInput = (float) (input - 0.2)/4 + 0.8f;
            return newInput;
        }
    }
}

使用方式和默认插值器

在xml和代码中使用插值器,省略代码中使用方式

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android" // 通过资源ID设置插值器 android:interpolator="@android:anim/overshoot_interpolator" android:duration="3000" android:fromXScale="0.0" android:fromYScale="0.0" android:pivotX="50%" android:pivotY="50%" android:toXScale="2" android:toYScale="2" />

内置插值器动画展示

Android动画之Interpolator

Android动画插值器

作用 资源ID 对应的Java类
动画加速进行 @android:anim/accelerte_interpolator Acceleraterplator
快速完成动画,超出再回到到结束样式 @android:anim/overshoot_interpolator OvershootInterpolator
先加速再减速 @android:anim/accelerate_decelerate_interpolator AccelerateDecelerateInterpolator
先退后再加速前进 @android:anim/anticipate_interpolator AnticipateInterpolator
先退后再加速前进,超出终点后再回终点 @android:anim/anticipate_overshoot_interpolator AnticipateOvershootInterpolator
最后阶段弹球效果 @android:anim/bounce_interpolator BounceInterpolator
周期运动 @android:anim/cycle_interpolator CycleInterpolator
减速 @android:anim/decelerate_interpolator DecelerateInterpolator
匀速 @android:anim/linear_interpolator LinearInterpolator

估值器

设置 属性值 从初始值过渡到结束值 的变化具体数值

  • 参数fraction: 表示当前动画的进度(插值器返回值
  • 返回值:表示当前对应类型的取值,也就是UpdateListener接口方法中传入的值
public class PointEvaluator implements TypeEvaluator<Point> {
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int radius = (int) (startValue.getRadius() + 
                            fraction*(endValue.getRadius() - startValue.getRadius()));
        return new Point(radius);
    }
}

自定义插值器、估值器、属性的使用:

public void doAnimation(){
    	//ObjectAnimator animator = ObjectAnimator.ofInt(mView, "Radius", 20, 80);
        ValueAnimator animatior = new ValueAnimator();
        animatior.setObjectValues(new Point(20), new Point(80));
    	animatior.setInterpolator(new PointInterpolator());
        animatior.setEvaluator(new PointEvaluator());
        
        animatior.setDuration(2000);
        animatior.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPoint = (Point) animation.getAnimatedValue();
                invalidate();
            }
        });
        animatior.start();
    }

PropertyValuesHolder

它其中保存了动画过程中所需要操作的属性和对应的值

通过ObjectAnimator.ofFloat(Object target, String propertyName, float… values)构造的动画,ofFloat()的内部实现其实就是将传进来的参数封装成PropertyValuesHolder实例来保存动画状态,后期的各种操作也是以PropertyValuesHolder为主。

//将需要操作的多个属性和值封装起来,一起放到ObjectAnimator中,相当于set操作。
PropertyValuesHolder rotateHolder = PropertyValuesHolder.ofFloat("Rotation", 0, 360, 0);
PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofFloat("scaleX", 1, 2, 1,2,1);
PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofFloat("scaleY", 1, 2, 1,2,1);
ObjectAnimator objectAnim = ObjectAnimator.ofPropertyValuesHolder(ivHolderTarget,
                                                                  rotateHolder, 
                                                                  scaleXHolder, 
                                                                  scaleYHolder);
objectAnim.setDuration(2000);
objectAnim.setInterpolator(new LinearInterpolator());
objectAnim.start();

KeyFrame(主要帧)

如果想要更精确的控制动画,想要控制整个动画过程的某个点或某个时段达到的值,可以通过自定义插值器或估值器来实现,但是那样又有些费事,并且不容易计算这段时间内值的变化。 这时可以用Keyframe来实现,即设置好某个时间点和值,系统会自动计算该点和上个点之间,值的变化。

/*** * 实现左右摇晃,每边最后有震动的效果。 * 摇晃角度100度:0.2f/0.2~0.4/0.4~0.5,分别设置不同的角度和加速器。 * 每个比例点达到哪个角度,这在估值器中也能做到,但是需要自己算每个时间段内值的变化过程。 * KeyFrame可以设置好 比例-值 以后,系统根据默认或设置的加速器改变:上个点和该点内的值如何变换。 * 这样可以更精确的控制动画过程,同时也不用自己费劲去计算值因该如何变换。 */
Keyframe kfRotation1 = Keyframe.ofFloat(0, 0);  //第一帧,如果没有该帧,会直接跳到第二帧开始动画。
//第二帧 0.2f时达到60度,线性加速应该作用于从0~0.2f的这段时间,而不是作用在0.2~0.4f这段。因为已经定好60度是要的结果了,那么实现就应该在前面这段。
Keyframe kfRotation2 = Keyframe.ofFloat(0.2f, 60);  
kfRotation2.setInterpolator(new LinearInterpolator());
Keyframe kfRotation3 = Keyframe.ofFloat(0.4f, 100);
kfRotation3.setInterpolator(new BounceInterpolator());
Keyframe kfRotation4 = Keyframe.ofFloat(0.5f, 0);  
kfRotation4.setInterpolator(new LinearInterpolator());  //最少有2帧
Keyframe kfRotation5 = Keyframe.ofFloat(0.7f, -60);
kfRotation5.setInterpolator(new LinearInterpolator());
Keyframe kfRotation6 = Keyframe.ofFloat(0.9f, -100);
kfRotation6.setInterpolator(new BounceInterpolator());
Keyframe kfRotation7 = Keyframe.ofFloat(1f, 0);//最后一帧,如果没有该帧,会以最后一个KeyFrame做结尾
kfRotation7.setInterpolator(new LinearInterpolator());

Keyframe kfScaleX1 = Keyframe.ofFloat(0, 1);
Keyframe kfScaleX2 = Keyframe.ofFloat(0.01f,2.8f);
Keyframe kfScaleX3 = Keyframe.ofFloat(0.8f,2.0f);
Keyframe kfScaleX4 = Keyframe.ofFloat(1f,1.0f);

Keyframe kfScaleY1 = Keyframe.ofFloat(0, 1);
Keyframe kfScaleY2 = Keyframe.ofFloat(0.01f,2.8f);
Keyframe kfScaleY4 = Keyframe.ofFloat(0.8f,2.0f);
Keyframe kfScaleY5 = Keyframe.ofFloat(1f,1.0f);

PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofKeyframe("rotation", kfRotation1, kfRotation2, kfRotation3,kfRotation4, kfRotation5, kfRotation6, kfRotation7);
PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofKeyframe("scaleX", kfScaleX1, kfScaleX2, kfScaleX3, kfScaleX4);
PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofKeyframe("scaleY", kfScaleY1, kfScaleY2, kfScaleY4, kfScaleY5);


ObjectAnimator objectAnim = ObjectAnimator.ofPropertyValuesHolder(ivHolderTarget,
                                                                  rotationHolder, 
                                                                  scaleXHolder, 
                                                                  scaleYHolder);
objectAnim.setDuration(1500);

AnimatorSet

AnimatorSet针对ValueAnimator和ObjectAnimator都是适用的,但一般而言,我们不会用到ValueAnimator的组合动画。

playTogether/playSequentially

无论是playTogether还是playSequentially方法,它们只是,仅仅是激活了动画什么时候开始,并不参与动画的具体操作。 例如:如果是playTogether,它只负责这个动画什么时候一起激活,至于anim1/anim2/anim3...哪个马上开始,哪个有延迟,哪个会无限重复,set都不管,只负责一起激活。 如果是playSequentially,它只负责什么时候开始激活第一个(因为有可能set设置延迟),并在第一个动画结束的时候,激活第二个,以此类推。

ObjectAnimator anim1 = ObjectAnimator.ofInt(mTv1, "BackgroundColor",  0xffff00ff, 0xffffff00, 0xffff00ff);

ObjectAnimator anim2 = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
anima2.setStartDelay(2000);
anima2.setRepeatCount(ValueAnimator.INFINITE);

ObjectAnimator anim3 = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);
anim3.setStartDelay(2000);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(anim1, anim2, anim3);//playSequentially(按次序播放)
animatorSet.setDuration(2000);
animatorSet.setStartDelay(2000);
animatorSet.start();
play(x).with(x)
  • play(anim1).with(anim2):2000ms后set开始激活动画,anim1启动,再过2000ms后anim2启动。
  • play(anim2).with(anim1):2000ms后set开始激活动画,再过2000ms后启动anim2,并且启动anim1.
set监听

addListener监听的是AnimatorSet的start/end/cacle/repeat。不会监听anim1/anim2的动画状态的。

联合动画XML实现
单独设置和Set中设置
  • 以set为准:
//设置单次动画时长
public AnimatorSet setDuration(long duration);
//设置加速器
public void setInterpolator(TimeInterpolator interpolator) //设置ObjectAnimator动画目标控件 public void setTarget(Object target) 
ObjectAnimator anim1 = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
anim1.setDuration(500000000);

ObjectAnimator anim2 = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);
anim2.setDuration(3000);//每次3000,而不是3次3000ms
anim2.setRepeatCount(3);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(tv2TranslateY).with(tv1TranslateY);
animatorSet.setDuration(2000);//以Set为准
animatorSet.start();

setDuration()是指单个动画的时间,并不是指总共做完这个动画过程的时间。比如:anim2中设置了3000ms,重复3次。是指每次3000ms,不是3次3000ms。
另外animatorSet设置了时间以后,anim1/anim2虽然也设置了,但是这时以set为准。即,anim1/anim2的单个动画时间为2000ms。只不过anim2是每次2000ms,重复3次,共6000ms。

  • 不以set为准:setStartDelay
ObjectAnimator anim1 = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);
anim2.setStartDelay(2000);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.addListener(new Animator.AnimatorListener(){...});
animatorSet.play(anim1).with(anim2);
animatorSet.setStartDelay(3000);//指的是Set的激活延迟,而不是动画延迟
animatorSet.setDuration(2000);
animatorSet.start();

setStartDelay不会覆盖单个动画的该方法,只会延长set的激活时间。所以,上面代码中动画的启动过程是:3000ms后set开始激活动画,anim1启动,再过2000ms后anim2启动。

ViewPropertyAnimator

属性动画已不再是针对于View而进行设计的了,而是一种对数值不断操作的过程,我们将属性动画对数值的操作过程设置到指定对象的属性上来,从而形成一种动画的效果。 虽然属性动画给我们提供了ValueAnimator类和ObjectAnimator类,在正常情况下,基本都能满足我们对动画操作的需求,但ValueAnimator类和ObjectAnimator类本身并不是针对View对象的而设计的,而我们在大多数情况下主要都还是对View进行动画操作的。

因此Google官方在Android 3.1系统中补充了ViewPropertyAnimator类,这个类便是专门为View动画而设计的。

  • 专门针对View对象动画而操作的类
  • 更简洁的链式调用设置多个属性动画,这些动画可以同时进行
  • 拥有更好的性能,多个属性动画是一次同时变化,只执行一次UI刷新(也就是只调用一次invalidate,而n个ObjectAnimator就会进行n次属性变化,就有n次invalidate)
  • 每个属性提供两种类型方法设置。scaleX()/scaleXBy()
  • 该类只能通过View的animate()获取其实例对象的引用
  • 自动调用start
btn.animate()
    .alpha(0.5f)
    .rotation(360)
    .scaleX(1.5f).scaleY(1.5f)
    .translationX(50).translationY(50)
    .setDuration(5000);
public ViewPropertyAnimator scaleY(float value) {
        animateProperty(SCALE_Y, value);
        return this;
    }

public ViewPropertyAnimator scaleYBy(float value) {
    animatePropertyBy(SCALE_Y, value);
    return this;
    

private void animateProperty(int constantName, float toValue) {
        float fromValue = getValue(constantName);
        float deltaValue = toValue - fromValue;
        animatePropertyBy(constantName, fromValue, deltaValue);
    }

private void animatePropertyBy(int constantName, float byValue) {
        float fromValue = getValue(constantName);
        animatePropertyBy(constantName, fromValue, byValue);
    }
    
//----------scale/rotation/alpha等方法,到最后都是调用该方法-----------------------------------

	/** * Utility function, called by animateProperty() and animatePropertyBy(), which handles the * details of adding a pending animation and posting the request to start the animation. * * @param constantName The specifier for the property being animated * @param startValue The starting value of the property * @param byValue The amount by which the property will change */
    private void animatePropertyBy(int constantName, float startValue, float byValue) {
        // First, cancel any existing animations on this property
        if (mAnimatorMap.size() > 0) {
            Animator animatorToCancel = null;
            Set<Animator> animatorSet = mAnimatorMap.keySet();
            for (Animator runningAnim : animatorSet) {
                PropertyBundle bundle = mAnimatorMap.get(runningAnim);
                // 如果在该属性上已经有动画,则结束该属性上的动画。
                if (bundle.cancel(constantName)) {
                    if (bundle.mPropertyMask == NONE) {
                        animatorToCancel = runningAnim;
                        break;
                    }
                }
            }
            if (animatorToCancel != null) {
                animatorToCancel.cancel();
            }
        }

        // 封装该属性和值,放入集合中
        NameValuesHolder nameValuePair = new NameValuesHolder(constantName, 
                                                              startValue, byValue);
        mPendingAnimations.add(nameValuePair);
        mView.removeCallbacks(mAnimationStarter);
        //每次操作属性都post了该Runnable类,但是每次都把原来的移除了,始终都在维护一个最新的集合post。
        mView.postOnAnimation(mAnimationStarter);
    }

image-20210604100429893

layoutAnimation

布局动画,api1,该属性只对创建ViewGroup时,对其子View有动画。已经创建过了该ViewGroup的话,再向其添加子View不会有动画。

  • onCreat创建加载布局时:
//anim -> rotate_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromDegrees="0" android:toDegrees="270" android:pivotX="50%" android:pivotY="50%" android:duration="1000" android:repeatMode = "reverse" android:repeatCount = "3" android:fillAfter = "true" >
</rotate>

// layoutAnimation标签
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" android:delay="1" android:animationOrder="normal" android:animation="@anim/rotate_anim">
</layoutAnimation>

//定义在LinearLayout上,在该界面生成时,Button显示动画。但是,后面在LinearLayout中添加Button时,不再有动画。
 <LinearLayout android:id="@+id/ll_tips_target_animation" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutAnimation="@anim/layout_animation" android:tag="在xml中设置的layoutAnimation" android:orientation="vertical">
        <Button style="@style/base_button" android:text="ViewGroup初始化时,子View有动画"/>
    </LinearLayout>
  • 代码中动态设置layoutAnimation,添加View
        //代码生成ViewGroup
        LinearLayout linear = new LinearLayout(this);

        Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate_anim);
        LayoutAnimationController controller = new LayoutAnimationController(animation);
        controller.setDelay(1);
        //动画模式,正常/倒叙/随机
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        //设置layoutAnimation
        linear.setLayoutAnimation(controller);
        linear.setLayoutAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                Log.e(TAG, "onAnimationStart: start" );
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                Log.e(TAG, "onAnimationEnd: end" );
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                Log.e(TAG, "onAnimationRepeat: repeat" );
            }
        });

        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
        										LinearLayout.LayoutParams.MATCH_PARENT, 
        										LinearLayout.LayoutParams.WRAP_CONTENT);
        linear.setLayoutParams(params);
        //给该ViewGroup添加子View,子View会有动画。
        addVeiw(linear,null);
        llTargetAnim.addView(linear, 0);

使用场景:

该属性只有ViewGroup创建的时候才能有效果,所以不适合动态添加子View的操作显示动画。一般做界面显示的时候的入场动画,比如打开一个界面,多个固定不变的item有动画的显示出来。(进入设置界面,信息展示界面)。

android:animateLayoutChanges属性:

Api11后,添加/移除子View时所带的默认动画,在Xml中设置。不能自定义动画,只能使用默认的。所以,使用范围较小。

<LinearLayout android:animateLayoutChanges="true" />

image

LayoutTransition布局容器动画:

animateLayoutChanges只能使用默认动画,LayoutTransition可以自定义动画,并且也不用非得创建ViewGroup时才能显示动画,后面添加子View也能显示

不做深入研究...

SVG(矢量动画)

SVG在Web上的应用非常广泛,在Android 5.X之前的Android版本上,可以通过一些第三方开源库来在Android中使用SVG。而在Android 5.X之后,Android中添加了对SVG的**标签**支持。从而让开发者可以使用SVG来创建更加丰富的动画效果。

SVG格式具备目前网络流行的jpg和png等格式无法具备的优势:放大不会失真;可在svg图像中保留可编辑和可搜寻的状态;平均来讲,svg文件比其它格式的图像文件要小很多,因而下载也很快。

SVG可以实现很多复杂绚丽的动画效果:

原谅我盗了一张图
  • VectorDrawable: 创建基于XML的SVG图形
  • AnimatedVectorDrawable:"胶水",把VectorDrawable和ObjectAnimator关联起来,实现动画

SVG矢量动画机制

Android矢量动画实践

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们