Android动画Animator家族使用指南

作者: e4e52c116681 | 来源:发表于2018-12-27 13:04 被阅读17次

    零、前言:本文知识点

    • ValueAnimator的认识与使用
    • 估值器TypeEvaluator的自定义与使用
    • 插值器TimeInterpolator的自定义与使用
    • Path于Animator的结合使用
    • ObjectAnimator的自定义与使用
    • TimeAnimator的使用
    • AnimatorSet动画集合的使用
    • Animator家族的监听器介绍与使用
    • Animator家族在xml中的使用

    一直用动画,貌似还没有好好地总结一下,趁有空,总结一波
    所谓动画,就是不停变化,在视觉上达到连续的效果
    Animator的体系并不复杂,但内部实现挺复杂的,很多类常年埋没于底层,不见天日
    如:PropertyValuesHolder及其子类Keyframes族Keyframe族KeyframeSet族
    今天试着读了一下源码,基本上读的懵懵懂懂,总的思路算是把握了


    第一节:ValueAnimator的使用

    一、简单的使用

    0.Animator家族简单认识:

    Animator是一个抽象类,不可用,只能找它的子类
    现在先看非常常用的ValueAnimator

    Animator体系.png
    1.下面是一段ValueAnimator最简单的使用
    ValueAnimator animator = ValueAnimator.ofInt(0, 10);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Log.e(TAG, animation.getAnimatedValue()+"---");
        }
    });
    animator.start();
    

    打印结果分析:

    2018-12-26 12:04:09.290 ~ 2018-12-26 12:04:09.584---->584-290=294
    默认持续时间是300(源码中定义的),基本一致,在这段时间内不断回调onAnimationUpdate方法
    并且animation的值从预定的0~10之间不断变化,这就是ValueAnimator的基本用处
    
    2018-12-26 12:04:09.290 2001-2001/com.toly1994.animator_test E/MainActivity: 0---
    2018-12-26 12:04:09.335 2001-2001/com.toly1994.animator_test E/MainActivity: 0---
    2018-12-26 12:04:09.351 2001-2001/com.toly1994.animator_test E/MainActivity: 1---
    2018-12-26 12:04:09.373 2001-2001/com.toly1994.animator_test E/MainActivity: 1---
    2018-12-26 12:04:09.412 2001-2001/com.toly1994.animator_test E/MainActivity: 3---
    2018-12-26 12:04:09.439 2001-2001/com.toly1994.animator_test E/MainActivity: 5---
    2018-12-26 12:04:09.450 2001-2001/com.toly1994.animator_test E/MainActivity: 5---
    2018-12-26 12:04:09.468 2001-2001/com.toly1994.animator_test E/MainActivity: 6---
    2018-12-26 12:04:09.484 2001-2001/com.toly1994.animator_test E/MainActivity: 7---
    2018-12-26 12:04:09.502 2001-2001/com.toly1994.animator_test E/MainActivity: 8---
    2018-12-26 12:04:09.517 2001-2001/com.toly1994.animator_test E/MainActivity: 8---
    2018-12-26 12:04:09.534 2001-2001/com.toly1994.animator_test E/MainActivity: 9---
    2018-12-26 12:04:09.568 2001-2001/com.toly1994.animator_test E/MainActivity: 9---
    2018-12-26 12:04:09.584 2001-2001/com.toly1994.animator_test E/MainActivity: 10---
    

    2.从中衍生的想法

    1).不断调用onAnimationUpdate回调
    2).可以获取有规律变化的不同的数值
    在自定义View中onAnimationUpdate刷新界面,并动态改变数值

    简单应用.gif
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/26 0026:7:50<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:Animator测试View
     */
    public class AnimatorView extends View {
        private static final String TAG = "AnimatorView";
        
        private Paint mPaint;//画笔
        private int mRadius = 100;//小球初始半径
        private ValueAnimator mAnimator;//动画器
    
        public AnimatorView(Context context) {
            this(context, null);
        }
    
        public AnimatorView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(0xff94E1F7);
    
            mAnimator = ValueAnimator.ofInt(100, 300);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mRadius= (int) animation.getAnimatedValue();
                    invalidate();
                }
            });
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.translate(400, 400);//移动坐标
            canvas.drawCircle(0, 0, mRadius, mPaint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "onTouchEvent: ");
                    mAnimator.start();//点击开启动画
                    break;
                case MotionEvent.ACTION_UP:
            }
            return super.onTouchEvent(event);
        }
    }
    

    其实道理很简单,就是把打印输出换成了刷新视图,而且半径在不断变化


    3.常规配置

    看一下RESTART(默认)和REVERSE的区别

    RESTART REVERSE
    mAnimator.setStartDelay(1000);//设置延迟
    mAnimator.setRepeatCount(2);//设置重复执行次数
    // mAnimator.setRepeatMode(ValueAnimator.RESTART);//重新开始100->300 100->300
    mAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
    mAnimator.setDuration(1000);//设置时长
    

    二、ofArgbofObject

    颜色变化 颜色大小

    1.改变颜色:ofArgb

    传入两个颜色(起始色和终止色)

    mColorAnimator = ValueAnimator.ofArgb(0xff94E1F7, 0xffF35519);
    mColorAnimator.setDuration(500);//设置时长
    mColorAnimator.setRepeatCount(1);//设置重复执行次数
    mColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
    
    mColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mPaint.setColor((Integer) animation.getAnimatedValue());
            invalidate();
        }
    });
    

    2.如何即改变大小又改变颜色:

    ValueAnimator.ofObject + TypeEvaluator

    2.1先定义一个类承载数据:Ball(为了演示简洁,使用public属性)
    public class Ball {
        public int color;
        public int r;
    
        public Ball() {
        }
    
        public Ball(int r, int color) {
            this.color = color;
            this.r = r;
        }
    }
    

    2.2.创建TypeEvaluator(类型估值器)

    TypeEvaluator是确定对象的各个属性如何变化,看下面例子:
    这里fraction是分率,startValue和endValue分别是起始和终止对象的状态

    public class BallEvaluator implements TypeEvaluator {
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            Ball start = (Ball) startValue;//小球初始状态
            Ball end = (Ball) endValue;//小球终止状态
            
            Ball ball = new Ball();//当前小球
            //半径=初始+分率*(结尾-初始) 比如运动到一半,分率是0.5
            ball.r = (int) (start.r + fraction * (end.r - start.r));
            //颜色怎么渐变?
            ball.color = evaluateColor(fraction, start.color, end.color);
            return null;
        }
        
        /**
         * 根据分率计算颜色
         */
        private int evaluateColor(float fraction, Object startValue, Object endValue) {
            int startInt = (Integer) startValue;
            float startA = ((startInt >> 24) & 0xff) / 255.0f;
            float startR = ((startInt >> 16) & 0xff) / 255.0f;
            float startG = ((startInt >> 8) & 0xff) / 255.0f;
            float startB = (startInt & 0xff) / 255.0f;
    
            int endInt = (Integer) endValue;
            float endA = ((endInt >> 24) & 0xff) / 255.0f;
            float endR = ((endInt >> 16) & 0xff) / 255.0f;
            float endG = ((endInt >> 8) & 0xff) / 255.0f;
            float endB = (endInt & 0xff) / 255.0f;
    
            // convert from sRGB to linear
            startR = (float) Math.pow(startR, 2.2);
            startG = (float) Math.pow(startG, 2.2);
            startB = (float) Math.pow(startB, 2.2);
    
            endR = (float) Math.pow(endR, 2.2);
            endG = (float) Math.pow(endG, 2.2);
            endB = (float) Math.pow(endB, 2.2);
    
            // compute the interpolated color in linear space
            float a = startA + fraction * (endA - startA);
            float r = startR + fraction * (endR - startR);
            float g = startG + fraction * (endG - startG);
            float b = startB + fraction * (endB - startB);
    
            // convert back to sRGB in the [0..255] range
            a = a * 255.0f;
            r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
            g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
            b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
    
            return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
        }
    }
    

    看源码中怎么渐变颜色的:ArgbEvaluator.getInstance()
    可以看到有个计算颜色的方法,拿来用呗(我直接拷过去用)

    public static ValueAnimator ofArgb(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        anim.setEvaluator(ArgbEvaluator.getInstance());
        return anim;
    }
    
    ---->[计算颜色方法evaluate]---------------
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        //计算颜色方法详情......
    }
    

    3.使用估值器指定曲线方程运动

    该方程是二次曲线:y=x*x/800 当然你也可以定义自己喜欢的方程

    指定曲线方程运动.gif
    public class Ball {
        public int color;
        public int r;
        public int x;
        public int y;
    
        public Ball() {
        }
    
        public Ball(int r, int color) {
            this.color = color;
            this.r = r;
        }
    
        public Ball(int r, int color, int x, int y) {
            this.color = color;
            this.r = r;
            this.x = x;
            this.y = y;
        }
    }
    

    估值器修改:

    public class BallEvaluator implements TypeEvaluator {
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            Ball start = (Ball) startValue;
            Ball end = (Ball) endValue;
            Ball ball = new Ball();
            ball.color = evaluateColor(fraction, start.color, end.color);
            ball.r = (int) (start.r + fraction * (end.r - start.r));
            ball.x = (int) (start.x + fraction * (end.x - start.x));
            ball.y= ball.x*ball.x/800;//此处依赖x确定y值
            return ball;
        }
    }
    

    AnimatorView

    public class AnimatorView extends View {
        private static final String TAG = "AnimatorView";
        private Paint mPaint;
        private int mRadius = 50;
        private int dx;
        private int dy;
    
        private ValueAnimator mAnimator;
        private ValueAnimator mColorAnimator;
        private ValueAnimator mObjAnimator;
    
        public AnimatorView(Context context) {
            this(context, null);
        }
    
        public AnimatorView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(0xff94E1F7);
            Ball startBall = new Ball(50, 0xff94E1F7,0,0);
            Ball endBall = new Ball(100, 0xffF35519,500,1000);
            mObjAnimator = ValueAnimator.ofObject(new BallEvaluator(), startBall, endBall);
    
            mObjAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
            mObjAnimator.setDuration(1000);//设置时长
            mObjAnimator.setRepeatCount(1);//设置重复执行次数
            mObjAnimator.setRepeatMode(ValueAnimator.REVERSE);
    
            mObjAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    Ball ball = (Ball) animation.getAnimatedValue();
                    mRadius = ball.r;
                    mPaint.setColor(ball.color);
                    dx=ball.x;
                    dy=ball.y;
                    Log.e(TAG, "onAnimationUpdate: "+dx+":"+dy);
                    invalidate();
                }
            });
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.translate(dx, dy);
            canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mObjAnimator.start();
                    break;
                case MotionEvent.ACTION_UP:
            }
    
            return super.onTouchEvent(event);
    
        }
    }
    
    

    基本套路就是这样,有了ofObject,属性随意变,还怕动画吗?
    核心就是估值器的定义,其实ofInt,ofFloat,ofArgb只是适用了内置估值器而已
    本质上和ofObject并没有什么不同,可以看成单属性的简易版ofObject


    三、插值器

    如果估值器TypeEvaluator告诉你给怎么跑,那么插值器则告诉你跑多快
    下面演示一下三个内置插值器(内置还有几个,自己试试)和自定义的三个插值器

    插值器.gif
    1.自定义插值器:sin型先快后慢

    这里的input是从0~1变化的值,插值器就是改变input值的变化情况

    public class D_Sin_Inter implements TimeInterpolator {
        @Override
        public float getInterpolation(float input) {
            //input是一个从0~1均匀变化的值
            //从0到PI/2均匀变化的值
            float rad = (float) (Math.PI/2 * input);
            //返回这个弧度的sin值--sin曲线在0~PI/2区域是增长越来越缓慢,小球运动越来越缓慢
            return (float) (Math.sin(rad));
        }
    }
    

    2.自定义插值器:sin型先满后快
    public class A_Sin_Inter implements TimeInterpolator {
        @Override
        public float getInterpolation(float input) {
            //input是一个从0~1均匀变化的值
            //从0到PI/2均匀变化的值
            float rad = (float) (Math.PI/2 * input+Math.PI/2);
            //返回这个弧度的sin值--sin曲线在PI/2~PI区域是降低越来越快
            return (float) (1-(Math.sin(rad)));//返回1-
        }
    }
    

    3.自定义插值器:log型
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/26 0026:20:41<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:Log型先快后慢
     */
    public class D_Log_Inter implements TimeInterpolator {
        @Override
        public float getInterpolation(float input) {
            return (float) (Math.log10(1 + 9 * input));
        }
    }
    

    插值器实际上就是基于input加工,时间流动(每次刷新间隔)是基本恒定的,
    input是从0~1均匀变化的,通过input将其映射到一组对应关系上,就像数学中的函数
    input是x,称为自变量,因变量y由函数式和x确定,返回值便是y,供代码中使用(D_Sin_Inter如下)
    LinearInterpolator线性插值器也就是x=y,而已,本质是一样的


    4.优雅的实现测试代码

    只需在名字数组和插值器数组里对应添加即可,其他会自动处理

    public class AnimatorInterView extends View {
        private static final String TAG = "AnimatorView";
    
        private Paint mPaint;
        private int mRadius = 50;
        private int dx[];
        private String[] mStrings;
        private TimeInterpolator[] mInterpolators;
    
        public AnimatorInterView(Context context) {
            this(context, null);
        }
    
        public AnimatorInterView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(0xff94E1F7);
            mPaint.setTextSize(40);
            mStrings = new String[]{"Linear", "Bounce", "AOI", "OI", "D_sin", "D_log", "A_sin", "A_log"};
            mInterpolators = new TimeInterpolator[]{
                    new LinearInterpolator(),
                    new BounceInterpolator(),
                    new AnticipateOvershootInterpolator(),
                    new OvershootInterpolator(),
                    new D_Sin_Inter(),
                    new D_Log_Inter(),
                    new A_Sin_Inter()};
            dx = new int[mInterpolators.length];
        }
    
        private ValueAnimator createAnimator(int index, TimeInterpolator interpolator) {
            ValueAnimator mAnimator = ValueAnimator.ofInt(0, 800);
            mAnimator.setRepeatCount(1);//设置重复执行次数
            mAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
            mAnimator.setDuration(3000);//设置时长
            mAnimator.setInterpolator(interpolator);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    dx[index] = (int) animation.getAnimatedValue();
                    invalidate();
                }
            });
            return mAnimator;
        }
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            for (int i = 0; i < dx.length; i++) {
                canvas.translate(0, 120);
                mPaint.setColor(0xff94E1F7);
                canvas.drawCircle(mRadius + dx[i], mRadius, mRadius, mPaint);
                mPaint.setColor(0xff000000);
                mPaint.setStrokeWidth(4);
                canvas.drawLine(mRadius, mRadius, 800 + mRadius, mRadius, mPaint);
                canvas.drawText(mStrings[i], 800 + 3 * mRadius, mRadius, mPaint);
            }
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    for (int i = 0; i < mInterpolators.length; i++) {
                        createAnimator(i, mInterpolators[i]).start();
                    }
                    break;
                case MotionEvent.ACTION_UP:
            }
            return super.onTouchEvent(event);
        }
    }
    

    [插曲]:路径于Animator的结合

    核心是使用PathMeasure和DashPathEffect对路径的长度进行控制
    关于Path的这方面知识,这里不做详解,详见:Android关于Path你所知道的和不知道的一切

    路径动画.gif
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/12/26 0026:7:50<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:Animator与Path
     */
    public class AnimatorPathView extends View {
        private static final String TAG = "AnimatorView";
    
        private Paint mPaint;
        private Path mPath;
        private PathMeasure pathMeasure;
    
        public AnimatorPathView(Context context) {
            this(context, null);
        }
    
        public AnimatorPathView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(0xff94E1F7);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(10);
            mPaint.setStrokeJoin(Paint.Join.ROUND);
            //测量路径
            mPath = new Path();
            mPath = nStarPath(mPath, 8, 250, 160);//八角形路径
            pathMeasure = new PathMeasure(mPath, false);
        }
    
        private ValueAnimator createAnimator() {
            ValueAnimator mAnimator = ValueAnimator.ofInt(0, 800);
            mAnimator.setRepeatCount(1);//设置重复执行次数
            mAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
            mAnimator.setDuration(3000);//设置时长
            mAnimator.setInterpolator(new AnticipateOvershootInterpolator());
    
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float value = animation.getAnimatedFraction();
                    //核心:创建DashPathEffect
                    DashPathEffect effect = new DashPathEffect(
                            new float[]{
                                    pathMeasure.getLength(),
                                    pathMeasure.getLength()},
                            value * pathMeasure.getLength());
                    mPaint.setPathEffect(effect);
                    invalidate();
                }
            });
            return mAnimator;
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.translate(250, 250);
            canvas.drawPath(mPath, mPaint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    createAnimator().start();
                    break;
                case MotionEvent.ACTION_UP:
            }
            return super.onTouchEvent(event);
        }
    
        /**
         * n角星路径
         *
         * @param num 几角星
         * @param R   外接圆半径
         * @param r   内接圆半径
         * @return n角星路径
         */
        public static Path nStarPath(Path path, int num, float R, float r) {
            float perDeg = 360 / num;
            float degA = perDeg / 2 / 2;
            float degB = 360 / (num - 1) / 2 - degA / 2 + degA;
            path.moveTo((float) (Math.cos(rad(degA)) * R), (float) (-Math.sin(rad(degA)) * R));
            for (int i = 0; i < num; i++) {
                path.lineTo(
                        (float) (Math.cos(rad(degA + perDeg * i)) * R),
                        (float) (-Math.sin(rad(degA + perDeg * i)) * R));
                path.lineTo(
                        (float) (Math.cos(rad(degB + perDeg * i)) * r),
                        (float) (-Math.sin(rad(degB + perDeg * i)) * r));
            }
            path.close();
            return path;
        }
    
        /**
         * 角度制化为弧度制
         *
         * @param deg 角度
         * @return 弧度
         */
        public static float rad(float deg) {
            return (float) (deg * Math.PI / 180);
        }
    }
    

    第二节:ValueAnimator之子ObjectAnimator和TimeAnimator

    作为孩子,它老爸能做的它也能做,并且还会有一些自己的特长
    ObjectAnimator针对有setXxx方法的属性,进行的"Xxx"属性变化动画
    注:Xxx的首字母大小写都可以


    一、View内置属性的测试

    1.简单入门--下移示例:
    下移动.gif
    private ObjectAnimator mMoveDown;//下移动画
    
    mMoveDown = ObjectAnimator//创建实例
            //(View,属性名,初始化值,结束值)
            .ofFloat(this, "translationY", 0, 300)
            .setDuration(1000);//设置时常
    
    @Override//绘制方法
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(50, 50, 50, mPaint);
    }
    
    mMoveDown.start();//开启动画
    

    加上背景看一下,可以看出是整个View进行了变化。

    加背景.gif
    2.常用属性一览:
    属性名 演示 解释
    alpha 透明度1~0
    translationX X方向移动
    translationY Y方向移动
    rotation 旋转(默认View中心点)
    rotationX X轴旋转(默认View中心横轴)
    rotationY Y轴旋转(默认View中心纵轴)
    scaleX X缩放 倍数
    scaleY Y缩放 倍数

    3.旋转、缩放中心点设置:
    setPivotX(200);
    setPivotY(200);
    
    旋转中心点.gif
    4.多参数情况(多参情况Animator家族皆适用)

    0-->360 360-->0 0-->90

    .ofFloat(this, "rotation", 0, 360,360,0,0,90)
    
    多参数.gif

    二、自定义ObjectAnimator属性

    内置的只是一些常用的,我们也可以自定义自己的属性

    1.自定义圆的大小动画

    必须用一个setXxx的方法,属性名则为xxx,调用重绘方法

    public void setRadius(int radius) {
        mRadius = radius;
        invalidate();//记得重绘
    }
    
    ObjectAnimator//创建实例
            //(View,属性名,初始化值,结束值)
            .ofInt(this, "Radius", 100, 50,100,20,100)
            .setDuration(3000);//设置时常
    
    自定义半径.gif
    2.自定义颜色动画
    public void setColor(int color) {
        mColor = color;
        mPaint.setColor(mColor);
        invalidate();//记得重绘
    }
    
    colorAnimator = ObjectAnimator//创建实例
             //(View,属性名,初始化值,结束值)
             .ofInt(this, "color", 0xff0000ff, 0xffF2BA38, 0xffDD70BC)
             .setDuration(3000);
    colorAnimator.setEvaluator(new ArgbEvaluator());//颜色的估值器
    
    自定义颜色.gif
    3.ValueAnimator和ObjectAnimator的区别在哪?
    1.ValueAnimator需要手动添加监听,手动获取ValueAnimator的数据,手动书写变更逻辑
    2.ObjectAnimator可以不用进行更新监听,核心在`setXxx`里进行,  
    也就是每次更新时会自己走setXxx里的方法,这样方便在外部使用来动态改变属性
    3.ValueAnimator的灵活性要好,毕竟自己动手,可以脑洞大开,想怎么玩怎么玩
    4.ObjectAnimator针对有setXxx的属性进行动画,两者的侧重点不同  
    5.总的来说ObjectAnimator向于应用(简洁,快速),ValueAnimator偏向于操作(灵活,多变)
    

    三、TimeAnimator

    这个类总共代码100行,而且几乎一半都是注释
    它继承自ValueAnimator,可谓也是Animator家族的掌上明珠,但非常纯真与专注
    她想做的只有一件事:提供一条时间流(每个16或17ms回调一次方法)

    mAnimator = new TimeAnimator();
    ////(自己,运行总时长,每次回调的时间间隔)
    mAnimator.setTimeListener((animation, totalTime, deltaTime) -> {
        Log.e(TAG, "totalTime:" + totalTime + ",  deltaTime:" + deltaTime);
        if (totalTime > 300) {
            animation.pause();
        }
    });
    

    运行结果:

    2018-12-27 10:09:35.047  E/TimeAnimatorView: totalTime:0,  deltaTime:0
    2018-12-27 10:09:35.051  E/TimeAnimatorView: totalTime:2,  deltaTime:2
    2018-12-27 10:09:35.068  E/TimeAnimatorView: totalTime:19,  deltaTime:17
    2018-12-27 10:09:35.085  E/TimeAnimatorView: totalTime:36,  deltaTime:17
    2018-12-27 10:09:35.101  E/TimeAnimatorView: totalTime:52,  deltaTime:16
    2018-12-27 10:09:35.118  E/TimeAnimatorView: totalTime:69,  deltaTime:17
    2018-12-27 10:09:35.135  E/TimeAnimatorView: totalTime:86,  deltaTime:17
    2018-12-27 10:09:35.151  E/TimeAnimatorView: totalTime:102,  deltaTime:16
    2018-12-27 10:09:35.167  E/TimeAnimatorView: totalTime:119,  deltaTime:17
    2018-12-27 10:09:35.184  E/TimeAnimatorView: totalTime:136,  deltaTime:17
    2018-12-27 10:09:35.200  E/TimeAnimatorView: totalTime:152,  deltaTime:16
    2018-12-27 10:09:35.218  E/TimeAnimatorView: totalTime:169,  deltaTime:17
    2018-12-27 10:09:35.234  E/TimeAnimatorView: totalTime:186,  deltaTime:17
    2018-12-27 10:09:35.251  E/TimeAnimatorView: totalTime:202,  deltaTime:16
    2018-12-27 10:09:35.268  E/TimeAnimatorView: totalTime:219,  deltaTime:17
    2018-12-27 10:09:35.284  E/TimeAnimatorView: totalTime:236,  deltaTime:17
    2018-12-27 10:09:35.300  E/TimeAnimatorView: totalTime:252,  deltaTime:16
    2018-12-27 10:09:35.318  E/TimeAnimatorView: totalTime:269,  deltaTime:17
    2018-12-27 10:09:35.334  E/TimeAnimatorView: totalTime:286,  deltaTime:17
    2018-12-27 10:09:35.350  E/TimeAnimatorView: totalTime:303,  deltaTime:17
    

    这样关于ValueAnimator基本上就结束了(还有几个监听,最后一起将)


    四、AnimatorSet

    综合前几次的动画效果,拼装在一起,AnimatorSet本身并不难

    1.Builder模式的AnimatorSet

    源码一翻,可见里面有个Builder,可就是建造者模式了,
    每个动画在AnimatorSet中是一个Node,Budiler中的方法就是:
    为处理当前节点和插入节点的关系,看下面一组动画 :

    动画集合.gif
    mSet//半径-->移动+渐变-->变色
            .play(translationX)//移动
            .with(alpha)//渐变
            .after(radiusAnimator)//半径
            .before(colorAnimator);//变色
    

    测试源码:

    public class AnimatorSetView extends View {
        private static final String TAG = "AnimatorView";
        private Paint mPaint;
        private int mRadius = 50;
        private int mColor = 50;
        private ObjectAnimator colorAnimator;
        private ObjectAnimator radiusAnimator;
        ObjectAnimator translationX;
        ObjectAnimator alpha;
        private AnimatorSet mSet;
    
        public AnimatorSetView(Context context) {
            this(context, null);
        }
    
        public AnimatorSetView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(0xff94E1F7);
            mSet = new AnimatorSet();
            translationX = ObjectAnimator//创建实例
                    //(View,属性名,初始化值,结束值)
                    .ofFloat(this, "translationX", 0, 300, 150, 100, 20, 100)
                    .setDuration(3000);//设置时常
            alpha = ObjectAnimator//创建实例
                    //(View,属性名,初始化值,结束值)
                    .ofFloat(this, "alpha", 1, 0.5f, 1, 0, 1)
                    .setDuration(3000);//设置时常
            radiusAnimator = ObjectAnimator//创建实例
                    //(View,属性名,初始化值,结束值)
                    .ofInt(this, "Radius", 50, 100, 50, 100, 20, 100)
                    .setDuration(3000);//设置时常
            colorAnimator = ObjectAnimator//创建实例
                    //(View,属性名,初始化值,结束值)
                    .ofInt(this, "color", 0xff0000ff, 0xffF2BA38, 0xffDD70BC)
                    .setDuration(3000);
            colorAnimator.setEvaluator(new ArgbEvaluator());//颜色的估值器
            mSet//半径-->移动+渐变-->变色
                    .play(translationX)
                    .with(alpha)
                    .after(radiusAnimator)
                    .before(colorAnimator);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mSet.start();
                    break;
                case MotionEvent.ACTION_UP:
            }
            return super.onTouchEvent(event);
        }
    
        public void setRadius(int radius) {
            mRadius = radius;
            setMeasuredDimension(mRadius * 2, mRadius * 2);
            invalidate();//记得重绘
        }
    
        public void setColor(int color) {
            mColor = color;
            mPaint.setColor(mColor);
            invalidate();//记得重绘
        }
    }
    

    2.AnimatorSet自身方法:

    顾名思义:也就是一起运动还是分批运动

    mSet.playTogether(translationX,alpha,radiusAnimator,colorAnimator);
    mSet.playSequentially(translationX,alpha,radiusAnimator,colorAnimator);
    

    四、Animator的监听:

    可见Animator有两个内部接口,AnimatorListenerAnimatorPauseListener
    AnimatorListenerAdapter是两个接口的空实现类,标准适配器模式。
    ValueAnimator作为孩子,有自己的一个接口AnimatorUpdateListener

    监听接口关系.png
    1、AnimatorListener:动画监听

    Animator中的监听器两个孩子也都能用

       //动画开启时回调
        void onAnimationStart(Animator animation);
        //动画结束时回调
        void onAnimationEnd(Animator animation);
        //动画取消时回调
        void onAnimationCancel(Animator animation);
        //重复时回调
        void onAnimationRepeat(Animator animation);
    

    2.动画测试

    开始时设为绿色-->重复时设为随机色-->取消是大小变为50-->结束时设为蓝色

    动画监听.gif
    mTranslationX = translationX();
    mTranslationX.setRepeatMode(ValueAnimator.REVERSE);
    mTranslationX.setRepeatCount(ValueAnimator.INFINITE);
    
    mTranslationX.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            //开始时设为绿色
            setColor(Color.GREEN);
        }
    
        @Override
        public void onAnimationEnd(Animator animation) {
            //结束时设为蓝色
            setColor(Color.BLUE);
        }
    
        @Override
        public void onAnimationCancel(Animator animation) {
            //取消时大小变为50
            setCircleR(50);
        }
    
        @Override
        public void onAnimationRepeat(Animator animation) {
            //重复时设为随机色
            setColor(ColUtils.randomColor());
        }
    });
    mTranslationX.start();
    
     mTranslationX.cancel();//取消动画
    

    3、AnimatorPauseListener:动画暂停监听
    //暂停回调
    void onAnimationPause(Animator animation);
    //恢复回调
    void onAnimationResume(Animator animation);
    

    效果如下:点击运动,右滑暂停颜色变黄,下滑恢复颜色变蓝

    暂停监听.gif
    mTranslationX.addPauseListener(new Animator.AnimatorPauseListener() {
        @Override
        public void onAnimationPause(Animator animation) {
            setColor(Color.YELLOW);//暂停黄色
        }
        @Override
        public void onAnimationResume(Animator animation) {
            setColor(Color.BLUE);//恢复蓝色
        }
    });
    

    4、AnimatorUpdateListener: ValueAnimator一系专有监听
    //更新时回调
    void onAnimationUpdate(ValueAnimator animation);
    

    效果如下:每当更新是将半径和位移联动

    更新监听.gif
    mTranslationX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mCircleR = (Float) animation.getAnimatedValue();
            invalidate();
        }
    });
    

    五、Animator家族在xml中的使用:

    在res下创建:animator文件夹

    1.Animator标签

    直接用animator标签感觉也有点麻烦,这里看一下吧

    xml中属性 含义 代码中对应
    duration 播放的时长 setDuration()
    valueType 参数值类型 ofXXX
    valueFrom 初始值 ofXXX(第1参)
    valueTo 结束值 ofXXX(第2参)
    startOffset 延时 startDelay()
    repeatCount 重复次数 setRepeatCount()
    interpolator 插值器 setRepeatMode()

    1.1.animator.xml
    xml中使用.gif
    <?xml version="1.0" encoding="utf-8"?>
    <animator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="2000"
        android:repeatCount="2"
        android:repeatMode="reverse"
        android:startOffset="1000"
        android:valueFrom="0dp"
        android:valueType="floatType"
        android:valueTo="200dp">
    </animator>
    

    1.2.布局
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        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=".MainActivity">
        <View
            android:id="@+id/id_btn_go"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginStart="24dp"
            android:layout_marginTop="32dp"
            android:background="#3ED7FA"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>
    </android.support.constraint.ConstraintLayout>
    

    1.3.代码中使用:MainActivity

    由Xml获取ValueAnimator,之后的事,就自己动手,感觉有点麻烦

    View button = findViewById(R.id.id_btn_go);
    ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.animator);
    
    animator.addUpdateListener(anim->{
        float animatedValue = (float) anim.getAnimatedValue();
        button.setTranslationX(animatedValue);
    });
    
    button.setOnClickListener((v)->{
        animator.start();
    });
    

    2.setobjectAnimator标签

    objectAnimator多了一个propertyName属性,其余一致


    2.1set_obj_animator.xml
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:ordering="sequentially">
        <objectAnimator
            android:duration="1500"
            android:propertyName="rotationY"
            android:valueFrom="0"
            android:valueTo="180"/>
        <objectAnimator
            android:duration="1500"
            android:propertyName="alpha"
            android:valueFrom="0.3f"
            android:valueTo="1f"/>
        <objectAnimator
            android:duration="1500"
            android:propertyName="translationX"
            android:valueFrom="0"
            android:valueTo="180dp"/>
    </set>
    

    2.2:代码中使用
    View button = findViewById(R.id.id_btn_go);
    Animator set_obj = AnimatorInflater.loadAnimator(this, R.animator.set_obj_animator);
    et_obj.setTarget(button);
            
    button.setOnClickListener((v)->{
        set_obj.start();
    });
    

    3、最后看一下我大objectAnimator变换路径

    详情可见:Android资源res之矢量图完全指南(加SVG-path命令分析)

    path.png
    箭头:M8,50, l100,0 M0,47, l40,40 M0,52 l40 -40
    菜单:M0,50, l80,0 M0,80, l80,0 M0,20 l80 0
    
    path变形 变形+旋转

    1.将两个path字符串放入string.xml

    直接写也可以,但复用不方便

    <resources>
        <string name="app_name">test</string>
        <string name="path_from">M8,50, l100,0 M0,47, l40,40 M0,52 l40 -40 </string>
        <string name="path_to">M0,50, l80,0 M0,80, l80,0 M0,20 l80 0</string>
    </resources>
    

    2.矢量图:path_test.xml
    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
            android:width="48dp"
            android:height="48dp"
            android:viewportWidth="100"
            android:viewportHeight="100">
        <group
            android:translateX="4"
            android:translateY="4">
            <path
                android:pathData="M0,0 A30,50,90,0,1,50,50"
                android:strokeWidth="4"
                android:strokeColor="@color/black"/>
        </group>
    </vector>
    

    3.旋转动画:rotation_animator.xml
    <?xml version="1.0" encoding="utf-8"?>
    <objectAnimator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="180"/>
    
    

    4.路径动画:path_animator.xml
    <?xml version="1.0" encoding="utf-8"?>
    
    <objectAnimator
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:interpolator="@android:interpolator/linear"
        android:propertyName="pathData"
        android:valueFrom="@string/path_from"
        android:valueTo="@string/path_to"
        android:valueType="pathType"/>
    

    5.矢量图文件:icon_path.xml
    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
            android:width="48dp"
            android:height="48dp"
            android:viewportWidth="100"
            android:viewportHeight="100">
        <group android:name="container"
            android:translateX="8"
            android:pivotX="50"
               android:scaleY="0.8"
               android:scaleX="0.8"
            android:pivotY="50">
    
            <path
                android:name="alpha_anim"
                android:pathData="@string/path_from"
                android:strokeWidth="8"
                android:strokeColor="#000"/>
        </group>
    </vector>
    

    6.整合动画:anima_path.xml
    <animated-vector
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/icon_path">
        <target
            android:name="alpha_anim"
            android:animation="@animator/path_animator"/>
        <target
            android:name="container"
            android:animation="@animator/rotation_animator">
        </target>
    </animated-vector>
    

    7.使用动画:
     <ImageView
         android:id="@+id/id_iv"
         android:layout_width="200dp"
         android:layout_height="200dp"
         android:src="@drawable/anima_path"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"/>
    
    //点击时:
    Drawable drawable = mIdIv.getDrawable();
    if (drawable instanceof Animatable){
        ((Animatable) drawable).start();
    }
    

    ok,这样就行了,你可以随意定制两个路径,但必须保证两个路径的指令相同,不然会崩


    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 备注
    V0.1--github 2018-12-27 Android动画Animator家族使用指南
    2.更多关于我
    笔名 QQ 微信 爱好
    张风捷特烈 1981462002 zdl1994328 语言
    我的github 我的简书 我的掘金 个人网站
    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持


    icon_wx_200.png

    相关文章

      网友评论

        本文标题:Android动画Animator家族使用指南

        本文链接:https://www.haomeiwen.com/subject/tisrlqtx.html