Android custom view process

The main purpose of Android custom view process is to summarize the ideas during the implementation process and some things that need attention. First, let’s take a look at a rendering:
Insert image description here

Implement logic

  • Re-specify View width and height
  • Drawing background and progress of outer circle arc
  • The background and progress of drawing a medium arc
  • Drawing inner circle arc background and progress

Knowledge points

onMeasure

  • Used to measure the size of View. The View does not need to be measured when it is created. It only needs to be measured when the View is placed in a container (parent control), and the measurement method is called by the parent control. When the parent control of the control wants to place the control, the parent control will call the onMeasure method of the child control to determine the space required by the child control, and then pass in widthMeasureSpec and heightMeasureSpec to tell the child control the available space size, and the child control uses these two You can measure its own width and height with just one parameter.

setMeasuredDimension

  • Used to reset View width and height

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

  • Draw an arc bounded by oval

onDraw

  • Used to determine what the View looks like. The onDraw drawing process is as follows:
    1. Draw the background
    2. If necessary, save the canvas' layers to prepare for fading (if necessary, save this layer to prepare for the sliding effect of the edge)
    3. Draw view's content
    4. Draw children (draw child View)
    5. If necessary, draw the fading edges and restore layers (if necessary, draw edge effects and save the layers)
    6. Draw decorations (scrollbars for instance) (draw borders, such as scrollbars, TextView)

main code

@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);
}

all codes

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, this article ends here~

Finally, I would like to share with you a set of "Advanced Materials on the Eight Modules of Android" written by Alibaba's senior architects to help you systematically organize messy, scattered, and fragmented knowledge, so that you can master Android development systematically and efficiently. Various knowledge points.

Due to the large content of the article and the limited space, the information has been organized into PDF documents. If you need the complete document of "Advanced Materials on Eight Modules of Android", you can add WeChat to get it for free!

"Android Eight Modules Advanced Notes"

Insert image description here

Compared with the fragmented content we usually read, the knowledge points in this note are more systematic, easier to understand and remember, and are strictly arranged according to the knowledge system.

1. Source code analysis collection

Insert image description here

2. Collection of open source frameworks

Insert image description here

At the same time, a WeChat group chat robot based on chatGPT has been built here to answer difficult technical questions for everyone 24 hours a day .

picture

Guess you like

Origin blog.csdn.net/huahaiyi/article/details/132581484