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 绘制过程如下:
- Draw the background(绘制背景)
- If necessary, save the canvas’layers to prepare for fading(如果须要,为保留这层为边缘的滑动成果作筹备)
- Draw view’s content(绘制内容)
- Draw children(绘制子 View)
- If necessary, draw the fading edges and restore layers(如果须要,绘制边缘成果并且保留图层)
- 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,如有侵权,请分割删除。