自定义View:
1.自定义属性声明与获取
2.onMeasure
3.onDraw
4.状态的存储与恢复
attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundProgressBar">
<attr name="color" format="color"></attr>
<attr name="line_width" format="dimension"></attr>
<attr name="radius" format="dimension"></attr>
<attr name="android:progress"></attr>
<attr name="android:textSize"></attr>
</declare-styleable>
</resources>
RoundProgressBar.java
public class RoundProgressBar extends View {
int mRadius;
int mColor;
int mLineWidth;
int mTextSize;
int mProgress;
private Paint mPaint;
private RectF mRectF;
//在使用xml +inflate的方法添加控件时会调用,里面多了一个AttributeSet类型的值
//这个方法一般来说用的最多(因为使用到了布局文件)
public RoundProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
mRadius = (int) typedArray.getDimension(R.styleable.RoundProgressBar_radius, dp2px(30));
mColor = typedArray.getColor(R.styleable.RoundProgressBar_color, 0xffff0000);
mLineWidth = (int) typedArray.getDimension(R.styleable.RoundProgressBar_line_width, dp2px(3));
mTextSize = (int) typedArray.getDimension(R.styleable.RoundProgressBar_android_textSize, dp2px(16));
mProgress = typedArray.getInt(R.styleable.RoundProgressBar_android_progress, 30);
typedArray.recycle();//进行回收
initPaint();
}
private float dp2px(int i) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, i, getResources().getDisplayMetrics());
}
private void initPaint() {
mPaint = new Paint();//画笔
mPaint.setColor(mColor);
mPaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
if (widthMode == MeasureSpec.EXACTLY) {
//如果用户设置的是确定的值,就直接使用
width = widthSize;
} else {
//用户没有给确定的值,你就要自己去测量
int needWidth = measureWidth() + getPaddingLeft() + getPaddingRight();
//但是如果是AT_MOST,那你自己测量的值是不能超过它的最大值的
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(needWidth, widthSize);
} else {
//否则就是你测量有多大,你就能有多大
width = needWidth;
}
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
if (heightMode == MeasureSpec.EXACTLY) {
//如果用户设置的是确定的值,就直接使用
height = heightSize;
} else {
//用户没有给确定的值,你就要自己去测量
int needHeight = measureHeight() + getPaddingTop() + getPaddingBottom();
//但是如果是AT_MOST,那你自己测量的值是不能超过它的最大值的
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(needHeight, heightSize);
} else {
//否则就是你测量有多大,你就能有多大
height = needHeight;
}
}
width = Math.min(width, height);
height = Math.min(width, height);
setMeasuredDimension(width, height);
}
private int measureHeight() {
return mRadius * 2;
}
private int measureWidth() {
//根据一个具体的实例来完成,如果是文字,就是文字宽度,如果是圆,就是圆的半径
return mRadius * 2;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF = new RectF(0, 0,
w - getPaddingLeft() - getPaddingRight(),
h - getPaddingTop() - getPaddingBottom());
}
@Override
protected void onDraw(Canvas canvas) {
//绘制小圆
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mLineWidth * 1.0f / 4);
int width = getWidth();
int height = getHeight();
canvas.drawCircle(width / 2, height / 2,
width / 2 - getPaddingLeft() / 2 - getPaddingRight() / 2 - mPaint.getStrokeWidth() / 2,
mPaint);
//绘制圆弧
mPaint.setStrokeWidth(mLineWidth);
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());//把绘制的坐标原点移动到圆的外接矩形的左上角
float angle = mProgress * 1.0f / 100 * 360;
canvas.drawArc(mRectF,
0, angle, false, mPaint);
canvas.restore();
//绘制文本
mPaint.setStyle(Paint.Style.FILL);
String text = mProgress + "%";
mPaint.setStrokeWidth(0);//把宽度清掉
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(mTextSize);
Rect bound = new Rect();
mPaint.getTextBounds(text, 0, text.length(), bound);
int y = getHeight() / 2;
int textHeight = bound.height();
canvas.drawText(text, 0, text.length(), getWidth() / 2,
y + textHeight / 2, mPaint);
}
public void setProgress(int progress) {
mProgress = progress;
invalidate();//重新绘制
}
public int getProgress() {
return mProgress;
}
private static final String INSTANCE = "instance";
private static final String KEY_PROGRESS = "key_progress";
@Nullable
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putInt(KEY_PROGRESS, mProgress);//这是你要存的
bundle.putParcelable(INSTANCE, super.onSaveInstanceState());//这是父view已经存的东西
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
Parcelable parcelable = bundle.getParcelable(INSTANCE);//取出父控件的东西
super.onRestoreInstanceState(parcelable);
mProgress = bundle.getInt(KEY_PROGRESS);//再拿我们自己的东西
return;
}
super.onRestoreInstanceState(state);
}
}
mainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View v = findViewById(R.id.id_pb);
//通过属性动画进行动态化
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator.ofInt(v, "progress", 0, 100).setDuration(3000).start();
}
});
}
}
activity_main.xml
<RelativeLayout 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">
<com.tcy.customviewdemo.RoundProgressBar
android:id="@+id/id_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="10dp"
android:progress="0"
android:textSize="20sp"
hyman:color="#fb7299"
hyman:radius="60dp" />
</RelativeLayout>