1. 程式人生 > >自定義控制元件之下載控制元件1(DownloadView1)

自定義控制元件之下載控制元件1(DownloadView1)

前段時間在乾貨集中營看到了兩個炫酷的下載按鈕:

     


可惜是隔壁 iOS 的孩子,怎麼辦,我也好喜歡,emmm,某該,只能自己模仿著實現一下了。先從第一個入手(第二個波浪效果暫時還不會)。


1 準備動作

寫過幾次自定義控制元件後,我也掌握了基本姿勢,首先我們將第一個控制元件肢解了,我大致將它分為五步,第一步,把冰箱門開啟,第二步,把大象。。。去去去,要啥自行車自行車。第一步:先畫出一個圓,圓環顏色有點像淺青色,由深青色填充,圓中間有一個常見的下載的箭頭;第二步:下載箭頭的豎線收縮變成一個點;第三步;下載箭頭的下半部分往上變成了一條橫線,同時剛才豎線收縮成的點往上移動到圓環正上方;第四步;下載箭頭的橫線收縮變成一個點,同時移動到圓環正上方的點向從圓的兩邊分別畫半圓直到畫完整個圓環;第五步:下載橫線收縮成的點變成綠色的對勾。 OK,瞭解了分解動作後,我們先新建一個控制元件 DownloadView1,然後初始化背景色和畫筆,確定一個半徑:
public class DownloadView1 extends View {
    private Paint mPaint;
    private float radius = 150; //圓半徑

    public DownloadView1(Context context) {
        this(context, null);
    }

    public DownloadView1(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DownloadView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setStrokeWidth(radius / 10);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setBackgroundColor(Color.rgb(10, 39, 46));
    }
}

2 畫圓和箭頭

下載箭頭的豎線可以直接用 drawLine() 方法畫,箭頭可以用 drawPath() 方法畫,在構造方法中新建一個 Path 物件,然後定義一個 lineLength 為半徑的一半。
public class DownloadView1 extends View {
    private Paint mPaint;
    private Path mPath;
    private float radius = 150; //圓半徑
    private float lineLength = radius / 2;  //下載的標誌中間的豎線的長度
    private float arrowTurningPointY = radius / 2;   //下載的標誌下面箭頭的轉折點的 Y 軸座標
    private float arrowLength = radius / 2;  //下載的標誌下面箭頭變成水平線後的長度

    public DownloadView1(Context context) {
        this(context, null);
    }

    public DownloadView1(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DownloadView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setStrokeWidth(radius / 10);
        mPaint.setStrokeCap(Paint.Cap.ROUND);

        mPath = new Path();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setBackgroundColor(Color.rgb(10, 39, 46));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(Color.rgb(10, 39, 46));
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);

        mPaint.setColor(Color.rgb(37, 66, 73));
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);

        mPaint.setColor(Color.rgb(255, 255, 255));
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - lineLength
                , getMeasuredWidth() / 2, getMeasuredHeight() / 2 + lineLength
                , mPaint);

        mPaint.setColor(Color.rgb(255, 255, 255));
        mPaint.setStyle(Paint.Style.STROKE);
        mPath.reset();
        mPath.moveTo(getMeasuredWidth() / 2 - arrowLength, getMeasuredHeight() / 2);
        mPath.lineTo(getMeasuredWidth() / 2, getMeasuredHeight() / 2 + arrowTurningPointY);
        mPath.lineTo(getMeasuredWidth() / 2 + arrowLength, getMeasuredHeight() / 2);
        canvas.drawPath(mPath, mPaint);
    }
}

現在已經是這樣了:


然後我們就可以在這個基礎上去實現動畫了,後面為了節省篇幅,就只貼關鍵程式碼了,主要看思路,最終的完整程式碼在最後貼出來。


3 下載箭頭豎線收縮成一個點

我們需要一個變數來控制這個豎線的長度,通過屬性動畫來讓這個長度越來越短就行了:
            //畫下載箭頭
            mPaint.setColor(Color.rgb(255, 255, 255));
            mPaint.setStyle(Paint.Style.STROKE);
            if (lineLength == 0) {
                //下載箭頭的豎線收縮成點後應該畫點
                canvas.drawPoint(getMeasuredWidth() / 2, linePointY, mPaint);
            } else {
                //否則應該畫線
                canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - lineLength
                        , getMeasuredWidth() / 2, getMeasuredHeight() / 2 + lineLength
                        , mPaint);
            }

