Android 動畫 Animator 家族使用指南

零、前言:本文知識點

  • ValueAnimator的認識與使用
  • 估值器TypeEvaluator的自定義與使用
  • 插值器TimeInterpolator的自定義與使用
  • Path與Animator的結合使用
  • ObjectAnimator的自定義與使用
  • TimeAnimator的使用
  • AnimatorSet動畫集合的使用
  • Animator家族的監聽器介紹與使用
  • Animator家族在xml中的使用

一直用動畫,貌似尚未好好地總結一下,趁有空,總結一波
所謂動畫,就是不停變化,在視覺上達到連續的效果
Animator的體系並不複雜,但內部實現挺複雜的,不少類常年埋沒於底層,不見天日
如:PropertyValuesHolder及其子類Keyframes族Keyframe族KeyframeSet族
今天試着讀了一下源碼,基本上讀的懵懵懂懂,總的思路算是把握了android


第一節:ValueAnimator的使用

1、簡單的使用

0.Animator家族簡單認識:

Animator是一個抽象類,不可用,只能找它的子類
如今先看很是經常使用的ValueAnimatorgit

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();
複製代碼

打印結果分析:github

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);
    }
}
複製代碼

其實道理很簡單,就是把打印輸出換成了刷新視圖,並且半徑在不斷變化canvas


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);//設置時長
複製代碼

2、ofArgbofObject

顏色變化 顏色大小

1.改變顏色:ofArgb

傳入兩個顏色(起始色和終止色)bash

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分別是起始和終止對象的狀態app

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()
能夠看到有個計算顏色的方法,拿來用唄(我直接拷過去用)dom

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


3、插值器

若是估值器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的首字母大小寫均可以


1、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


2、自定義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偏向於操做(靈活,多變)
複製代碼

3、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基本上就結束了(還有幾個監聽,最後一塊兒將)


4、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);
複製代碼

4、Animator的監聽:

可見Animator有兩個內部接口,AnimatorListenerAnimatorPauseListenerAnimatorListenerAdapter是兩個接口的空實現類,標準適配器模式。
ValueAnimator做爲孩子,有本身的一個接口AnimatorUpdateListener

監聽接口關係.png

一、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();//取消動畫
複製代碼

三、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);//恢復藍色
    }
});
複製代碼

四、AnimatorUpdateListener: ValueAnimator一系專有監聽
//更新時回調
void onAnimationUpdate(ValueAnimator animation);
複製代碼

效果以下:每當更新是將半徑和位移聯動

更新監聽.gif

mTranslationX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mCircleR = (Float) animation.getAnimatedValue();
        invalidate();
    }
});
複製代碼

5、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();
});
複製代碼

三、最後看一下我大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
相關文章
相關標籤/搜索