1. 程式人生 > 其它 >Android自定義view流程

Android自定義view流程

Android自定義view流程,主要目的是總結實現過程中的思路以及一些需要注意的地方。
首先,我們先來看一張效果圖:

實現邏輯

  • 重新指定View寬高
  • 繪製外圓圓弧背景及進度
  • 繪製中圓圓弧背景及進度
  • 繪製內圓圓弧背景及進度

知識點

onMeasure

  • 用於測量View的大小。建立時View無需測量,當將這個View放入一個容器(父控制元件)時候才需要測量,而測量方法由父控制元件呼叫。當控制元件的父控制元件要放置該控制元件的時候,父控制元件會呼叫子控制元件的onMeasure方法確定子控制元件需要的空間大小,然後傳入widthMeasureSpec和heightMeasureSpec來告訴子控制元件可獲得的空間大小,子控制元件通過這兩個引數就可以測量自身的寬高了。

setMeasuredDimension

  • 用於重新設定View寬高

Canvas#drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

  • 繪製以oval為邊界的圓弧

onDraw

  • 用來確定View長什麼樣。onDraw繪製過程如下:
    1. Draw the background(繪製背景)
    2. If necessary, save the canvas’ layers to prepare for fading(如果需要,為儲存這層為邊緣的滑動效果作準備)
    3. Draw view’s content(繪製內容)
    4. Draw children(繪製子View)
    5. If necessary, draw the fading edges and restore layers(如果需要,繪製邊緣效果並且儲存圖層)
    6. Draw decorations (scrollbars for instance)(繪製邊框,比如scrollbars,TextView)

主要程式碼

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 根據父控制元件傳遞的widthMeasureSpec和heightMeasureSpec呼叫MeasureSpec.getSize測量自身寬高
    int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
    int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
    int finalWidth = measureWidth;
    int finalHeight = measureHeight;
    // 根據自身寬高重新計算新的寬高,使新的寬高比為2:1
    if (measureWidth >= measureHeight * 2) {
        finalWidth = measureHeight * 2;
    } else {
        finalHeight = measureWidth / 2;
    }
    // 設定View新的寬高
    setMeasuredDimension(finalWidth, finalHeight);
}

/**
 * 繪製圓弧
 * @param canvas
 * @param progress 進度
 * @param color 進度顏色
 * @param radius 圓弧半徑
 */
private void drawArc(Canvas canvas, float progress, int color, float radius){
    // 圓心
    mXCenter = getWidth() / 2;
    mYCenter = getHeight() ;

    mPaint.setColor(mBackgroundArcColor);
    // 構造邊界矩形
    RectF oval = new RectF();
    oval.left = (mXCenter - radius);
    oval.top = (mYCenter - radius);
    oval.right = mXCenter + radius;
    oval.bottom = radius * 2 + (mYCenter - radius);
    //繪製圓弧背景
    canvas.drawArc(oval, -180, 180, false, mPaint);

    //繪製圓弧進度
    float showDegree = progress / 100 * 180;
    mPaint.setColor(color);
    canvas.drawArc(oval, -180, showDegree, false, mPaint);
}

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

    // 初始半徑
    float originalRadius = getWidth() * .5f;
    // 畫筆半寬
    float halfArcStokeWidth = mArcStrokeWidth * .5f;

    // 外圓環半徑=初始半徑-畫筆半寬
    float outSideArcRadius = originalRadius - halfArcStokeWidth;
    drawArc(canvas, mOutsideProgress, mOutsideArcColor, outSideArcRadius);

    // 中圓環半徑=外圓的半徑-圓環偏移值-畫筆半寬
    float middleArcRadius = outSideArcRadius - mArcOffset - halfArcStokeWidth;
    drawArc(canvas, mMiddleProgress, mMiddleArcColor, middleArcRadius);

    // 內圓環半徑=中圓的半徑-圓環偏移值-畫筆半寬
    float insideArcRadius = middleArcRadius - mArcOffset - halfArcStokeWidth;
    drawArc(canvas, mInsideProgress, mInsideArcColor, insideArcRadius);
}

全部程式碼

ThreeArcView.java

public class ThreeArcView extends View {

    //圓弧畫筆
    private Paint mPaint;
    //背景圓環顏色
    private int mBackgroundArcColor;
    //外圓環顏色
    private int mOutsideArcColor;
    //中圓環顏色
    private int mMiddleArcColor;
    //內圓環顏色
    private int mInsideArcColor;
    //外圓展示弧度
    private float mOutsideProgress;
    //中圓展示弧度
    private float mMiddleProgress;
    //內圓展示弧度
    private float mInsideProgress;
    //圓弧寬度
    private float mArcStrokeWidth;
    //圓偏移值
    private float mArcOffset;

    // 圓心x座標
    private int mXCenter;
    // 圓心y座標
    private int mYCenter;

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

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

