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开发的各个知识点。
由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要《Android八大模块进阶资料》完整文档的可以加微信 即可免费领取!
PS:(文末还有使用ChatGPT机器人小福利哦!!大家不要错过)
《Android八大模块进阶笔记》
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、源码解析合集
二、开源框架合集
同时这里还搭建了一个基于chatGPT的微信群聊机器人,24小时为大家解答疑难技术问题。