1. 程式人生 > >仿狗東載入效果—支援載入成功和載入失敗動畫效果和顏色自定義的view

仿狗東載入效果—支援載入成功和載入失敗動畫效果和顏色自定義的view

  最近在爬坑自定義View,看到狗東支付時有一個支付成功後的動畫效果。

  遂決定自己也擼一個,加入了自己的一些想法,把實現的思路分享一下。
特點:

  1. 載入的view元素顏色支援自定義
  2. 載入成功和載入失敗會有一個動畫效果

先上效果圖

這裡寫圖片描述

自定義屬性

新建 res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PowerfulLoadingView">
        <!--載入檢視背景的顏色-->
<attr name="bg_color" format="color"/> <!--載入條的顏色--> <attr name="loading_bar_color" format="color"/> <!--鉤和叉的顏色--> <attr name="tick_cross_color" format="color"/> </declare-styleable> </resources>

老規矩,構造方法三連擊。然後進行自定義屬性值的讀取。如果未指定自定義屬性,則使用預設值。
這裡new了兩個畫筆,一個用於畫線和圓弧,一個用於畫實心圓

    private static final int DEFAULT_CONTENT_COLOR = Color.WHITE;
    private static final int DEFAULT_LOADING_BAR_COLOR = Color.rgb(65, 105, 225);
    private static final int DEFAULT_BG_COLOR = Color.argb(55, 0, 0, 0);
    private int mLoadingBarColor = DEFAULT_LOADING_BAR_COLOR;
    private int mLoadingBgColor = DEFAULT_BG_COLOR;
    private
int mTickOrCrossColor = DEFAULT_CONTENT_COLOR; public PowerfulLoadingView(Context context) { super(context); init(context, null); } public PowerfulLoadingView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public PowerfulLoadingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, @Nullable AttributeSet attrs) { mContext = context; //用於畫實心圓 mPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintFill.setStyle(Paint.Style.FILL); //用於畫線和圓弧 mPaintStroke = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintStroke.setStyle(Paint.Style.STROKE); mPaintStroke.setStrokeWidth(STROKE_WIDTH); if (attrs != null) { TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.PowerfulLoadingView); for (int i = 0; i < array.getIndexCount(); i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.PowerfulLoadingView_bg_color: mLoadingBgColor = array.getColor(attr, DEFAULT_BG_COLOR); break; case R.styleable.PowerfulLoadingView_loading_bar_color: mLoadingBarColor = array.getColor(attr, DEFAULT_LOADING_BAR_COLOR); break; case R.styleable.PowerfulLoadingView_tick_cross_color: mTickOrCrossColor = array.getColor(attr, DEFAULT_CONTENT_COLOR); break; } } array.recycle(); } }

載入中效果實現

  看下載入效果,是一條圓弧在圍繞中心轉動。這裡採用 ValueAnimator 來實現。
  定義一個0~360的範圍,表示角度360度變化。在 setDuration 設定的時間內,AnimatedValue 會從0遞增到360,值每變化一次,都會呼叫 onDraw 方法進行重繪。每次進入onDraw方法,都會通過 drawCircle 先繪製一個圓形的背景,然後通過 getAnimatedValue 方法獲取當前的值(表示角度值),然後通過 drawArc 方法繪製圓弧。只需要改變繪製圓弧時的傳入的角度引數,就可以實現旋轉的效果。

    private ValueAnimator mCircleAngleAnimator;

    public void startLoading() {
        clearAllAnimator();

        //初始化載入條動畫,並迴圈播放
        mCircleAngleAnimator = ValueAnimator.ofFloat(0, 360);
        mCircleAngleAnimator.setDuration(ANIMATOR_TIME);
        mCircleAngleAnimator.setRepeatMode(ValueAnimator.RESTART);
        mCircleAngleAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mCircleAngleAnimator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //繪製轉動的載入條
        if (mCircleAngleAnimator != null && mCircleAngleAnimator.isRunning()) {
            drawBackground(canvas, mLoadingBgColor);
            mPaintStroke.setColor(mLoadingBarColor);
            mRectF.set(STROKE_WIDTH * 2, STROKE_WIDTH * 2,
                    getWidth() - STROKE_WIDTH * 2, getHeight() - STROKE_WIDTH * 2);
            canvas.drawArc(mRectF, (float) mCircleAngleAnimator.getAnimatedValue(), 270, false, mPaintStroke);
            invalidate();
        }
        ......
    }

    //繪製背景
    private void drawBackground(Canvas canvas, int color) {
        mPaintFill.setColor(color);
        canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, getWidth() / 2f, mPaintFill);
    }

