Android高級進階——自定義View實踐篇(二)好看的打鉤小動畫

效果圖如下:

因爲GIF上傳有限制而且有點小卡,錄製的效果不是特別的好,但是也可以瞭解要顯示的效果是怎樣的。

實現思路

這個 view 大致分爲 4 個過程,分段來,後面會給出完整代碼
- 1、點擊之前顯示爲:
image.png

想要實現這個效果非常簡單,只需要調用 canvas 的 drawAcdrawArc 方法即可實現,難點在於 對勾 的繪製,我這邊是這樣計算的:

        //初始化鉤子三個點的座標
        index = new float[6];
        index[0] = mCircleRadiu * 2 / 3;       //第一個點 x 大約在 1/3 圓直徑的位置
        index[1] = mCircleRadiu;               //第一個點 y 大約在 1/2 圓直徑的位置
        index[2] = mCircleRadiu;               //第二個點 x 大約在 1/2 圓直徑的位置
        index[3] = (float) (1.3 * mCircleRadiu); //第二個點 y 大約在圓直徑的 1/2 偏下一點位置
        index[4] = mCircleRadiu * 2 / 3 * 2;    //第三個點 x 大約在 2/3 圓直徑的位置
        index[5] = (float) (0.7 * mCircleRadiu); //第三個點 y 大約在圓直徑 1/2 偏上一點的位置
  • 2、點擊 View 之後,是這樣顯示的
    Jietu20180501-175022-HD.gif

這個繪製的過程也非常簡單,只需要使用動畫控制一下就可以

        ValueAnimator animator = ValueAnimator.ofInt(0, 360);
        animator.setDuration(3000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sweepAngle = (int) animation.getAnimatedValue();
                invalidate();
            }
        });


OnDraw()方法代碼
  //動態繪製選中狀態 時圓環
                canvas.drawArc(new RectF(mStrokeWidth, mStrokeWidth, mCircleRadiu * 2, mCircleRadiu * 2), 0, sweepAngle, false, mCirclePaint);
  • 3、這個效果是在 上面 那個效果完成之後開始繪製的

Jietu20180501-175842-HD.gif

代碼也是非常簡單,貼一下吧,

        ValueAnimator animator = ValueAnimator.ofInt(0, 360);
        animator.setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sweepAngle = (int) animation.getAnimatedValue();
                invalidate();
            }
        });


        final ValueAnimator animator1 = ValueAnimator.ofFloat(mCircleRadiu, 0);
        animator1.setDuration(1000);
        animator1.setInterpolator(new LinearInterpolator());
        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radiu = (float) animation.getAnimatedValue();
                invalidate();
            }

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                drawArcEnd = true;
                resetPaint();
                invalidate();
                animator1.start();
            }
        });

改繪製是在上一個動畫結束之後繪製的,所以需要監聽上一個動畫的完成狀態

  • 4、最後一個狀態,是一個放大縮小的過程

Jietu20180501-180457-HD.gif

代碼也是非常的簡單,也是通過動畫進行控制的,來看下代碼,這個繪製也是在上一個繪製完成之後纔開始的

        final ValueAnimator animator1 = ValueAnimator.ofFloat(mCircleRadiu, 0);
        animator1.setDuration(1000);
        animator1.setInterpolator(new LinearInterpolator());
        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radiu = (float) animation.getAnimatedValue();
                invalidate();
            }
        });

        final ValueAnimator animator2 = ValueAnimator.ofFloat(mCircleRadiu, (float) (mCircleRadiu * 1.5), mCircleRadiu);
        animator2.setDuration(500);
        animator2.setInterpolator(new LinearInterpolator());
        animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radiu1 = (float) animation.getAnimatedValue();
                invalidate();
            }
        });


        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                drawCircleEnd = true;
                animator2.start();

            }
        //繪製底部圓形黃色背景
        canvas.drawCircle(mCircleRadiu, mCircleRadiu, radiu1, mCirclePaint);
        canvas.drawPath(path, mHookPaint);