然後寫一個 startAnimation() 方法,點選後呼叫這個方法:
private void startAnimation() {
        //每個動畫設定不同的時長
        lineLengthAnimator.setDuration(500);

        //新增動畫監聽,實際上就是一直重繪
        lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);

        //設定動畫的播放順序
        AnimatorSet mAnimatorSet = new AnimatorSet();
        mAnimatorSet.play(lineLengthAnimator);
	mAnimatorSet.start();
}

這是動畫更新的監聽器:
    private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            invalidate();
        }
    };



4 下載箭頭的箭頭部分變成橫線,同時豎線的圓點上移

下面箭頭變成橫線,我們則需要修改 path 中路徑中的轉折點,我定義的是 arrowTurningPointY,先讓它移到圓心,然後往下移一點再移回來形成抖一下的效果,同時下載箭頭豎線變成的點在 Y 軸的座標也要變,從圓心移到圓環正上方,定義了 linePointY 來控制,所以增加幾個動畫,修改一個 startAnimation() 方法:
    private void startAnimation() {
        //下載箭頭豎線收縮成點的動畫
        ObjectAnimator lineLengthAnimator = ObjectAnimator.ofFloat(this, "lineLength", radius / 2, 0);
        //下載箭頭豎線收縮成點後的動畫1(下載箭頭的箭頭變成橫線)
        ObjectAnimator arrowTurningPointYAnimator1 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", radius / 2, 0);
        //下載箭頭豎線收縮成點後的動畫2(下載箭頭的箭頭抖一下的效果)
        ObjectAnimator arrowTurningPointYAnimator2 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", 0, radius / 4, 0);
        //下載箭頭豎線收縮成點後的在 Y 軸的座標變化的動畫
        ObjectAnimator linePointYAnimator = ObjectAnimator.ofFloat(this, "linePointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 - radius);

        //每個動畫設定不同的時長
        lineLengthAnimator.setDuration(500);
        arrowTurningPointYAnimator1.setDuration(500);
        arrowTurningPointYAnimator2.setDuration(500);
        linePointYAnimator.setDuration(500);

        //新增動畫監聽,實際上就是一直重繪
        lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
        arrowTurningPointYAnimator1.addUpdateListener(mAnimatorUpdateListener);
        arrowTurningPointYAnimator2.addUpdateListener(mAnimatorUpdateListener);
        linePointYAnimator.addUpdateListener(mAnimatorUpdateListener);

        //設定動畫的播放順序
        AnimatorSet mAnimatorSet = new AnimatorSet();
        mAnimatorSet.play(arrowTurningPointYAnimator1).after(lineLengthAnimator);
        mAnimatorSet.play(arrowTurningPointYAnimator2).after(arrowTurningPointYAnimator1);
        mAnimatorSet.play(linePointYAnimator).after(arrowTurningPointYAnimator1);

        mAnimatorSet.start();
    }



5 畫兩個半圓,下載箭頭變成的橫線收縮成一個點

這個動畫也比較簡單,要畫兩段圓弧,圓弧掃過的角度不斷增加,直到形成兩個半圓,即最後形成一個圓環,定義一個 sweepAngle 來控制,還有下載箭頭箭頭部分收縮成點,定義 arrowLength 來控制這個長度: 畫半圓:
        //畫兩個半圓
        mPaint.setColor(Color.rgb(255, 255, 255));
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawArc(mRectF, -90, sweepAngle, false, mPaint);
        canvas.drawArc(mRectF, -90, 0 - sweepAngle, false, mPaint);

箭頭部分:
            mPaint.setColor(Color.rgb(255, 255, 255));
            mPaint.setStyle(Paint.Style.STROKE);
            if (arrowLength == 0) {
                //下載箭頭的箭頭部分收縮成點後應該畫點
                canvas.drawPoint(getMeasuredWidth() / 2, tickTurningPointY, mPaint);
                drawOk = true;
            } else {
                //否則應該畫箭頭
                mPath.reset();
                mPath.moveTo(getMeasuredWidth() / 2 - arrowLength, getMeasuredHeight() / 2);
                mPath.lineTo(getMeasuredWidth() / 2, getMeasuredHeight() / 2 + arrowTurningPointY);
                mPath.lineTo(getMeasuredWidth() / 2 + arrowLength, getMeasuredHeight() / 2);
                canvas.drawPath(mPath, mPaint);
            }

修改 startAnimation() 方法:
    private void startAnimation() {
        //下載箭頭豎線收縮成點的動畫
        ObjectAnimator lineLengthAnimator = ObjectAnimator.ofFloat(this, "lineLength", radius / 2, 0);
        //下載箭頭豎線收縮成點後的動畫1(下載箭頭的箭頭變成橫線)
        ObjectAnimator arrowTurningPointYAnimator1 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", radius / 2, 0);
        //下載箭頭豎線收縮成點後的動畫2(下載箭頭的箭頭抖一下的效果)
        ObjectAnimator arrowTurningPointYAnimator2 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", 0, radius / 4, 0);
        //下載箭頭豎線收縮成點後的在 Y 軸的座標變化的動畫
        ObjectAnimator linePointYAnimator = ObjectAnimator.ofFloat(this, "linePointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 - radius);
        //畫半圓的動畫
        ObjectAnimator sweepAngleAnimator = ObjectAnimator.ofFloat(this, "sweepAngle", 0, 180);
        //下載箭頭的箭頭變成橫線後收縮成點的動畫
        ObjectAnimator arrowLengthAnimator = ObjectAnimator.ofFloat(this, "arrowLength", radius / 2, 0);

        //每個動畫設定不同的時長
        lineLengthAnimator.setDuration(500);
        arrowTurningPointYAnimator1.setDuration(500);
        arrowTurningPointYAnimator2.setDuration(500);
        linePointYAnimator.setDuration(500);
        sweepAngleAnimator.setDuration(3000);
        arrowLengthAnimator.setDuration(3000);

        //新增動畫監聽,實際上就是一直重繪
        lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
        arrowTurningPointYAnimator1.addUpdateListener(mAnimatorUpdateListener);
        arrowTurningPointYAnimator2.addUpdateListener(mAnimatorUpdateListener);
        linePointYAnimator.addUpdateListener(mAnimatorUpdateListener);
        sweepAngleAnimator.addUpdateListener(mAnimatorUpdateListener);
        arrowLengthAnimator.addUpdateListener(mAnimatorUpdateListener);

        //設定動畫的播放順序
        AnimatorSet mAnimatorSet = new AnimatorSet();
        mAnimatorSet.play(arrowTurningPointYAnimator1).after(lineLengthAnimator);
        mAnimatorSet.play(arrowTurningPointYAnimator2).after(arrowTurningPointYAnimator1);
        mAnimatorSet.play(linePointYAnimator).after(arrowTurningPointYAnimator1);
        mAnimatorSet.play(arrowLengthAnimator).after(arrowTurningPointYAnimator2);
        mAnimatorSet.play(sweepAngleAnimator).after(arrowTurningPointYAnimator2);

        mAnimatorSet.start();
    }



6 畫對勾

這裡要說一下,我們在畫對勾的時候就不要畫下載箭頭的那部分了,所以剛才程式碼中有一個 boolean 型別的 drawOk 的標誌位,通過它來判斷是畫對勾還是下載箭頭,在下載箭頭的箭頭部分也收縮成一個點後將它置為 true,即 arrowLength = 0 時,畫對勾我們這裡需要動態計算它的轉折點的位置還有對勾的總長度 看這張圖:

這樣一來,point1 的 x 座標就是圓心的 x 座標減去斜邊1的長度乘以 sin45°,point2 的 x 座標就是圓心的 x 座標,point3 的x 座標是圓心的 x 座標加上斜邊2的長度乘以 sin45°。point2 的 y 座標是我們已知的變化,所以通過 point2 的 y 座標可以算出 point1 和 point3 的 y 座標,point1 的 y 座標等於 point2 的 y 座標減去斜邊1乘以 cos45°,point3 的 y 座標等於 point2 的 y 座標減去斜邊2乘以 cos45°。雖然理論上是這樣,但是實際效果並不好看。
            mPath.moveTo((float) (getMeasuredWidth() / 2 - (Math.sin(45) * tickLength / 3))
                    , (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3)));
            mPath.lineTo(getMeasuredWidth() / 2, tickTurningPointY);
            mPath.lineTo((float) (getMeasuredWidth() / 2 + (Math.sin(45) * tickLength / 3*2))
                    , (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3*2)));