    public ThreeArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
        initVariable();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThreeArcView, 0, 0);
        mArcStrokeWidth = typeArray.getDimension(R.styleable.ThreeArcView_ts_strokeWidth, dp2px(context, 20));
        // 圓環背景顏色
        mBackgroundArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_bgArcColor, 0xFFFFFFFF);
        // 圓環顏色
        mOutsideArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_outsideBgColor, 0xFFFFFFFF);
        mMiddleArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_middleBgColor, 0xFFFFFFFF);
        mInsideArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_insideBgColor, 0xFFFFFFFF);
        // 圓進度
        mOutsideProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_outsideProgress, 0f);
        mMiddleProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_middleProgress, 0f);
        mInsideProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_insideProgress, 0f);
        // 圓環偏移值
        mArcOffset = typeArray.getDimension(R.styleable.ThreeArcView_ts_radiusOffset, dp2px(context, 20));
        typeArray.recycle();

        // 偏移值不能小於畫筆寬度的一半,否則會發生覆蓋
        if (mArcOffset < mArcStrokeWidth / 2){
            mArcOffset = mArcStrokeWidth / 2;
        }
    }

    private void initVariable() {
        //背景圓弧畫筆設定
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mArcStrokeWidth);
        mPaint.setStrokeCap(Paint.Cap.ROUND);//開啟顯示邊緣為圓形
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 分別獲取期望的寬度和高度,並取其中較小的尺寸作為該控制元件的寬和高
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        //裁剪出一個 (寬:高) = (2:1) 的矩形
        int finalWidth = measureWidth;
        int finalHeight = measureHeight;
        if (measureWidth >= measureHeight * 2) {
            finalWidth = measureHeight * 2;
        } else {
            finalHeight = measureWidth / 2;
        }
        setMeasuredDimension(finalWidth, finalHeight);
    }

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

        // 初始半徑
        float originalRadius = getWidth() * .5f;
        // 畫筆半寬
        float halfArcStokeWidth = mArcStrokeWidth * .5f;

        // 外圓環半徑=初始半徑-畫筆半寬
        float outSideArcRadius = originalRadius - halfArcStokeWidth;
        drawArc(canvas, mOutsideProgress, mOutsideArcColor, outSideArcRadius);

        // 中圓環半徑=外圓的半徑-圓環偏移值-畫筆半寬
        float middleArcRadius = outSideArcRadius - mArcOffset - halfArcStokeWidth;
        drawArc(canvas, mMiddleProgress, mMiddleArcColor, middleArcRadius);

        // 內圓環半徑=中圓的半徑-圓環偏移值-畫筆半寬
        float insideArcRadius = middleArcRadius - mArcOffset - halfArcStokeWidth;
        drawArc(canvas, mInsideProgress, mInsideArcColor, insideArcRadius);
    }

    /**
     * 繪製圓弧
     * @param canvas
     * @param progress 進度
     * @param color 進度顏色
     * @param radius 圓弧半徑
     */
    private void drawArc(Canvas canvas, float progress, int color, float radius){
        // 圓心
        mXCenter = getWidth() / 2;
        mYCenter = getHeight() ;

        mPaint.setColor(mBackgroundArcColor);
        // 構造邊界矩形
        RectF oval = new RectF();
        oval.left = (mXCenter - radius);
        oval.top = (mYCenter - radius);
        oval.right = mXCenter + radius;
        oval.bottom = radius * 2 + (mYCenter - radius);
        //繪製圓弧背景
        canvas.drawArc(oval, -180, 180, false, mPaint);

        //繪製圓弧進度
        float showDegree = progress / 100 * 180;
        mPaint.setColor(color);
        canvas.drawArc(oval, -180, showDegree, false, mPaint);
    }

    private void setOutSideProgress(float progress){
        this.mOutsideProgress = progress;
        postInvalidate();
    }

    private void setMiddleProgress(float progress){
        this.mMiddleProgress = progress;
        postInvalidate();
    }

    private void setInsideProgress(float progress){
        this.mInsideProgress = progress;
        postInvalidate();
    }

    public void setProgress(float outSideProgress, float middleProgress, float insideProgress) {
        mOutsideProgress = outSideProgress;
        mMiddleProgress = middleProgress;
        mInsideProgress = insideProgress;
        postInvalidate();
    }

    public int dp2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    public int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    public int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }
}

styes.xml

<declare-styleable name="ThreeArcView">
    <!-- 畫筆寬度 -->
    <attr name="ts_strokeWidth" format="dimension" />
    <!-- 圓弧背景色 -->
    <attr name="ts_bgArcColor" format="color" />
    <!-- 外圓進度顏色 -->
    <attr name="ts_outsideBgColor" format="color" />
    <!-- 中圓進度顏色 -->
    <attr name="ts_middleBgColor" format="color" />
    <!-- 內圓進度顏色 -->
    <attr name="ts_insideBgColor" format="color" />
    <!-- 外圓進度 -->
    <attr name="ts_outsideProgress" format="float" />
    <!-- 中圓進度 -->
    <attr name="ts_middleProgress" format="float" />
    <!-- 內圓進度 -->
    <attr name="ts_insideProgress" format="float" />
    <!-- 圓偏移值 -->
    <attr name="ts_radiusOffset" format="dimension" />
</declare-styleable>

OK,本文到此結束,若發現問題,歡迎一起留言一起探討,感謝~

相關教程

Android基礎系列教程:
Android基礎課程U-小結_嗶哩嗶哩_bilibili
Android基礎課程UI-佈局_嗶哩嗶哩_bilibili
Android基礎課程UI-控制元件_嗶哩嗶哩_bilibili
Android基礎課程UI-動畫_嗶哩嗶哩_bilibili
Android基礎課程-activity的使用_嗶哩嗶哩_bilibili
Android基礎課程-Fragment使用方法_嗶哩嗶哩_bilibili
Android基礎課程-熱修復/熱更新技術原理_嗶哩嗶哩_bilibili

本文轉自https://juejin.cn/post/6847902216959819790,如有侵權,請聯絡刪除。