載入成功和載入失敗效果實現

確定鉤和叉的大小和位置

  繪製載入完成的動畫之前,需要先確定鉤和叉的大小和位置。這一步在 onMeasure 中完成。
  分析一下,可以知道,確定一個鉤的大小和位置,需要確定三個點的座標。確定一個叉的大小和位置,需要確定四個點的座標。所以這裡定義兩個 Point 物件的陣列來儲存這些座標資訊。
  這裡採用view整個畫布的中心點,來做為參考點,來確定這個7個點的座標。具體可以自行調整。

    private Point[] mTickPoint = new Point[3];
    private Point[] mCrossPoint = new Point[4];

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        measureTickPosition(width / 2, height / 2);
        measureCrossPosition(width / 2, height / 2);
    }

    //測量鉤的大小和位置
    private void measureTickPosition(int centerX, int centerY) {
        Point position = new Point();
        position.x = centerX / 2;
        position.y = centerY;
        mTickPoint[0] = position;

        position = new Point();
        position.x = centerX / 10 * 9;
        position.y = centerY + centerY / 3;
        mTickPoint[1] = position;

        position = new Point();
        position.x = centerX + centerX / 2;
        position.y = centerY / 3 * 2;
        mTickPoint[2] = position;
    }

    //測量叉的大小和位置
    private void measureCrossPosition(int centerX, int centerY) {
        Point position = new Point();
        position.x = centerX / 3 * 2;
        position.y = centerY / 3 * 2;
        mCrossPoint[0] = position;

        position = new Point();
        position.x = centerX / 3 * 2;
        position.y = centerY + centerY / 3;
        mCrossPoint[1] = position;

        position = new Point();
        position.x = centerX + centerX / 3;
        position.y = centerY + centerY / 3;
        mCrossPoint[2] = position;

        position = new Point();
        position.x = centerX + centerX / 3;
        position.y = centerY / 3 * 2;
        mCrossPoint[3] = position;
    }

載入成功的效果實現

  向外暴露方法 loadSucceed(),來實現載入成功的入口。載入完成後,先有個以畫布中心為圓心的實心圓不斷縮小的過渡動畫,然後同時播放鉤出現的動畫和放大再回彈的動畫。同樣採用屬性動畫來實現。動畫的實現,跟上面的載入條基本一致,也是利用 ValueAnimator 的值不斷變化,然後不斷重繪,來實現動畫效果。

  1. 繪製向圓心縮的動畫,先繪製一個固定的背景實心圓,然後通過 ValueAnimator 值的變化,在背景上繪製半徑不斷減小的實心圓。
  2. 繪製一個鉤,只需要根據之前儲存在 mTickPoint 陣列中的三個點的座標,通過 drawLine 繪製兩條線即可。然後通過ValueAnimator 值變化,改變繪製的透明度,從全透明到不透明,免得鉤出現的很突兀,有一個過渡效果。
  3. 放大再回彈的效果。通過 ObjectAnimator 的縮放動畫來完成。先放大,再縮小到圓尺寸即可。

  動畫完成後,如果需要進行進一步的邏輯操作,可以傳入一個監聽介面,當動畫完成後,會回撥 onAnimationEnd 方法,在該方法中進行相應操作即可。

    private ValueAnimator mCircleRadiusAnimator;
    private ValueAnimator mTickAnim;
    private AnimatorSet mAnimatorSet = new AnimatorSet();
    private ObjectAnimator mScaleAnimator;

    public void loadSucceed(@Nullable Animator.AnimatorListener listener) {
        clearAllAnimator();

        //初始化向圓心縮小的圓的動畫
        mCircleRadiusAnimator = ValueAnimator.ofFloat(0, getWidth() / 2f);
        mCircleRadiusAnimator.setDuration(ANIMATOR_TIME / 2);

        //初始化打鉤的動畫,並註冊監聽
        mTickAnim = ValueAnimator.ofInt(0, 255);
        mTickAnim.setDuration(ANIMATOR_TIME / 2);
        if (listener != null) {
            mTickAnim.addListener(listener);
        }

        //放大再回彈的動畫
        mScaleAnimator = getScaleAnimator();

        mAnimatorSet.play(mTickAnim).after(mCircleRadiusAnimator).with(mScaleAnimator);
        mAnimatorSet.start();
    }

    //獲取放大再回彈的動畫
    private ObjectAnimator getScaleAnimator() {
        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat(SCALE_X, 1f, 1.2f, 1f);
        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat(SCALE_Y, 1f, 1.2f, 1f);

        return ObjectAnimator
                .ofPropertyValuesHolder(this, scaleX, scaleY)
                .setDuration(ANIMATOR_TIME / 2);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        ......

        //繪製向圓心縮小的圓
        if (mCircleRadiusAnimator != null && mCircleRadiusAnimator.isRunning()) {
            drawBackground(canvas, mLoadingBarColor);
            mPaintFill.setColor(mTickOrCrossColor);
            canvas.drawCircle(getWidth() / 2f, getHeight() / 2f,
                    getWidth() / 2f - (float) mCircleRadiusAnimator.getAnimatedValue(), mPaintFill);
            invalidate();
        }

        //繪製鉤
        if (mTickAnim != null && mTickAnim.isRunning()) {
            drawBackground(canvas, mLoadingBarColor);
            mPaintStroke.setAlpha((int) mTickAnim.getAnimatedValue());
            mPaintStroke.setColor(mTickOrCrossColor);
            mPaintStroke.setStrokeCap(Paint.Cap.ROUND); //畫線時,線頭為圓形
            canvas.drawLine(mTickPoint[0].x, mTickPoint[0].y, mTickPoint[1].x, mTickPoint[1].y, mPaintStroke);
            canvas.drawLine(mTickPoint[1].x, mTickPoint[1].y, mTickPoint[2].x, mTickPoint[2].y, mPaintStroke);
            invalidate();
        }

        ......
    }