所以自己加點固定的值修改一下,感覺這樣固定了的演算法並不好,等想到好了的再優化一下:
            mPath.moveTo((float) (getMeasuredWidth() / 2 - (Math.sin(45) * tickLength / 3))
                    , (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3)-radius/4));
            mPath.lineTo(getMeasuredWidth() / 2, tickTurningPointY-radius/4);
            mPath.lineTo((float) (getMeasuredWidth() / 2 + (Math.sin(45) * tickLength / 3))
                    , (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3*2)-radius/4));

然後新增動畫,程式碼就不貼了,看最後的整體程式碼吧。最終的整體效果如下:


7 總結

這次這個自定義控制元件的動畫算是比較複雜的了,步驟有點多,也涉及了一點我以前學習屬性動畫疏漏的地方,這次趕緊補上。而且尤其是最後一個步驟的時候,感覺實現得真的不好,果然自己在數學和演算法方面差距有點大。不過這次又更加讓我明白了,就算是再複雜的動畫,只要分解好了,一步一步的實現,都沒有什麼難度。


8 原始碼

/**
 * Description:
 * Created by 禽獸先生
 * Created on 2017/9/4
 */

public class DownloadView1 extends View {
    private Paint mPaint;
    private Path mPath;
    private float radius = 150; //圓半徑
    private float lineLength = radius / 2;  //下載的標誌中間的豎線的長度
    private float arrowTurningPointY = radius / 2;   //下載的標誌下面箭頭的轉折點的 Y 軸座標
    private float linePointY;   //下載的標誌中間的豎線收縮成點之後點的 Y 座標,onMeasured() 方法中初始化
    private RectF mRectF;
    private float sweepAngle = 0;   //兩邊弧線掃過的角度
    private float arrowLength = radius / 2;  //下載的標誌下面箭頭變成水平線後的長度
    private float tickTurningPointY;  //對鉤 Y 座標,onMeasured() 方法中初始化
    private float tickLength = 0;  //對鉤的總長度;
    private boolean drawOk; //是否應該畫最後的對鉤的標誌位

