Android 動畫 Animator 家族使用指南

張風捷特烈發表於2018-12-27

零、前言:本文知識點

  • 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
Android 動畫 Animator 家族使用指南
Android 動畫 Animator 家族使用指南
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

顏色變化 顏色大小
Android 動畫 Animator 家族使用指南
Android 動畫 Animator 家族使用指南

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,而已,本質是一樣的

Android 動畫 Animator 家族使用指南


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
Android 動畫 Animator 家族使用指南
透明度1~0
translationX
Android 動畫 Animator 家族使用指南
X方向移動
translationY
Android 動畫 Animator 家族使用指南
Y方向移動
rotation
Android 動畫 Animator 家族使用指南
旋轉(預設View中心點)
rotationX
Android 動畫 Animator 家族使用指南
X軸旋轉(預設View中心橫軸)
rotationY
Android 動畫 Animator 家族使用指南
Y軸旋轉(預設View中心縱軸)
scaleX
Android 動畫 Animator 家族使用指南
X縮放 倍數
scaleY
Android 動畫 Animator 家族使用指南
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有兩個內部介面,AnimatorListenerAnimatorPauseListenerAnimatorListenerAdapter是兩個介面的空實現類,標準介面卡模式。
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屬性,其餘一致

Android 動畫 Animator 家族使用指南


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變形 變形+旋轉
Android 動畫 Animator 家族使用指南
Android 動畫 Animator 家族使用指南

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

相關文章