自定义View之简单仪表盘进度条
1. 前言
一点一点学习自定义View,按照《Android开发艺术探索》中的说法,自定义View大致可以分为4类: 1. 继承View重写onDraw方法; 2. 继承ViewGroup派生特殊Layout; 3. 继承特定View; 4. 继承特定ViewGroup
看下第一种,制作一个简单的仪表盘进度条。
2. 实现思路
- 继承View;
- 自定义属性值:arcColor,bgColor,arc_textColor,arc_textSize,分别是前景色,背景色,进度文字颜色,进度文字字体大小;
- 确定弧形绘制位置和文字绘制位置
- 实现onDraw()方法
3. 效果
4. 知识点
4.1 MeasureSpec
MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode指的是测量模式,SpecSize指的是对应测量模式下的大小。
SpecMode有下面如下3中模式:
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
SpecMode | 描述 |
---|---|
UNSPECIFIED | 父容器不对View有任何限制,系统涉及,平时很少涉及 |
EXACTLY | 父容器已经检测出View所需要的精准大小,即SpecSize的大小。对应match_parent和固定数值两种情况 |
AT_MOST | 父容器指定一个View可用的大小。View的大小不能超过这个值,具体是什么值要看View的具体实现。对应wrap_content情况 |
SpecSize大小单位px。
生成MeasureSpec方式如下:
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
获取size和mode的方式
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
4.2 View的工作流程
过程 | 描述 |
---|---|
onMeasure | 测量过程,确定View的宽度和高度,传入的参数是widthMeasureSpec,heightMeasureSpec |
onLayout | 布局过程,确定View的上下左右四个角的位置 |
onDraw | 绘制过程,一般用来绘制特殊图形 |
继承View的自定义View基本只需要关注onDraw()和onMeasure()。由于没有子View所以不需要关注onLayout()。
4.3 图形绘制
具体请查看Canvas类和Paint类,这两个类内容比较多,可以自己研究一下,有很多有用的东西。
4.3.1 Canvas绘制方法
4.3.2 Paint属性值
Paint枚举值
设置属性方法
5. 关键代码
5.1 attrs.xml
<declare-styleable name="ArcView">
<attr name="arcColor" format="color"/>
<attr name="bgColor" format="color"/>
<attr name="arc_textColor" format="color"/>
<attr name="arc_textSize" format="dimension"/>
</declare-styleable>
5.2 ArcView
public class ArcView extends View {
private final int MAX_SWEEP_ANGLE = 240;
private final int START_SWEEP_ANGLE = 150;
private final int DEFAULT_MAX_PROGRESS = 100;
private final int DEFAULT_ARC_COLOR = Color.RED;
private final int DEFAULT_BG_COLOR = Color.DKGRAY;
private final int DEFAULT_TEXT_COLOR = Color.BLACK;
private final int DEFAULT_TEXT_SIZE = 40;
private int mArcColor = DEFAULT_ARC_COLOR;
private int mBgColor = DEFAULT_BG_COLOR;
private int mTextColor = DEFAULT_TEXT_COLOR;
private int mTextSize = DEFAULT_TEXT_SIZE;
private int progress = 0;
private int mMaxProgress = DEFAULT_MAX_PROGRESS;
private Paint mCirclePaint;
private Paint mBgPaint;
private Paint mTextPaint;
private final Rect mTextBound = new Rect();
public ArcView(Context context) {
this(context, null);
}
public ArcView(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray attributes = getContext().obtainStyledAttributes(attrs,
R.styleable.ArcView);
mArcColor = attributes
.getColor(
R.styleable.ArcView_arcColor,
DEFAULT_ARC_COLOR);
mBgColor = attributes
.getColor(
R.styleable.ArcView_bgColor,
DEFAULT_BG_COLOR);
mTextColor = attributes
.getColor(
R.styleable.ArcView_arc_textColor,
DEFAULT_TEXT_COLOR);
mTextSize = (int) attributes
.getDimension(
R.styleable.ArcView_arc_textSize,
DEFAULT_TEXT_SIZE);
attributes.recycle();
init();
}
private void init() {
mCirclePaint = new Paint();
mCirclePaint.setColor(mArcColor);
mCirclePaint.setStrokeWidth(8.0F);
mCirclePaint.setDither(true);
mCirclePaint.setAntiAlias(true);
mCirclePaint.setStyle(Paint.Style.STROKE);
mBgPaint = new Paint();
mBgPaint.setColor(mBgColor);
mBgPaint.setStrokeWidth(20.0F);
mBgPaint.setAntiAlias(true);
mBgPaint.setStyle(Paint.Style.STROKE);
mBgPaint.setStrokeCap(Paint.Cap.ROUND);
mTextPaint = new Paint();
mTextPaint.setStrokeWidth(4);
//字体SP单位转换成PX
int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
mTextSize, getResources().getDisplayMetrics());
mTextPaint.setTextSize(size);
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextAlign(Paint.Align.LEFT);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int circleWidth = getWidth() - paddingLeft - paddingRight;
int circleHeight = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(circleWidth, circleHeight) / 2;
int left = getLeft() + paddingLeft;
int right = left + radius * 2;
int top = getTop() + getPaddingTop();
int bottom = top + 2 * radius;
canvas.drawArc(left, top, right, bottom, START_SWEEP_ANGLE, MAX_SWEEP_ANGLE, false,
mBgPaint);
int sweepArc = MAX_SWEEP_ANGLE * progress / mMaxProgress;
canvas.drawArc(left, top, right, bottom, START_SWEEP_ANGLE, sweepArc, false, mCirclePaint);
String text = String.valueOf(progress) + "%";
mTextPaint.getTextBounds(text, 0, text.length(),
mTextBound);
canvas.drawText(text, (left + right) / 2 - mTextBound.width() / 2,
(top + bottom) / 2 + mTextBound.height() / 2, mTextPaint);
}
/**
* 设置进度大小
*/
public void setProgress(int progress) {
if (progress < 0 || progress > mMaxProgress) {
return;
}
this.progress = progress;
invalidate();
}
/**
* 设置最大进度值
*/
public void setMaxProgress(int maxProgress) {
this.mMaxProgress = maxProgress;
}
}
6. 源码
源码已上传Github,后续自定义View的学习Demo也会陆续上传
转载自:https://juejin.cn/post/6844903503580626958