進階之自定義View之自繪控件

自定義View按類型來劃分的話,自定義View的實現方式大概可以分爲三種:

  1. 組合控件
  2. 繼承控件
  3. 自繪控件

上一篇我們講過了組合控件,接下來我們來講講自繪控件。
每一個視圖的繪製過程都必須經歷三個最主要的階段,即onMeasure()onLayout()onDraw()

onMeasure():測量的意思,顧名思義就是用於測量視圖的大小的。**measure()**方法接收兩個參數widthMeasureSpec和heightMeasureSpec。

widthMeasureSpec:用於確定視圖的寬度的規格和大小。
heightMeasureSpec:用於確定視圖的寬度和高度的規格和大小。
MeasureSpec的值由specSize和specMode共同組成的,其中specSize記錄的是大小,specMode記錄的是規格。
specMode一共有三種類型  EXACTLY、AT_MOST、UNSPECIFIED

EXACTLY:即子視圖的大小是由specSize的值來決定的,系統默認會按照這個規則來設置子視圖的大小。

AT_MOST:即子視圖最大隻能是specSize中指定的大小,並且不會超過specSize。系統默認會按照這個規則來設置子視圖的大小。

UNSPECIFIED:即開發人員可以將視圖按照自己的意願設置成任意的大小,沒有任何限制。

onLayout():這個方法是用於給視圖進行佈局的,也就是確定視圖的位置。

layout()方法接收四個參數,分別代表着左、上、右、下的座標,當然這個座標是相對於當前視圖的父視圖而言的。

public void layout(int l, int t, int r, int b) {}

onDraw():measure和layout的之後,就進入到draw的過程。

下面我們就來自定義一個自己的View

public class CircularProgressBar extends View {

    /**
     * 動畫時長
     */
    private int mDuration;
    /**
     * 接口
     */
    private OnFinishListener mListener;
    /**
     * 動畫
     */
    private ValueAnimator mAnimator;

    /**
     * 逆向進度條最大值
     */
    private int mCurrentProgress;

    /**
     * 進度條最大值
     */
    private int maxValue = 100;

    /**
     * 當前進度值
     */
    private int mCurrentValue;

    /**
     * 每次掃過的角度,用來設置進度條圓弧所對應的圓心角,alphaAngle=(currentValue/maxValue)*360
     */
    private float mAlphaAngle;

    /**
     * 底部圓弧的顏色,默認 ResUtil.getColor(R.color.colorAccent)
     */
    private int mDefaultColor;

    /**
     * 進度條圓弧塊的顏色, 默認 ResUtil.getColor(R.color.colorWhith)
     */
    private int mRunColor;

    /**
     * 中間文字顏色(默認藍色)
     */
    private int cTextColor = Color.BLUE;

    /**
     * 中間文字的字體大小(默認30dp)
     */
    private int cTextSize;

    /**
     * 圓環的寬度
     */
    private int mCircleWidth;

    /**
     * 畫圓弧的畫筆
     */
    private Paint mCirclePaint;

    /**
     * 畫文字的畫筆
     */
    private Paint mTextPaint;

    /**
     * 進度條方向
     */
    private boolean mDirectionof;

    public void setListener(OnFinishListener mListener) {
        this.mListener = mListener;
    }

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

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

    public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    //初始化
    @SuppressLint("ResourceAsColor")
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {

        TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CirculaProgressBar,
                defStyleAttr, 0);
        // 默認底色白色透明度百分之20
        mDefaultColor = ta.getColor(R.styleable.CirculaProgressBar_circular_defaultColor, R.color.colorGray);
        // 默認進度條顏色爲白色
        mRunColor = ta.getColor(R.styleable.CirculaProgressBar_circular_runColor, R.color.colorWhith);
        // 默認中間文字顏色爲藍色
        cTextColor = ta.getColor(R.styleable.CirculaProgressBar_circular_cTextColor, Color.BLUE);
        // 默認中間文字字體大小爲30dp
        cTextSize = ta.getDimensionPixelSize(R.styleable.CirculaProgressBar_circular_cTextSize, DpDonversionPxUtils.px2dip(30));
        // 默認圓弧寬度爲6dp
        mCircleWidth = ta.getDimensionPixelSize(R.styleable.CirculaProgressBar_circular_circleWidth, DpDonversionPxUtils.px2dip(6));
        // 默認順時針方向
        mDirectionof = ta.getBoolean(R.styleable.CirculaProgressBar_circular_directionof, false);

        ta.recycle();

        //畫圓畫筆
        mCirclePaint = new Paint();
        // 抗鋸齒
        mCirclePaint.setAntiAlias(true);
        // 防抖動
        mCirclePaint.setDither(true);
        //畫筆寬度
        mCirclePaint.setStrokeWidth(mCircleWidth);


        //畫文字畫筆
        mTextPaint = new Paint();
        // 抗鋸齒
        mTextPaint.setAntiAlias(true);
        // 防抖動
        mTextPaint.setDither(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取屏幕寬
        int widthPixels = this.getResources().getDisplayMetrics().widthPixels;
        //獲取屏幕高
        int heightPixels = this.getResources().getDisplayMetrics().heightPixels;

        int rwidth = MeasureSpec.getSize(widthMeasureSpec);
        int rhedight = MeasureSpec.getSize(heightMeasureSpec);

        int minWidth = Math.min(widthPixels, rwidth);
        int minHedight = Math.min(heightPixels, rhedight);

        setMeasuredDimension(Math.min(minWidth, minHedight), Math.min(minWidth, minHedight));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int center = this.getWidth() / 2;
        int radius = center - mCircleWidth / 2;
        // 繪製進度圓弧
        drawCircle(canvas, center, radius);
        drawText(canvas, center);
    }

