Android 自定义进度条
为什么要自定义控件:
- 特定的显示风格
- 处理特有的用户交互
- 优化我们的布局
- 封装
如何自定义控件:
- 自定义属性的声明与获取
- 测量onMeasure
- 布局onLayout(ViewGroup)
- 绘制onDraw
- onTouch
- onInterceptTouchEvent(ViewGroup)
- 状态的恢复与保存
从上面这张图来分析需要自定义哪些属性
- 分析自定义属性:
- 左边进度条的颜色、宽度
- 右边进度条的颜色、宽度
- 中间文字的颜色、字体大小
- 文字与进度条的间隙
- 在 res/values/attrs.xml 定义声明
- 在layout 文件中使用
- 在View 的构造方法中进行获取
水平进度条
- 自定义属性,在values文件夹下建立一个attrs.xml 资源文件,并声明属性
- 建立一个自定义View类,并添加两个参数的构造器
- 在attrs.xml 文件中声明该view使用那些属性
attrs.xml文件内容
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--声明属性-->
<attr name="progress_unreach_color" format="color"></attr>
<attr name="progress_unreach_height" format="dimension"></attr>
<attr name="progress_reach_color" format="color"></attr>
<attr name="progress_reach_height" format="dimension"></attr>
<attr name="progress_text_color" format="color"></attr>
<attr name="progress_text_size" format="dimension"></attr>
<attr name="progress_text_offset" format="dimension"></attr>
<!--在自定义view中使用定义的属性-->
<declare-styleable name="HorizontalProgressbarWithProgress">
<attr name="progress_unreach_color" ></attr>
<attr name="progress_unreach_height" ></attr>
<attr name="progress_reach_color" ></attr>
<attr name="progress_reach_height" ></attr>
<attr name="progress_text_color" ></attr>
<attr name="progress_text_size" ></attr>
<attr name="progress_text_offset" ></attr>
</declare-styleable>
</resources>
自定义view 一步步实现
- 构造器
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);
mTextSize = textSize;
}
- 默认属性值,和上面自定义属性值对应
private static final int DEFAULT_TEXT_SIZE = 10; //sp
private static final int DEFAULT_TEXT_COLOE = 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_COLOE;
private static final int DEFAULT_HEIGHT_REACH = 2; //dp
private static final int DEFAULT_TEXT_OFFSET = 10; //dp
- 两个单位转换工具方法 ——dp2px、sp2px,实际绘制时使用像素来绘制,因此需要把dp、sp等单位转为px
// dp 单位转为px
private int dp2px(int dpval) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpval, getResources().getDisplayMetrics());
}
// sp 转为px
private int sp2px(int spval) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spval, getResources().getDisplayMetrics());
}
- 将默认属性值转为像素值
// 像素单位值
private int mTextSize = sp2px(DEFAULT_TEXT_SIZE);
private int mTextColor = DEFAULT_TEXT_COLOE;
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中使用
- 在构造函数中获取属性,这里我单独写一个方法,在构造器中调用
/**
* 获取自定义属性
* @param attrs
*/
private void obtainStyleAttrs(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs,
R.styleable.HorizontalProgressbarWithProgress);
mTextSize = (int) ta.getDimension(
R.styleable.HorizontalProgressbarWithProgress_progress_text_size,
mTextSize);
mTextColor = ta.getColor(
R.styleable.HorizontalProgressbarWithProgress_progress_text_color,
mTextColor);
mTextOffset = (int) ta.getDimension(
R.styleable.HorizontalProgressbarWithProgress_progress_text_offset,
mTextOffset);
mUnreachColor = ta.getColor( R.styleable.HorizontalProgressbarWithProgress_progress_unreach_color,
mUnreachColor);
mUnreachHeight = (int) ta.getDimension( R.styleable.HorizontalProgressbarWithProgress_progress_unreach_height,
mUnreachHeight);
mReachColor = ta.getColor( R.styleable.HorizontalProgressbarWithProgress_progress_reach_color,
mReachColor);
mReachHeight = (int) ta.getDimension( R.styleable.HorizontalProgressbarWithProgress_progress_reach_height,
mReachHeight);
}
- 尺寸测量
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthval = MeasureSpec.getSize(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
// 设置测量的宽度和高度
setMeasuredDimension(widthval, height);
mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
// 给定确定的值
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
// 自己计算值
int textHeight = (int) (mPaint.descent() - mPaint.ascent());
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;
}
- 实现onDraw方法
@Override
protected synchronized void onDraw(Canvas canvas) {
canvas.save();
// 移动画布
canvas.translate(getPaddingLeft(), getHeight()/2);
boolean noNeedUnrech = false;
String text = getProgress() + "%";
// 进度条进度比例
float radio = getProgress() * 1.0f / getMax();
// 测量文字宽度
int textWidth = (int) mPaint.measureText(text);
// 计算已完成进度条宽度
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);
}
// 绘制文本
mPaint.setColor(mTextColor);
// 让文字基线处于中心
int y = (int) (-(mPaint.descent() + mPaint.ascent())/2);
canvas.drawText(text, progressX, y, mPaint);
if (!noNeedUnrech) {
float start = (int) (progressX + textWidth + mTextOffset/2);
mPaint.setColor(mUnreachColor);
mPaint.setStrokeWidth(mUnreachHeight);
canvas.drawLine(start, 0, mRealWidth, 0, mPaint);
}
canvas.restore();
}
在activity中和其他系统控件一样使用
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <!-- 给自定义属性添加命名空间 --> xmlns:hyman="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.song.progressbar.MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <com.example.view.HorizontalProgressbarWithProgress android:id="@+id/id_prohress01" android:layout_width="match_parent" android:layout_height="wrap_content" android:progress="50" android:padding="5dp" android:layout_marginTop="30dp" hyman:progress_reach_color="#ffff0000" hyman:progress_text_color="#ffff0000" hyman:progress_unreach_color="#44ff0000" /> </ScrollView>
圆形进度条
该实现方法和水平进度条类似,只是绘制不同形状,在这里添加了一个圆形半径的属性,且继承自水平进度条,在测量尺寸时,没有像水平进度条那样对测量模式、尺寸进行详细的测量,而是使用resolveSize(except, widthMeasureSpec);方法。
public class RoundProgressbarWithProgress extends HorizontalProgressbarWithProgress {
private int mRadius ;
private int mRealWidth;
private int mMaxPaintWidth;
public RoundProgressbarWithProgress(Context context) {
this(context, null);
}
public RoundProgressbarWithProgress(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundProgressbarWithProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = getContext().obtainStyledAttributes(attrs,
R.styleable.RoundProgressbarWithProgress);
mRadius = (int) ta.getDimension(R.styleable.RoundProgressbarWithProgress_radius,
20);
ta.recycle();
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取两个画笔的最大值,
mMaxPaintWidth = Math.max(mReachHeight, mUnreachHeight);
// 计算期望的控件大小
int except = mRadius * 2 + mMaxPaintWidth + getPaddingLeft() + getPaddingRight();
// 解析控件实际尺寸
int width = resolveSize(except, widthMeasureSpec);
int height = resolveSize(except, heightMeasureSpec);
mRealWidth = Math.max(width, height);
mRadius = (mRealWidth - getPaddingLeft() - getPaddingRight() - mMaxPaintWidth) / 2;
setMeasuredDimension(mRealWidth, mRealWidth);
}
@Override
protected synchronized void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(getPaddingLeft()+mMaxPaintWidth/2, getPaddingLeft() + mMaxPaintWidth/2);
String text = getProgress() + "%";
int textWidth = (int) mPaint.measureText(text);
int textHeight = (int) ((mPaint.descent() + mPaint.ascent()) / 2);
mPaint.setStyle(Paint.Style.STROKE);
// 绘制未完成进度条的圆
mPaint.setColor(mUnreachColor);
mPaint.setStrokeWidth(mUnreachHeight);
mPaint.setAntiAlias(true);
mPaint.setDither(false);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
// 绘制已完成圆弧
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);
// 绘制文本
mPaint.setColor(mTextColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText(text, mRadius - textWidth/2, mRadius - textHeight/2, mPaint);
canvas.restore();
}
}