    public DownloadView1(Context context) {
        this(context, null);
    }

    public DownloadView1(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DownloadView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setStrokeWidth(radius / 10);
        mPaint.setStrokeCap(Paint.Cap.ROUND);

        mPath = new Path();

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                startAnimation();
            }
        });
    }

    public void setLineLength(float lineLength) {
        this.lineLength = lineLength;
    }

    public void setArrowTurningPointY(float arrowTurningPointY) {
        this.arrowTurningPointY = arrowTurningPointY;
    }

    public void setLinePointY(float linePointY) {
        this.linePointY = linePointY;
    }

    public void setSweepAngle(float sweepAngle) {
        this.sweepAngle = sweepAngle;
    }

    public void setArrowLength(float arrowLength) {
        this.arrowLength = arrowLength;
    }

    public void setTickTurningPointY(float tickTurningPointY) {
        this.tickTurningPointY = tickTurningPointY;
    }

    public void setTickLength(float tickLength) {
        this.tickLength = tickLength;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setBackgroundColor(Color.rgb(10, 39, 46));
        linePointY = getMeasuredHeight() / 2;
        tickTurningPointY = getMeasuredHeight() / 2;
        mRectF = new RectF(getMeasuredWidth() / 2 - radius
                , getMeasuredHeight() / 2 - radius
                , getMeasuredWidth() / 2 + radius
                , getMeasuredHeight() / 2 + radius);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //畫整個圓
        mPaint.setColor(Color.rgb(10, 39, 46));
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);

        //畫圓環
        mPaint.setColor(Color.rgb(37, 66, 73));
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);

        //畫兩個半圓
        mPaint.setColor(Color.rgb(255, 255, 255));
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawArc(mRectF, -90, sweepAngle, false, mPaint);
        canvas.drawArc(mRectF, -90, 0 - sweepAngle, false, mPaint);

        //根據是否應該畫對勾的標誌位來決定畫什麼
        if (drawOk) {
            //畫對勾
            mPaint.setColor(Color.rgb(98, 178, 117));
            mPaint.setStyle(Paint.Style.STROKE);
            mPath.reset();
            mPath.moveTo((float) (getMeasuredWidth() / 2 - (Math.sin(45) * tickLength / 3))
                    , (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3)-radius/4));
            mPath.lineTo(getMeasuredWidth() / 2, tickTurningPointY-radius/4);
            mPath.lineTo((float) (getMeasuredWidth() / 2 + (Math.sin(45) * tickLength / 3))
                    , (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3*2)-radius/4));
            canvas.drawPath(mPath, mPaint);
        } else {
            //畫下載箭頭
            mPaint.setColor(Color.rgb(255, 255, 255));
            mPaint.setStyle(Paint.Style.STROKE);
            if (lineLength == 0) {
                //下載箭頭的豎線收縮成點後應該畫點
                canvas.drawPoint(getMeasuredWidth() / 2, linePointY, mPaint);
            } else {
                //否則應該畫線
                canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - lineLength
                        , getMeasuredWidth() / 2, getMeasuredHeight() / 2 + lineLength
                        , mPaint);
            }

            mPaint.setColor(Color.rgb(255, 255, 255));
            mPaint.setStyle(Paint.Style.STROKE);
            if (arrowLength == 0) {
                //下載箭頭的箭頭部分收縮成點後應該畫點
                canvas.drawPoint(getMeasuredWidth() / 2, tickTurningPointY, mPaint);
                drawOk = true;
            } else {
                //否則應該畫箭頭
                mPath.reset();
                mPath.moveTo(getMeasuredWidth() / 2 - arrowLength, getMeasuredHeight() / 2);
                mPath.lineTo(getMeasuredWidth() / 2, getMeasuredHeight() / 2 + arrowTurningPointY);
                mPath.lineTo(getMeasuredWidth() / 2 + arrowLength, getMeasuredHeight() / 2);
                canvas.drawPath(mPath, mPaint);
            }
        }
    }

    private void startAnimation() {
        //下載箭頭豎線收縮成點的動畫
        ObjectAnimator lineLengthAnimator = ObjectAnimator.ofFloat(this, "lineLength", radius / 2, 0);
        //下載箭頭豎線收縮成點後的動畫1(下載箭頭的箭頭變成橫線)
        ObjectAnimator arrowTurningPointYAnimator1 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", radius / 2, 0);
        //下載箭頭豎線收縮成點後的動畫2(下載箭頭的箭頭抖一下的效果)
        ObjectAnimator arrowTurningPointYAnimator2 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", 0, radius / 4, 0);
        //下載箭頭豎線收縮成點後的在 Y 軸的座標變化的動畫
        ObjectAnimator linePointYAnimator = ObjectAnimator.ofFloat(this, "linePointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 - radius);
        //畫半圓的動畫
        ObjectAnimator sweepAngleAnimator = ObjectAnimator.ofFloat(this, "sweepAngle", 0, 180);
        //下載箭頭的箭頭變成橫線後收縮成點的動畫
        ObjectAnimator arrowLengthAnimator = ObjectAnimator.ofFloat(this, "arrowLength", radius / 2, 0);
        //對勾的轉折點在 Y 軸座標變化的動畫
        ObjectAnimator tickTurningPointYAnimator = ObjectAnimator.ofFloat(this, "tickTurningPointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 + radius / 2);
        //對勾的總長度的變化動畫
        ObjectAnimator tickLengthAnimator = ObjectAnimator.ofFloat(this, "tickLength", 0, radius);

        //每個動畫設定不同的時長
        lineLengthAnimator.setDuration(500);
        arrowTurningPointYAnimator1.setDuration(500);
        arrowTurningPointYAnimator2.setDuration(500);
        linePointYAnimator.setDuration(500);
        sweepAngleAnimator.setDuration(3000);
        arrowLengthAnimator.setDuration(3000);
        tickTurningPointYAnimator.setDuration(1000);
        tickLengthAnimator.setDuration(1000);

        //新增動畫監聽,實際上就是一直重繪
        lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
        arrowTurningPointYAnimator1.addUpdateListener(mAnimatorUpdateListener);
        arrowTurningPointYAnimator2.addUpdateListener(mAnimatorUpdateListener);
        linePointYAnimator.addUpdateListener(mAnimatorUpdateListener);
        sweepAngleAnimator.addUpdateListener(mAnimatorUpdateListener);
        arrowLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
        tickTurningPointYAnimator.addUpdateListener(mAnimatorUpdateListener);
        tickLengthAnimator.addUpdateListener(mAnimatorUpdateListener);

        //設定動畫的播放順序
        AnimatorSet mAnimatorSet = new AnimatorSet();
        mAnimatorSet.play(arrowTurningPointYAnimator1).after(lineLengthAnimator);
        mAnimatorSet.play(arrowTurningPointYAnimator2).after(arrowTurningPointYAnimator1);
        mAnimatorSet.play(linePointYAnimator).after(arrowTurningPointYAnimator1);
        mAnimatorSet.play(arrowLengthAnimator).after(arrowTurningPointYAnimator2);
        mAnimatorSet.play(sweepAngleAnimator).after(arrowTurningPointYAnimator2);
        mAnimatorSet.play(tickTurningPointYAnimator).after(sweepAngleAnimator);
        mAnimatorSet.play(tickLengthAnimator).after(sweepAngleAnimator);

        //新增動畫集監聽,結束後重置各個變數
        mAnimatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
//                lineLength = radius / 2;
//                arrowTurningPointY = radius / 2;
//                linePointY = getMeasuredHeight() / 2;
//                sweepAngle = 0;
//                arrowLength = radius / 2;
//                tickTurningPointY = getMeasuredHeight() / 2;
//                tickLength = 0;
//                drawOk = false;
//                invalidate();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        mAnimatorSet.start();
    }

    private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            invalidate();
        }
    };
}