    /**
     * 繪製進度圓弧
     *
     * @param canvas 畫布對象
     * @param center 圓心的x和y座標
     * @param radius 圓的半徑
     */
    private void drawCircle(Canvas canvas, int center, int radius) {
        if (mCirclePaint == null) {
            return;
        }
        // 清除上一次的shader
        mCirclePaint.setShader(null);
        // 設置底部圓環的顏色,這裏使用第一種顏色
        mCirclePaint.setColor(mDefaultColor);
        // 設置繪製的圓爲空心
        mCirclePaint.setStyle(Paint.Style.STROKE);
        // 畫底部的空心圓
        canvas.drawCircle(center, center, radius, mCirclePaint);
        // 圓的外接正方形
        RectF oval = new RectF(center - radius, center - radius, center + radius, center + radius);


        mCirclePaint.setShadowLayer(10, 10, 10, Color.BLUE);
        // 設置圓弧的顏色
        mCirclePaint.setColor(mRunColor);
        // 把每段圓弧改成圓角的
        mCirclePaint.setStrokeCap(Paint.Cap.ROUND);
        // 計算每次畫圓弧時掃過的角度,這裏計算要注意分母要轉爲float類型,否則alphaAngle永遠爲0
        if (mDirectionof) {
            canvas.drawArc(oval, -90, mCurrentProgress - 360, false, mCirclePaint);
        } else {
            mAlphaAngle = mCurrentValue * 360.0f / maxValue * 1.0f;
            canvas.drawArc(oval, -90, mAlphaAngle, false, mCirclePaint);
        }
    }

    /**
     * 繪製文字
     *
     * @param canvas 畫布對象
     * @param center 圓心的x和y座標
     */
    private void drawText(Canvas canvas, int center) {
        // 計算進度
        int result = ((maxValue - mCurrentValue) * (mDuration / 1000) / maxValue);
        String percent;
        if (maxValue == mCurrentValue) {
            percent = "完成";
            // 設置要繪製的文字大小
            mTextPaint.setTextSize(cTextSize);
        } else {
            percent = result + "";
            // 設置要繪製的文字大小
            mTextPaint.setTextSize(cTextSize + cTextSize / 3);
        }
        // 設置文字居中,文字的x座標要注意
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        // 設置文字顏色
        mTextPaint.setColor(cTextColor);
        // 注意此處一定要重新設置寬度爲0,否則繪製的文字會重疊
        mTextPaint.setStrokeWidth(0);
        // 文字邊框
        Rect bounds = new Rect();
        // 獲得繪製文字的邊界矩形
        mTextPaint.getTextBounds(percent, 0, percent.length(), bounds);
        // 獲取繪製Text時的四條線
        Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
        // 計算文字的基線,方法見http://blog.csdn.net/harvic880925/article/details/50423762
        int baseline = center + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        // 繪製表示進度的文字
        canvas.drawText(percent, center, baseline, mTextPaint);

    }


    /**
     * 設置圓環的寬度
     *
     * @param width
     */
    public void setCircleWidth(int width) {
        this.mCircleWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, getResources()
                .getDisplayMetrics());
        mCirclePaint.setStrokeWidth(mCircleWidth);
        //一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
        invalidate();
    }

    /**
     * 設置圓環的底色,默認爲亮灰色LTGRAY
     *
     * @param color
     */
    public void setDefaultColor(int color) {
        this.mDefaultColor = color;
        mCirclePaint.setColor(mDefaultColor);
        //一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
        invalidate();
    }

    /**
     * 設置進度條的顏色,默認爲白色<br>
     *
     * @param color
     */
    public void setRunColor(int color) {
        this.mRunColor = color;
        mCirclePaint.setColor(mRunColor);
        //一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
        invalidate();
    }

    /**
     * 設置進度條運行方向
     *
     * @param directionof
     */
    public void setRunDirectionof(boolean directionof) {
        this.mDirectionof = directionof;
        //一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
        invalidate();
    }


    /**
     * 按進度顯示百分比,可選擇是否啓用數字動畫
     *
     * @param duration 動畫時長
     */
    public void setDuration(int duration, OnFinishListener listener) {
        this.mListener = listener;
        this.mDuration = duration + 1000;
        if (mAnimator != null) {
            mAnimator.cancel();
        } else {
            mAnimator = ValueAnimator.ofInt(0, maxValue);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentValue = (int) animation.getAnimatedValue();
                    Log.e(" onAnimationUpdate ", " onAnimationUpdate  =   " + mCurrentValue);
                    mCurrentProgress = (int) (360 * (mCurrentValue / 100f));
                    Log.e(" onAnimationUpdate ", " onAnimationUpdate  mCurrentProgress  =   " + mCurrentProgress);
                    //一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
                    invalidate();
                    if (maxValue == mCurrentValue && CircularProgressBar.this.mListener != null) {
                        CircularProgressBar.this.mListener.onFinish();
                    }
                }
            });
            mAnimator.setInterpolator(new LinearInterpolator());
        }
        mAnimator.setDuration(duration);
        mAnimator.start();
    }

    /**
     * 停止動畫
     */
    public void stopAnimator() {
        if (mAnimator != null) {
            mAnimator.cancel();
        }
        mCurrentValue = 0;
        mCurrentProgress = 0;
        invalidate();
    }

}

效果圖
在這裏插入圖片描述

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