載入失敗的效果實現

  向外暴露方法 loadFailed(),來實現載入失敗的入口。大部分的實現和上面的一致。唯一的區別就是繪製叉。繪製叉也很簡單,根據儲存在陣列 mCrossPoint 中的四個點的座標,通過 drawLine 繪製出兩條交叉的線即可完成。

    private ValueAnimator mCircleRadiusAnimator;
    private ValueAnimator mCrossAnim;
    private AnimatorSet mAnimatorSet = new AnimatorSet();
    private ObjectAnimator mScaleAnimator;

    public void loadFailed(@Nullable Animator.AnimatorListener listener) {
        clearAllAnimator();

        //初始化向圓心縮小的圓的動畫
        mCircleRadiusAnimator = ValueAnimator.ofFloat(0, getWidth() / 2f);
        mCircleRadiusAnimator.setDuration(ANIMATOR_TIME / 2);

        //初始化打叉的動畫,並註冊監聽
        mCrossAnim = ValueAnimator.ofInt(0, 255);
        mCrossAnim.setDuration(ANIMATOR_TIME / 2);
        if (listener != null) {
            mCrossAnim.addListener(listener);
        }

        //放大再回彈的動畫
        mScaleAnimator = getScaleAnimator();

        mAnimatorSet.play(mCrossAnim).after(mCircleRadiusAnimator).with(mScaleAnimator);
        mAnimatorSet.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //繪製向圓心縮小的圓
        if (mCircleRadiusAnimator != null && mCircleRadiusAnimator.isRunning()) {
            drawBackground(canvas, mLoadingBarColor);
            mPaintFill.setColor(mTickOrCrossColor);
            canvas.drawCircle(getWidth() / 2f, getHeight() / 2f,
                    getWidth() / 2f - (float) mCircleRadiusAnimator.getAnimatedValue(), mPaintFill);
            invalidate();
        }

        //繪製叉
        if (mCrossAnim != null && mCrossAnim.isRunning()) {
            drawBackground(canvas, mLoadingBarColor);
            mPaintStroke.setAlpha((int) mCrossAnim.getAnimatedValue());
            mPaintStroke.setColor(mTickOrCrossColor);
            canvas.drawLine(mCrossPoint[0].x, mCrossPoint[0].y, mCrossPoint[2].x, mCrossPoint[2].y, mPaintStroke);
            canvas.drawLine(mCrossPoint[1].x, mCrossPoint[1].y, mCrossPoint[3].x, mCrossPoint[3].y, mPaintStroke);
            invalidate();
        }
    }

  囉嗦完畢,歡迎交流指教哦。