乐趣区

关于android: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,如有侵权,请分割删除。

退出移动版