Android 动画 Animator 家族使用指南-程序员宅基地

技术标签: 移动开发  

零、前言:本文知识点
  • ValueAnimator的认识与使用
  • 估值器TypeEvaluator的自定义与使用
  • 插值器TimeInterpolator的自定义与使用
  • Path与Animator的结合使用
  • ObjectAnimator的自定义与使用
  • TimeAnimator的使用
  • AnimatorSet动画集合的使用
  • Animator家族的监听器介绍与使用
  • Animator家族在xml中的使用

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


第一节:ValueAnimator的使用

一、简单的使用
0.Animator家族简单认识:

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


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刷新界面,并动态改变数值

/**
 * 作者:张风捷特烈<br/>
 * 时间:2018/12/26 0026:7:50<br/>
 * 邮箱:[email protected]<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 当然你也可以定义自己喜欢的方程

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告诉你给怎么跑,那么插值器则告诉你跑多快
下面演示一下三个内置插值器(内置还有几个,自己试试)和自定义的三个插值器


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/>
 * 邮箱:[email protected]<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你所知道的和不知道的一切

/**
 * 作者:张风捷特烈<br/>
 * 时间:2018/12/26 0026:7:50<br/>
 * 邮箱:[email protected]<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.简单入门--下移示例:

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进行了变化。


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);
复制代码


4.多参数情况(多参情况Animator家族皆适用)

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

.ofFloat(this, "rotation", 0, 360,360,0,0,90)
复制代码


二、自定义ObjectAnimator属性

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

1.自定义圆的大小动画

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

public void setRadius(int radius) {
    mRadius = radius;
    invalidate();//记得重绘
}
复制代码
ObjectAnimator//创建实例
        //(View,属性名,初始化值,结束值)
        .ofInt(this, "Radius", 100, 50,100,20,100)
        .setDuration(3000);//设置时常
复制代码


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());//颜色的估值器
复制代码


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中的方法就是: 为处理当前节点和插入节点的关系,看下面一组动画 :

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有两个内部接口,AnimatorListenerAnimatorPauseListenerAnimatorListenerAdapter是两个接口的空实现类,标准适配器模式。
ValueAnimator作为孩子,有自己的一个接口AnimatorUpdateListener

1、AnimatorListener:动画监听

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

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

2.动画测试

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

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);
复制代码

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

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);
复制代码

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

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 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命令分析)

箭头: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----看到这里,我在此感谢你的喜欢与支持


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_34319999/article/details/87951168

智能推荐

Python计算机视觉第九章 图像分割_最小割结果进行图像分割-程序员宅基地

文章浏览阅读1.3k次。图像分割1 图割(Graph Cut)1.1 从图像创建图图像分割是将一幅图像分割成有意义区域的过程。区域可以是图像的前景与背景或图像中一些单独的对象。这些区域可以利用一些诸如颜色、边界或近邻相似性等特征进行构建。本章中,我们将看到一些不同的分割技术。1 图割(Graph Cut)图论中的图(graph)是由若干节点(有时也称为顶点)和连接节点的边构成的集合。下图给出了一个示例。边可以是有向的或无向的,并且这些可能有与它们相关联的权重。图割是将一个有向图分隔成两个互不相交的集合,可以用来解决很多计_最小割结果进行图像分割

C# winform C/S WebBrowser 微信第三方登录-程序员宅基地

文章浏览阅读730次。网上很多的资料都是B/S结构的,这里是基于C# C/S 结构的微信第三方授权登录一、准备知识1 http Get和Post方法。做第三方授权登录,获取信息基本上都是用get和post方法,做之前需要进行基本的了解,基本上网页都是get。2 微信开发文档。这里参考的是: 微信开发平台——资源中心——网址应用——微信登录功能 。3 熟悉WebBrowser控件。这里熟悉的主要是webB..._c/s应用程序支持微信登录

python宝典 宋强 pdf_Python宝典/宝典丛书:杨佩璐//宋强 : 电子电脑 :计算机技术 :程序与语言 ...-程序员宅基地

文章浏览阅读279次。导语《Python宝典》由杨佩璐、宋强等编著,本书内容共分三篇26章,分别为入门篇、高级篇和案例篇,针对Python的常用扩展模块给出了详细的语法介绍,并且给出了典型案例,通过对本书的学习,读者能够很快地使用Python进行编程开发。本书以PVthon 3.x为基础进行讲解,并在与Python 2.x有区别的地方加上了相关介绍,使Pvthon 2.x和Pvthon 3.x的读者都能使用本书。内容提..._python宝典杨佩璐

js对象遍历输出的时候真的是按照顺序输出吗?_map循环遍历对象数据是顺序的吗-程序员宅基地

文章浏览阅读1.3w次。js对象遍历输出的时候真的是按照顺序输出吗?  下边就来实践一下:var obj={'3':'ccc',name:'abc',age:23,school:'sdfds',class:'dfd',hobby:'dsfd'};Object.keys(obj)输出:["3", "name", "age", "school", "class", "hobby"];换一下对象顺序,var obj=_map循环遍历对象数据是顺序的吗

HDU--4305(生成树计数)-程序员宅基地

文章浏览阅读176次。2015-09-0722:23:26【传送门】题意:平面上300个点,如果两点之间距离<=R,且两点形成的线段上没有另外的点,那么两点之间有一条无向边。问生成树的方案数。思路:暴力n^2建图,关于判断两点形成线段上是否有其他点,比如判断 k 点知否在 i ,j 之间,首先看斜率是否相等,不能直接求斜率,而应该转化为乘式;再判断 dis(i,k)+dis(k,j)是否等..._hdu4305

阿里云OSS请求文件跨域问题Access-Control-Allow-Origin_oss预签名url access-control-allow-origin-程序员宅基地

文章浏览阅读2.2k次。跨域问题网上很多解决方案提示到这里配置但是不生效,一定要勾选Vary:Origin这个选项,请求的时候浏览器记得请求在控制台要清理缓存。_oss预签名url access-control-allow-origin

随便推点

git push error: 403_git push 403-程序员宅基地

文章浏览阅读5.0k次。我今天在把本地仓库push到远程仓库的时候,出现了问题:一会儿是这个:fatal: unable to access 'https://github.com/***/***/': OpenSSL SSL_read: Connection was reset, errno 10054一会儿又变成了这个:remote: Permission to Dxuan-chen/huashan.git denied to xuanfchen.fatal: unable to access 'https:_git push 403

