简介
Android系统内置的控件有时候无法满足我们的效果,这时候可以自定义View。自定义View可以通过几种方式实现:
- 继承View重写OnDraw方法
- 继承ViewGroup实现特殊的Layout
- 继承特定的View(比如TextView)
- 继承特定的ViewGroup(比如LinearLayout)
本文实现一个通过继承View实现自定义的加载进度条,效果如下图所示:
(gif录的有点卡,真机上不会)
自定义属性
首先新建一个名为ColorProgressBar
的class继承View。为了提供一些可定制属性,在values目录下新建一个attrs.xml文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ColorProgressBar">
<attr name="firstColor" format="color" />
<attr name="secondColor" format="color" />
<attr name="circleWidth" format="dimension" />
</declare-styleable>
</resources>
其中提供了进度条的两种颜色以及圆的宽度几种属性。然后在布局文件中,使用这些属性。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.administrator.test.ColorPregressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="100dp" />
<com.example.administrator.test.ColorPregressBar
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:layout_marginTop="80dp"
app:circleWidth="16dp"
app:firstColor="#f58a47"
app:secondColor="#5be9d8"
/>
</LinearLayout>
上面在布局文件中使用了两个自定义的控件,第二个定制了颜色、宽度。
获取属性
在ColorProgressBar
的构造方法中获取布局文件中的属性,如下所示:
public class ColorPregressBar extends View {
private Paint mPaint;
private RectF mRectF;
//进度
private int mProgress;
//颜色以及宽度
private int mFirstColor;
private int mSecondColor;
private int mCircleWidth;
//是否到下一圈
private boolean mChanged;
//空间的宽度 以及 高度(两个值设为一样)
private int mWidth;
public ColorPregressBar(Context context) {
this(context, null);
}
public ColorPregressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ColorPregressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColorProgressBar);
mFirstColor = ta.getColor(R.styleable.ColorProgressBar_firstColor, Color.RED);
mSecondColor = ta.getColor(R.styleable.ColorProgressBar_secondColor, Color.BLUE);
mCircleWidth = ta.getDimensionPixelSize(R.styleable.ColorProgressBar_circleWidth, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics()));
ta.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mCircleWidth);
mChanged = false;
}
...//省略了其他方法
}
onMeasure
如果布局文件中控件的宽度设为wrap_content
,必须要进行特殊处理,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST) {
mWidth = Math.min(widthSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 160, getResources().getDisplayMetrics()));
setMeasuredDimension(mWidth, mWidth);
}
}
在onMeasure
中,如果MeasurecSpec
的模式是AT_MOST
则把宽高设置为160像素。
onDraw
最关键的代码在于onDraw
方法,这里实现了圆的绘制以及更新:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int center = getWidth() / 2;
int radius = center - mCircleWidth / 2;
mRectF = new RectF(center - radius, center - radius, center + radius, center + radius);
if (!mChanged) {
mPaint.setColor(mSecondColor);
canvas.drawCircle(center, center, radius, mPaint);
mPaint.setColor(mFirstColor);
canvas.drawArc(mRectF, -90, mProgress, false, mPaint);
} else {
mPaint.setColor(mFirstColor);
canvas.drawCircle(center, center, radius, mPaint);
mPaint.setColor(mSecondColor);
canvas.drawArc(mRectF, -90, mProgress, false, mPaint);
}
startProgress();
}
上面的实现方法是先判断当前颜色,画一个圆形,然后改成另一种颜色绘制一段圆弧,代表进度。进度的更新是调用startProgress
方法,代码如下:
private void startProgress() {
if (isShown()) {
postDelayed(new Runnable() {
@Override
public void run() {
Log.d("ly", "调用post");
mProgress += 10;
if (mProgress >= 360) {
mProgress = 0;
mChanged = !mChanged;
}
invalidate();
}
}, 10);
}
}
其实就是在控件显示的情况下增加mProgress
的值,如果进度到头了则重新变为0
,开始下一圈,然后调用invalidate
更新视图。每10
毫秒更新一次,不断循环。这个也可以用线程实现,不过要在onDetachedFromWindow
方法中关闭线程,以免内存泄漏。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。