0. 效果图
1. 基础
1.0 注意注意点
- 在 Android 原生的API中使用的尺寸都是像素(px);
1.1 为什么要自定义控件
- 实现特定的显示风格.
- 处理特有的用户交互.
- 优化我们的布局.
- 封装等...
1.2 如何自定义控件
- 自定义属性的定义和声明.
- 在
res/values/ attrs.xml
定义声明. - 在layout中使用.
- 在View的构造方法中进行获取.
- 在
- 测量 onMeasure.
- 布局 onLayout(ViewGroup).
- 绘制 onDraw.
- onTouchEvent
- onInterceptTouchEvent (拦截子View事件).
- 状态的回复与保存.
2. 横向/圆形进度条
自定义属性文件 : res/values/attrs.xml
横向进度条 HorizontalProgressWithProgress
package com.wsj.test.custom.progress;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.ProgressBar;
import com.wsj.test.customviewone.R;
/**
* 自定义水平进度条.
* 最后一部分可能会有点问题.
* Created by WSJ on 2017/1/16.
*/
public class HorizontalProgressWithProgress extends ProgressBar {
private static final String TAG = "HorizontalProgressWithProgress";
private final int DEFAULT_TEXT_SIZE = 10 ; // sp
private final int DEFAULT_TEXT_OFFSET = 10 ; // dp
private final int DEFAULT_REACH_HEIGHT = 2 ; // dp
private final int DEFAULT_UNREACH_HEIGHT = 2 ; // dp
private final int DEFAULT_TEXT_COLOR = 0xFFFC00D1 ;
private final int DEFAULT_REACH_COLOR = DEFAULT_TEXT_COLOR ;
private final int DEFAULT_UNREACH_COLOR = 0xFFD3D6DA ;
protected int mTextSize = sp2px(DEFAULT_TEXT_COLOR);
protected int mTextOffset = dp2px(DEFAULT_TEXT_OFFSET); // 文本和Bar之间的距离.
protected int mReachHeight = dp2px(DEFAULT_REACH_HEIGHT);
protected int mUnreachHeight = dp2px(DEFAULT_UNREACH_HEIGHT);
protected int mTextColor = DEFAULT_TEXT_COLOR;
protected int mReachColor = DEFAULT_REACH_COLOR;
protected int mUnreachColor = DEFAULT_UNREACH_COLOR;
// 画笔.
protected Paint mPaint = new Paint();
// 当前控件宽度 - Padding值. 在 onMeasure 中赋值.在onDraw 中使用.
private int mRealWidth;
public HorizontalProgressWithProgress(Context context) {
this(context,null);
}
public HorizontalProgressWithProgress(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public HorizontalProgressWithProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 1. 获取自定义属性.
obtainStyledAttrs(attrs);
// 2. 设置字体大小. , 一定不能少.
mPaint.setTextSize(mTextSize);
}
// 获取自定义属性.
private void obtainStyledAttrs(AttributeSet attrs) {
// 第二个参数就是在 attrs.xml 的 name
// TypeArray 使用完后一定要记得释放 .
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.HorizontalProgressWithProgress);
mTextSize = (int) ta.getDimension(R.styleable.HorizontalProgressWithProgress_progress_text_size,mTextSize);
mTextColor = ta.getColor(R.styleable.HorizontalProgressWithProgress_progress_text_color,mTextColor);
mTextOffset = (int) ta.getDimension(R.styleable.HorizontalProgressWithProgress_progress_text_offset,mTextOffset);
mReachColor = ta.getColor(R.styleable.HorizontalProgressWithProgress_progress_reach_color,mReachColor);
mReachHeight = (int) ta.getDimension(R.styleable.HorizontalProgressWithProgress_progress_reach_height,mReachHeight);
mUnreachColor = ta.getColor(R.styleable.HorizontalProgressWithProgress_progress_unreach_color,mReachColor);
mUnreachHeight = (int) ta.getDimension(R.styleable.HorizontalProgressWithProgress_progress_unreach_height,mReachHeight);
ta.recycle();
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 计算尺寸
final int height = measureHeight(heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width,height);
mRealWidth = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();
}
// 计算高度.
private int measureHeight(int heightMeasureSpec) {
final int size = MeasureSpec.getSize(heightMeasureSpec);
final int mode = MeasureSpec.getMode(heightMeasureSpec);
int result = 0;
if (mode == MeasureSpec.EXACTLY){
// 精确模式 不需要修改.
result = size;
}else {
// 计算Text 高度.
final int textHeight = (int) (mPaint.descent() - mPaint.ascent());
// 记得加上Padding值.
result = getPaddingTop() + getPaddingBottom() + Math.max(Math.max(mReachHeight,mUnreachHeight),textHeight);
if (mode == MeasureSpec.AT_MOST){
result = Math.min(result,size);
}
}
return result;
}
@Override
protected synchronized void onDraw(Canvas canvas) {
canvas.save();
// 一定画笔
canvas.translate(getPaddingLeft(),getHeight() / 2);
boolean noNeedUnreach = false;
// draw reach
// 进度百分比.
final float radio = getProgress() * 1.0f / getMax();
String text = getProgress() + "%";
// 测量文本宽度.
int textWidth = (int) mPaint.measureText(text);
float progressX = radio * mRealWidth;
if ((progressX + textWidth) > mRealWidth){
// 右侧不需要绘制.
progressX = mRealWidth - textWidth;
noNeedUnreach = 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
if (!noNeedUnreach) {
float startX = progressX + mTextOffset / 2 + textWidth;
mPaint.setColor(mUnreachColor);
mPaint.setStrokeWidth(mUnreachHeight);
canvas.drawLine(startX, 0, mRealWidth, 0, mPaint);
canvas.restore();
}
}
// dp --> px
protected int dp2px(final int dpValue){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpValue,getResources().getDisplayMetrics());
}
// sp --> px
protected int sp2px(final int spValue){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,spValue,getResources().getDisplayMetrics());
}
}
圆形进度条 CircleProgressWithProgress
package com.wsj.test.custom.progress;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import com.wsj.test.customviewone.R;
/**
* 圆形进度条.
* Created by WSJ on 2017/1/16.
*/
public class CircleProgressWithProgress extends HorizontalProgressWithProgress {
private static final int DEFAULT_PRORESS_RADIUS = 10; // dp;
// 半径.
private int mRadius = dp2px(DEFAULT_PRORESS_RADIUS);
private int mMaxPaintWidth = 0;
public CircleProgressWithProgress(Context context) {
this(context,null);
}
public CircleProgressWithProgress(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CircleProgressWithProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取自定义属性.
TypedArray ta = getContext().obtainStyledAttributes(attrs,R.styleable.CircleProgressWithProgress);
mRadius = (int) ta.getDimension(R.styleable.CircleProgressWithProgress_progress_radius,mRadius);
ta.recycle();
Log.d("Circle","半径 : " + mRadius);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMaxPaintWidth = Math.max(mReachHeight , mUnreachHeight);
int expectSize = mRadius * 2 + mMaxPaintWidth + getPaddingLeft() + getPaddingRight();
int width = resolveSize(expectSize,widthMeasureSpec);
int height = resolveSize(expectSize,heightMeasureSpec);
int realWidth = Math.min(width,height);
mRadius = (realWidth - getPaddingLeft() - getPaddingRight() - mMaxPaintWidth) / 2;
setMeasuredDimension(realWidth,realWidth);
}
@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
mPaint.setColor(mUnreachColor);
mPaint.setStrokeWidth(mUnreachHeight);
canvas.drawCircle(mRadius,mRadius,mRadius,mPaint);
// draw reach
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mReachHeight);
// 计算角度.
float sweepAngle = getProgress() * 1.0f / getMax() * 360;
final RectF rect = new RectF(0,0,mRadius * 2, mRadius * 2);
canvas.drawArc(rect,0,sweepAngle,false,mPaint);
// draw text
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mTextColor);
canvas.drawText(text,mRadius - textWidth / 2,mRadius - textHeight / 2,mPaint);
canvas.restore();
}
}
使用--布局文件
使用 MainActivity
package com.wsj.test.customviewone;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.wsj.test.custom.progress.CircleProgressWithProgress;
import com.wsj.test.custom.progress.HorizontalProgressWithProgress;
public class MainActivity extends AppCompatActivity {
private HorizontalProgressWithProgress mHorizontalProgressWithProgress;
private CircleProgressWithProgress mCircleProgressWithProgress;
private static final int MSG_UPDATE = 0x01;
private Handler mHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
int progress = mHorizontalProgressWithProgress.getProgress();
mHorizontalProgressWithProgress.setProgress(++progress);
mCircleProgressWithProgress.setProgress(progress);
if (progress >= 100){
mHandler.removeMessages(MSG_UPDATE);
}
mHandler.sendEmptyMessageDelayed(MSG_UPDATE,100);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHorizontalProgressWithProgress = (HorizontalProgressWithProgress) findViewById(R.id.id_progress);
mCircleProgressWithProgress = (CircleProgressWithProgress) findViewById(R.id.id_progress_circle);
mHandler.sendEmptyMessageDelayed(MSG_UPDATE,100);
}
}