excel shell合成_1分钟拆解:「如何将10多个工作表sheet,合并成一张?」-程序员宅基地

文章浏览阅读1.1k次。大家好,我是有讲课堂的认证达人:解题宝宝。今天到了VBA教学时间!因为今天阿,解题宝宝无聊闲逛,惊奇发现了两份VBA代码,特意分享给大家。是解决如何合并大量不同的工作表哒。多少张都没问题!亲测有效!分为以下两种情况☟01 合并同一工作簿的不同工作表。效果长这样:本来,同一工作簿下,一个排班表是一张sheet;接下里,就变成:所有排班表汇总成一张sheet,格式还自动排好!◎ 效果演示代码立即备上..._execl通过shell脚本的方式合并

程序员,30岁,还没有做管理层?你应该看看这篇文章_33岁一直做it,还没到管理层-程序员宅基地

文章浏览阅读1k次。如果你30岁,你是一名程序员,那么你就要好好把握接下来的黄金5年。_33岁一直做it,还没到管理层

AMOS分析技术:二阶验证性因子分析-程序员宅基地

文章浏览阅读4.8w次,点赞5次,收藏90次。基础准备草堂君在前面介绍了验证性因子分析的内容,包括验证性因子分析与探索性因子分析的区别联系,斜交验证性因子分析和正交验证性因子分析,可以点击下方文章链接回顾:AMOS分析技术:验证性因子分析介绍;信度与效度指标详解AMOS分析技术:斜交验证性因子分析;介绍如何整理出能够放入论文的模型信效度结果AMOS分析技术:正交验证性因子分析;模型拟合质量好,模型就一定好吗?今天草堂君要介绍的是二阶验证性因子_二阶验证性因子分析

读jQuery之二十(Deferred对象)-程序员宅基地

文章浏览阅读55次。为什么80%的码农都做不了架构师?>>> ...

C++代码实现Top-K问题最优解决办法_c++用优先队列解决topk-程序员宅基地

文章浏览阅读1.6k次。Top-K问题Top-K问题1、问题描述2、解法思想和实现Top-K问题1、问题描述Top-K问题是一个十分经典的问题,一般有以下两种方式来描述问题:在10亿的数字里,找出其中最大的100个数;或者在一个包含n个整数的数组中,找出最大的100个数。前边两种问题描述稍有区别,但都是说的Top-K问题,前一种描述方式是说这里也许没有足够的空间存储大量的数字或其他东西,我们最好可以在一边输入数据,一边求出结果,而不需要存储数据;后一种说法则表示可以存储数据,这种情况下,最简单直观的想法就是对数组进行排序,_c++用优先队列解决topk

推荐文章

热门文章

相关标签