這就是這個效果的分解動畫,這個效果非常簡單,這裏就不在進行詳細介紹了,有了思路分分鐘搞定,下面給出完整代碼,當然這個代碼並沒有一些小細節並沒有處理的特別的好,裏面槽點還是挺多的,有興趣的可以自己完善下

package com.summary.hecom.custom.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

import com.summary.hecom.R;

/**
 * Created by hecom on 2018/5/1.
 */

public class TickView extends View implements View.OnClickListener {

    private final Context mContext;
    private Paint paint;
    private Path path;
    private float mCircleRadiu;
    private float mStrokeWidth;
    private TypedArray typedArray;
    private int mSelectCircleColor;
    private int mSelectedCircleColor;
    private int mSelectHookColor;
    private int mSelectedHookColor;
    private Paint mCirclePaint;
    private Paint mHookPaint;
    private boolean isCheck;
    private float mCircleStartX;
    private float mCircleStartY;
    private float[] index;
    private int sweepAngle;
    private boolean isDrawArc;
    private float radiu;
    private boolean drawArcEnd;
    private boolean drawCircleEnd;
    private float radiu1;

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

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

    public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        initAttr(attrs);
        init();
        resetPaint();
    }

    private void initAttr(AttributeSet attrs) {
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.TickView);
        //圓半徑
        mCircleRadiu = typedArray.getDimension(R.styleable.TickView_circleRadiu, 150);
        //畫筆寬度(弧寬度)
        mStrokeWidth = typedArray.getDimension(R.styleable.TickView_circle_width, 0);
        if (mStrokeWidth == 0) {
            mStrokeWidth = 10;
        }
        //未點擊時,圓弧顏色
        mSelectCircleColor = typedArray.getColor(R.styleable.TickView_nocheck_circle_color, getResources().getColor(R.color.lightgray));
        //點擊 View 後 顏色
        mSelectedCircleColor = typedArray.getColor(R.styleable.TickView_check_circle_color, getResources().getColor(R.color.yellow));
        //未點擊 View 時,鉤子的顏色
        mSelectHookColor = typedArray.getColor(R.styleable.TickView_nochecck_hook_color, getResources().getColor(R.color.lightgray));
        //點擊後鉤子的顏色
        mSelectedHookColor = typedArray.getColor(R.styleable.TickView_check_hook_color, getResources().getColor(R.color.white));
    }


    private void init() {

        //圓弧(圓)畫筆
        mCirclePaint = new Paint();
        //鉤子畫筆
        mHookPaint = new Paint();
        //一個逐漸縮小的圓
        paint = new Paint();

        //鉤子路徑
        path = new Path();

        //初始化鉤子三個點的座標
        index = new float[6];
        index[0] = mCircleRadiu * 2 / 3;       //第一個點 x 大約在 1/3 圓直徑的位置
        index[1] = mCircleRadiu;               //第一個點 y 大約在 1/2 圓直徑的位置
        index[2] = mCircleRadiu;               //第二個點 x 大約在 1/2 圓直徑的位置
        index[3] = (float) (1.3 * mCircleRadiu); //第二個點 y 大約在圓直徑的 1/2 偏下一點位置
        index[4] = mCircleRadiu * 2 / 3 * 2;    //第三個點 x 大約在 2/3 圓直徑的位置
        index[5] = (float) (0.7 * mCircleRadiu); //第三個點 y 大約在圓直徑 1/2 偏上一點的位置


        setOnClickListener(this);
    }

    //每次狀態改變後重置畫筆
    public void resetPaint() {
        mCirclePaint.reset();
        mHookPaint.reset();
        path.reset();
        if (isCheck) {
            if (drawArcEnd) {
                mCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE);
            } else {
                mCirclePaint.setStyle(Paint.Style.STROKE);
                mCirclePaint.setStrokeWidth(mStrokeWidth);
            }

            mCirclePaint.setColor(mSelectedCircleColor);

            mHookPaint.setColor(mSelectedHookColor);
        } else {
            mCirclePaint.setStyle(Paint.Style.STROKE);
            mCirclePaint.setStrokeWidth(mStrokeWidth);
            mCirclePaint.setColor(mSelectCircleColor);

            mHookPaint.setColor(mSelectHookColor);
        }
        mHookPaint.setStyle(Paint.Style.STROKE);
        mHookPaint.setStrokeWidth(mStrokeWidth);

        mCirclePaint.setAntiAlias(true);
        mHookPaint.setAntiAlias(true);

        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(getResources().getColor(R.color.white));
        paint.setAntiAlias(true);

        path.moveTo(index[0], index[1]);
        path.lineTo(index[2], index[3]);
        path.lineTo(index[4], index[5]);

    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //View 被點擊
        if (isCheck) {
            //圓弧動態繪製結束(在這裏是黃色的圓環)
            if (drawArcEnd) {
                //繪製底部圓形黃色背景
                canvas.drawCircle(mCircleRadiu, mCircleRadiu, mCircleRadiu, mCirclePaint);
                //繪製上面白色的圓(是一個越來越小,最終會消失的一個白色的圓)
                canvas.drawCircle(mCircleRadiu, mCircleRadiu, radiu, paint);
                if (drawCircleEnd) {
                    //繪製底部圓形黃色背景(一個抖動效果,先變大,在變回原來大小)
                    canvas.drawCircle(mCircleRadiu, mCircleRadiu, radiu1, mCirclePaint);
                    //繪製鉤子
                    canvas.drawPath(path, mHookPaint);
                }
            } else {
                //動態繪製選中狀態 時圓環
                canvas.drawArc(new RectF(mStrokeWidth, mStrokeWidth, mCircleRadiu * 2, mCircleRadiu * 2), 0, sweepAngle, false, mCirclePaint);
            }
        } else {    //默認狀態(未被點擊時狀態)
            //繪製未選中狀態
            canvas.drawArc(new RectF(mStrokeWidth, mStrokeWidth, mCircleRadiu * 2, mCircleRadiu * 2), 0, 360, false, mCirclePaint);
            //繪製鉤子
            canvas.drawPath(path, mHookPaint);
        }
    }

    public void animation() {
        //圓弧動畫(黃色圓環)
        ValueAnimator animator = ValueAnimator.ofInt(0, 360);
        animator.setDuration(1000);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sweepAngle = (int) animation.getAnimatedValue();
                invalidate();
            }
        });

        //緩慢顯示黃色圓背景的動畫(也是是文章中提到的第三步)
        final ValueAnimator animator1 = ValueAnimator.ofFloat(mCircleRadiu, 0);
        animator1.setDuration(1000);
        animator1.setInterpolator(new LinearInterpolator());
        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radiu = (float) animation.getAnimatedValue();
                invalidate();
            }
        });

        //一個抖動效果(讓圓先變大,然後在變爲原來的大小)
        final ValueAnimator animator2 = ValueAnimator.ofFloat(mCircleRadiu, (float) (mCircleRadiu * 1.5), mCircleRadiu);
        animator2.setDuration(500);
        animator2.setInterpolator(new LinearInterpolator());
        animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radiu1 = (float) animation.getAnimatedValue();
                invalidate();
            }
        });



        //圓弧(圓環)繪製結束後,開始後面的繪製
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                drawArcEnd = true;
                resetPaint();
                invalidate();
                animator1.start();
            }
        });

        //繪製最後一步
        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                drawCircleEnd = true;
                animator2.start();

            }
        });
        animator.start();
    }

    @Override
    public void onClick(View v) {
        isCheck = true;
        resetPaint();
        animation();

    }
}

自定義屬性:

    <declare-styleable name="TickView">
        <attr name="circleRadiu" format="dimension"/>
        <attr name="nocheck_circle_color" format="color" />
        <attr name="check_circle_color" format="color" />
        <attr name="nochecck_hook_color" format="color" />
        <attr name="check_hook_color" format="color" />
        <attr name="circle_width" format="dimension" />
        <attr name="circle_startx" format="dimension" />
        <attr name="circle_starty" format="dimension" />

    </declare-styleable>

完事………

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章