0. 效果展示
1. 为什么要自定义控件
- 特定的显示风格(适用于进度条)
- 处理特有的用户交互
- 优化布局
- 封装等
2. 如何自定义控件
- 自定义属性的声明和获取
- 测量onMeasure
- 布局onLayout (ViewGroup)
- 绘制onDraw
- onTouchEvent (处理交互)
- onInterceptTouchEvent(ViewGroup 拦截事件)
- 状态的恢复与保存
3. 自定义进度条所需操作
- 自定义属性的声明和获取
- 测量onMeasure
- 绘制onDraw
- 状态的恢复和保存
4. 自定义横向带进度的进度条
-
4.1 分析所需要的自定义属性(7个)
4.1.1. 进度条左边的颜色和高度
4.1.2. 进度条右边的颜色和高度
4.1.3. 进度条的字体大小和颜色
4.1.4. 字体与进度条之间的距离
4.2 在res/values/attrs.xml中定义声明
- 4.3 在View的方法中进行获取
/**
* Created by FuKaiqiang on 2018-01-21.
*/
public class HorizontalProgressBarWithProgress extends ProgressBar {
//声明自定义属性的变量,提供默认值,方便使用
private static final int DEFAULT_TEXT_SIZE = 10; //sp
private static final int DEFAULT_TEXT_COLOR = 0xFFFC00D1;
private static final int DEFAULT_COLOR_UNREACH = 0xFFD3D6DA;
private static final int DEFAULT_HEIGHT_UNREACH = 2; //dp
private static final int DEFAULT_COLOR_REACH = DEFAULT_TEXT_COLOR;
private static final int DEFAULT_HEIGHT_REACH = 2; //dp
private static final int DEFAULT_TEXT_OFFSET = 10; //dp
//声明变量
private int mTextSize = sp2px(DEFAULT_TEXT_SIZE);
private int mTextColor = DEFAULT_TEXT_COLOR;
private int mUnReachColor = DEFAULT_COLOR_UNREACH;
private int mUnReachHeight = dp2px(DEFAULT_HEIGHT_UNREACH);
private int mReachColor = DEFAULT_COLOR_REACH;
private int mReachHeight = dp2px(DEFAULT_HEIGHT_REACH);
private int mTextOffset = dp2px(DEFAULT_TEXT_OFFSET);
private Paint mPaint = new Paint();
private int mRealWidth; //当前控件的宽度减去它的Padding值,也就是真正的宽度;onMeasure赋值,onDraw使用
//new控件的使用会调用一个参数的构造方法
public HorizontalProgressBarWithProgress(Context context) {
this(context, null);
}
//在布局中使用一般调用两个参数的构造方法并且进行自定义属性的获取
public HorizontalProgressBarWithProgress(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HorizontalProgressBarWithProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
obtainStyledAttrs(attrs);
}
/**
* 获取自定义属性
*
* @param attrs
*/
private void obtainStyledAttrs(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs,
R.styleable.HorizontalProgressBarWithProgress);
mTextSize = (int) typedArray.getDimension(R.styleable.HorizontalProgressBarWithProgress_progress_text_size,
mTextSize);
mTextColor = typedArray.getColor(R.styleable.HorizontalProgressBarWithProgress_progress_text_color,
mTextColor);
mTextOffset = (int) typedArray.getDimension(R.styleable.HorizontalProgressBarWithProgress_progress_text_offset,
mTextOffset);
mUnReachColor = typedArray.getColor(R.styleable.HorizontalProgressBarWithProgress_progress_unreach_color,
mUnReachColor);
mUnReachHeight = (int) typedArray.getDimension(R.styleable.HorizontalProgressBarWithProgress_progress_unreach_height,
mUnReachHeight);
mReachColor = typedArray.getColor(R.styleable.HorizontalProgressBarWithProgress_progress_reach_color,
mReachColor);
mReachHeight = (int) typedArray.getDimension(R.styleable.HorizontalProgressBarWithProgress_progress_reach_height,
mReachHeight);
typedArray.recycle();
mPaint.setTextSize(mTextSize);
}
/**
* 测量
*
* @param widthMeasureSpec 宽度的Spec
* @param heightMeasureSpec 高度的Spec
*/
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//利用MeasureSpec类获取widthMeasureSpec中宽度或高度的模式和值
//根据progressbar的实际情况,用户在使用过程中肯定会给我一个宽度值,所以这里就不需要mode了,注销掉,直接拿值
// int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
//widthMode与三种模式进行比较,做不同的处理
int height = measureHeight(heightMeasureSpec);
//宽和高都有了,setMeasuredDimension的意思是确定了view的宽和高
setMeasuredDimension(widthVal, height);
//赋值(实质上绘制的区域的宽度)
mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
/**
* 绘制三个部分 reachBar和Text和unreachBar
*
* @param canvas
*/
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(getPaddingLeft(), getHeight() / 2);
//定义变量是否需要绘制
boolean noNeedUnRech = false;
//获取文本的宽度
String text = getProgress() + "%";
int textWidth = (int) mPaint.measureText(text);
//整型转换为浮点型(当前的进度/最大的进度)
float radio = getProgress() * 1.0f / getMax();
//进度float值
float progressX = radio * mRealWidth;
if (progressX + textWidth> mRealWidth) {
progressX = mRealWidth - textWidth;
noNeedUnRech = true;
}
float endX = progressX-mTextOffset/2;
if (endX > 0) {
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mReachHeight);
canvas.drawLine(0, 0, endX, 0, mPaint);
}
//draw text
mPaint.setColor(mTextColor);
int y = (int) (-(mPaint.descent() + mPaint.ascent() / 2));
canvas.drawText(text, progressX,y,mPaint);
//draw unreach bar
if (!noNeedUnRech){
//起始坐标
float start = progressX+mTextOffset/2+textWidth;
mPaint.setColor(mUnReachColor);
mPaint.setStrokeWidth(mUnReachHeight);
canvas.drawLine(start,0, mRealWidth,0,mPaint);
}
canvas.restore();
}
/**
* 根据模式不同得到不同的height
* 当EXACTLY时 result = size;当UNSPECIFIED时,result=测量之后的值;当AT_MOST时,取第一种模式和第二种模式下result的最小值
*
* @param heightMeasureSpec
* @return
*/
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
// 精确值模式
if (mode == MeasureSpec.EXACTLY) {
result = size;
}
// AT_MOST和UNSPECIFIED两种模式需要自己去测量(分析:高度最大值是text高度决定的)
else {
//Paint 的高度 UNSPECIFIED模式下让我们自己测量
int textHeight = (int) (mPaint.descent() +mPaint.ascent());
//获取bar的高度 (上边距+下边距+mReachHeight或mUnReachHeight+textHeight的最大值)
result = getPaddingTop() + getPaddingBottom() + Math.max(Math.max(mReachHeight, mUnReachHeight),
Math.abs(textHeight));
//测量值不能超过给的那个size
if (mode == MeasureSpec.AT_MOST) {
//所以这里取最小值
result = Math.min(result, size);
}
}
return result;
}
//dp To px
private int dp2px(int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
}
//sp To px
private int sp2px(int spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics());
}
}
- 4.4 在layout文件中进行使用
5. 自定义圆形带进度的进度条
- 5.1 修改刚才的成员变量为protected,并新建一个类继承原有类
//声明变量
protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);
protected int mTextColor = DEFAULT_TEXT_COLOR;
protected int mUnReachColor = DEFAULT_COLOR_UNREACH;
protected int mUnReachHeight = dp2px(DEFAULT_HEIGHT_UNREACH);
protected int mReachColor = DEFAULT_COLOR_REACH;
protected int mReachHeight = dp2px(DEFAULT_HEIGHT_REACH);
protected int mTextOffset = dp2px(DEFAULT_TEXT_OFFSET);
protected Paint mPaint = new Paint();
protected int mRealWidth; //当前控件的宽度减去它的Padding值,也就是真正的宽度;onMeasure赋值,onDraw使用
//dp To px
protected int dp2px(int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
}
//sp To px
protected int sp2px(int spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics());
}
public class RoundProgressBarWidthProgress extends HorizontalProgressBarWithProgress {
public RoundProgressBarWidthProgress(Context context) {
this(context,null);
}
public RoundProgressBarWidthProgress(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RoundProgressBarWidthProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
- 5.2 自定义属性
- 5.3 获取属性值
/**
* Created by FuKaiqiang on 2018-01-21.
*/
public class RoundProgressBarWidthProgress extends HorizontalProgressBarWithProgress {
private int mRadius = dp2px(30);
private int mMaxPaintWidth; //用来计算宽度
public RoundProgressBarWidthProgress(Context context) {
this(context, null);
}
public RoundProgressBarWidthProgress(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundProgressBarWidthProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mReachHeight = (int) (mUnReachHeight * 2.5f); //为了好看,没有实际意义
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBarWidthProgress);
mRadius = (int) typedArray.getDimension(R.styleable.RoundProgressBarWidthProgress_radius, mRadius);
typedArray.recycle();
mPaint.setStyle(Paint.Style.STROKE); //只绘制图形轮廓
mPaint.setAntiAlias(true); //锯齿
mPaint.setDither(true); //防抖动
//(可设 可不设)设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
/**
* 测量
*
* @param widthMeasureSpec 宽度的Spec
* @param heightMeasureSpec 高度的Spec
*/
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMaxPaintWidth = Math.max(mReachHeight, mUnReachHeight);
//因为是圆形默认四个padding一致;except期望值,如果用户选择wrap的话
int except = mRadius * 2 + mMaxPaintWidth + getPaddingLeft() + getPaddingRight();
int width = resolveSize(except, widthMeasureSpec); //同measureHeight方法
int height = resolveSize(except, heightMeasureSpec);
//最终的宽的大小
int readWidth = Math.min(width, height);
mRadius = (readWidth - getPaddingLeft() - getPaddingRight() - mMaxPaintWidth) / 2;
setMeasuredDimension(readWidth, readWidth);
}
@Override
protected synchronized void onDraw(Canvas canvas) {
String text = getProgress() + "%";
float textWidth = mPaint.measureText(text);
float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;
canvas.save();
canvas.translate(getPaddingLeft() + mMaxPaintWidth / 2, getPaddingTop() + mMaxPaintWidth / 2);
mPaint.setStyle(Paint.Style.STROKE);
//draw unreach bar
mPaint.setColor(mUnReachColor);
mPaint.setStrokeWidth(mUnReachHeight);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
//draw reach bar
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mReachHeight); //设置空心宽度
//弧度
float sweepAngle = getProgress() * 1.0f / getMax() * 360;
canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius * 2),
0, sweepAngle, false, mPaint);
//draw text
mPaint.setColor(mTextColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText(text,mRadius-textWidth/2,mRadius-textHeight,mPaint);
canvas.restore();
}
}
- 5.4 布局等同上
6. 总结
- 如果你只想改变某个控件的样式,自定义控件直接继承自系统原有控件。
- 过程包括:分析所需属性、声明属性值、获取属性、测量、绘制。
- 注意onMeasure的三种模式
7. 下载
- 下载地址:
https://gitee.com/fkq2017/ZiDingYiHengXiangHeYuanXingDaiJinDuJinDuTiao