眼看6月到了,由于前段时间域名备案等原因,服务器关闭了差不多一个月,所以没更新文章,索性今天补一篇吧,准备写一个简单的自定义View,就拿进度条做这个需求吧,虽然简单,但是也包含了基本自定义View的几要素,比如自定义属性、重写测量、重写绘制等功能。
需求分析:
- 1.进度通过绘制线条实现。
- 2.进度文字跟随当前进度实时变化,并非一直显示在固定位置。
- 3.控件未给出宽高属性时,我们需要给出默认值,具体以文字大小而定。
- 4.为了满足开发需求,自定义属性要多,能够最大程度的让开发者控制View的某个属性。
效果图:
开始撸码:
新建类继承View,并实现构造方法:
public class ProgressBarView extends View {
public ProgressBarView(Context context) {
this(context, null);
}
public ProgressBarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ProgressBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
}
新建attrs.xml,并编写自定义属性:
<declare-styleable name="ProgressBarView">
<attr name="progress_left_color" format="color" />
<attr name="progress_center_color" format="color" />
<attr name="progress_right_color" format="color" />
<attr name="progress_left_height" format="dimension" />
<attr name="progress_right_height" format="dimension" />
<attr name="progress_text_size" format="dimension" />
<attr name="progress_current_progress" format="integer" />
declare-styleable>
声明变量,并接收自定义属性的值(记得初始化方法在构造中调用):
/**
* 画笔
*/
private Paint mPaint;
/**
* 默认进度值
*/
private int mProgress = 0;
/**
* 进度边距
*/
private int mProgressPadding = dp2px(2);
/**
* 控件高度
*/
private int mHeight = 0;
/**
* 字体大小
*/
float mTextSize = 18f;
/**
* 左边进度条颜色
*/
int mLeftColor = Color.RED;
/**
* 中间文字条颜色
*/
int mCenterColor = Color.RED;
/**
* 右边进度条颜色
*/
int mRightColor = Color.RED;
/**
* 左边进度条高度
*/
int mLeftHeight = dp2px(10);
/**
* 右边进度条高度
*/
int mRightHeight = dp2px(10);
/**
* 初始化
*/
private void init(AttributeSet attrs) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
mLeftColor = ta.getColor(R.styleable.ProgressBarView_progress_left_color, mLeftColor);
mCenterColor = ta.getColor(R.styleable.ProgressBarView_progress_center_color, mCenterColor);
mRightColor = ta.getColor(R.styleable.ProgressBarView_progress_right_color, mRightColor);
mLeftHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_left_height, mLeftHeight);
mRightHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_right_height, mRightHeight);
mTextSize = ta.getDimension(R.styleable.ProgressBarView_progress_text_size, mTextSize);
mProgress = ta.getInt(R.styleable.ProgressBarView_progress_current_progress, mProgress);
ta.recycle();
mPaint.setTextSize(mTextSize);
}
重写 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法测量自身:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(widthVal, height);
}
/**
* 测量高度
*
* @param heightMeasureSpec
* @return
*/
private int measureHeight(int heightMeasureSpec) {
int result;
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(mHeight, textHeight);
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
重写 protected void onDraw(Canvas canvas)并实现自定义视图绘制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mHeight = getMeasuredHeight();
String str = mProgress + "%";
float textWidth = mPaint.measureText(str);
float width = getMeasuredWidth() - textWidth - getPaddingLeft() - getPaddingRight() - mProgressPadding * 2;
float currentProgress = getPaddingLeft() + width * mProgress / 100;
mPaint.setColor(mLeftColor);
mPaint.setStrokeWidth(mLeftHeight);
canvas.drawLine(getPaddingLeft(), mHeight / 2, currentProgress, mHeight / 2, mPaint);
int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
mPaint.setColor(mCenterColor);
canvas.drawText(str, currentProgress + mProgressPadding, mHeight / 2 + y, mPaint);
mPaint.setColor(mRightColor);
mPaint.setStrokeWidth(mRightHeight);
canvas.drawLine(currentProgress + textWidth + mProgressPadding * 2, mHeight / 2, getMeasuredWidth() - getPaddingRight(), mHeight / 2, mPaint);
}
在布局中引用编写的控件,并设置自定义属性:
.peakchao.view.view.ProgressBarView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:progress_center_color="#ff00ff"
app:progress_current_progress="80"
app:progress_left_color="#f00"
app:progress_left_height="1dp"
app:progress_right_color="#0f0"
app:progress_right_height="2dp"
app:progress_text_size="30dp" />
至此控件已经能够正常显示了,但是由于目前只是写个Demo,还有没考虑的东西,比如各个属性的get和set方法等,需要自己根据需求完善。
附上ProgressBarView完整代码:
/**
* Created by Chao 2018/6/1 on 17:49
* description
*/
public class ProgressBarView extends View {
/**
* 画笔
*/
private Paint mPaint;
/**
* 默认进度值
*/
private int mProgress = 0;
/**
* 进度边距
*/
private int mProgressPadding = dp2px(2);
/**
* 控件高度
*/
private int mHeight = 0;
/**
* 字体大小
*/
float mTextSize = 18f;
/**
* 左边进度条颜色
*/
int mLeftColor = Color.RED;
/**
* 中间文字条颜色
*/
int mCenterColor = Color.RED;
/**
* 右边进度条颜色
*/
int mRightColor = Color.RED;
/**
* 左边进度条高度
*/
int mLeftHeight = dp2px(10);
/**
* 右边进度条高度
*/
int mRightHeight = dp2px(10);
public ProgressBarView(Context context) {
this(context, null);
}
public ProgressBarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ProgressBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
/**
* 初始化
*/
private void init(AttributeSet attrs) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
mLeftColor = ta.getColor(R.styleable.ProgressBarView_progress_left_color, mLeftColor);
mCenterColor = ta.getColor(R.styleable.ProgressBarView_progress_center_color, mCenterColor);
mRightColor = ta.getColor(R.styleable.ProgressBarView_progress_right_color, mRightColor);
mLeftHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_left_height, mLeftHeight);
mRightHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_right_height, mRightHeight);
mTextSize = ta.getDimension(R.styleable.ProgressBarView_progress_text_size, mTextSize);
mProgress = ta.getInt(R.styleable.ProgressBarView_progress_current_progress, mProgress);
ta.recycle();
mPaint.setTextSize(mTextSize);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(widthVal, height);
}
/**
* 测量高度
*
* @param heightMeasureSpec
* @return
*/
private int measureHeight(int heightMeasureSpec) {
int result;
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(mHeight, textHeight);
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mHeight = getMeasuredHeight();
String str = mProgress + "%";
float textWidth = mPaint.measureText(str);
float width = getMeasuredWidth() - textWidth - getPaddingLeft() - getPaddingRight() - mProgressPadding * 2;
float currentProgress = getPaddingLeft() + width * mProgress / 100;
mPaint.setColor(mLeftColor);
mPaint.setStrokeWidth(mLeftHeight);
canvas.drawLine(getPaddingLeft(), mHeight / 2, currentProgress, mHeight / 2, mPaint);
int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
mPaint.setColor(mCenterColor);
canvas.drawText(str, currentProgress + mProgressPadding, mHeight / 2 + y, mPaint);
mPaint.setColor(mRightColor);
mPaint.setStrokeWidth(mRightHeight);
canvas.drawLine(currentProgress + textWidth + mProgressPadding * 2, mHeight / 2, getMeasuredWidth() - getPaddingRight(), mHeight / 2, mPaint);
}
private int dp2px(int val) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, val, getResources().getDisplayMetrics());
}
private int sp2px(int val) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, val, getResources().getDisplayMetrics());
}
}