最近下大工夫功课自定义View这一关。我把自定义View划分为八个类别,写完这八个类别,我就基本上弄清楚自定义控件的门道了。以下是我自己划分的八个类别:
1.使用现有控件布局,对子控件进行格式化和监听,纯代码实现;
2.使用现有控件布局,对子控件进行格式化和监听,带布局文件和属性文件;
3.继承View,自己画一个,纯代码;
4.继承View,自己画一个,带属性文件;
5.继承现有控件,纯代码扩展;
6.继承现有控件,带属性文件扩展;
7.继承ViewGroup或者布局,实现对子控件的操作,纯代码;
8.继承ViewGroup或者布局,实现对子控件的操作,带属性文件。
实际上是四大类。只不过为了使用方便,我比较喜欢那种纯代码的自定义控件,方便移植。带着attrs和布局文件很是不方便。虽然说有依赖可以用,但是怎么也没有一个单文件方便。不过,为了方便布局,属性文件和布局文件也是不可少的。所以一类就分为两种实现方式。
昨天我简单实现了第一种,动态添加子控件,简单实现自己的监听事件,还开放了纯代码的设置接口,使用起来也算是比较灵活。今天这个主要是为了实现一个完全自己定义的进度条,没有实现监听,我先是使用纯代码来控制,后来想着布局方便,就把attrs和属性的获取,单位的转换全加上了。基本上有了这个进度条,自定义控件的门道大致就比较清楚了。
以下是代码:
首先把attrs展示出来:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--预设的属性-->
<attr name="rule_color" format="color|integer" />
<attr name="rule_height" format="dimension" />
<attr name="padding" format="dimension" />
<attr name="cursor_color" format="color|integer" />
<attr name="cursor_radius" format="dimension" />
<!--控件属性集合-->
<declare-styleable name="ProgressBarView">
<attr name="rule_color" />
<attr name="rule_height" />
<attr name="padding" />
<attr name="cursor_color" />
<attr name="cursor_radius" />
</declare-styleable>
</resources>
然后就是源代码:
/**
* 自定义progressBar
* Created by Devin Chen on 2016/12/26.
*/
public class ProgressBarView extends View {
public static final int DEFAULT_RULE_COLOR = 0xff444444;//默认标尺颜色
public static final int DEFAULT_RULE_HEIGHT = 1;//默认标尺高度,即线条的粗细
public static final int DEFAULT_PADDING = 10;//默认边距
public static final int DEFAULT_CURSOR_COLOR = 0xffff4444;//默认游标颜色
public static final int DEFAULT_CURSOR_RADIUS = 6;//默认游标半径
private int mWidth;//控件宽
private int mHeight;//控件高
private int mMinWidth = 400;//最小宽度
private int mMinHeight = 40;//最小高度
private Paint mPaint;//画笔
private int progress = 0;//进度值
private int ruleColor = DEFAULT_RULE_COLOR;//标尺颜色
private int ruleHeight = dp2px(DEFAULT_RULE_HEIGHT);//标尺高度,即线条的粗细
private int mPadding = dp2px(DEFAULT_PADDING);//边距
private int cursorColor = DEFAULT_CURSOR_COLOR;//游标颜色
private int cursorRadius = dp2px(DEFAULT_CURSOR_RADIUS);//游标半径
public ProgressBarView(Context context) {
this(context, null);
}
public ProgressBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ProgressBarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
obtainStyledAttrs(attrs);
initialize();
}
/**
* 获取布局的属性
*
* @param attrs
*/
private void obtainStyledAttrs(AttributeSet attrs) {
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
ruleColor = array.getColor(R.styleable.ProgressBarView_rule_color, ruleColor);
ruleHeight = (int) array.getDimension(R.styleable.ProgressBarView_rule_height, ruleHeight);
mPadding = (int) array.getDimension(R.styleable.ProgressBarView_padding, mPadding);
cursorColor = array.getColor(R.styleable.ProgressBarView_cursor_color, cursorColor);
cursorRadius = (int) array.getDimension(R.styleable.ProgressBarView_cursor_radius, cursorRadius);
array.recycle();
}
/**
* 初始化
*/
private void initialize() {
initPaint();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setStrokeWidth(ruleHeight);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getDefaultWidth(widthMeasureSpec);
mHeight = getDefaultHeight(heightMeasureSpec);
setMeasuredDimension(mWidth, mHeight);
}
/**
* 得到默认的控件宽度
*
* @param widthMeasureSpec
* @return
*/
private int getDefaultWidth(int widthMeasureSpec) {
int hMode = MeasureSpec.getMode(widthMeasureSpec);
int hSize = MeasureSpec.getSize(widthMeasureSpec);
//如果用户设定了精确值。则按照精确值取值,否则返回最小宽度
if (hMode == MeasureSpec.EXACTLY) {
//宽度不能小于设定的最小值
if (hSize < mMinWidth) {
hSize = mMinWidth;
}
return hSize;
} else {
return mMinWidth;
}
}
/**
* 得到默认的控件高度
*
* @param heightMeasureSpec
* @return
*/
private int getDefaultHeight(int heightMeasureSpec) {
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
if (hMode == MeasureSpec.EXACTLY) {
if (hSize < mMinHeight) {
hSize = mMinHeight;
}
return hSize;
} else {
return mMinHeight;
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
mPaint.setColor(ruleColor);
mPaint.setStyle(Paint.Style.STROKE);
//绘制标尺
canvas.drawLine(mPadding, getHeight() / 2, mWidth - mPadding, mHeight / 2, mPaint);
mPaint.setColor(cursorColor);
mPaint.setStyle(Paint.Style.FILL);
//绘制游标
canvas.drawCircle(mPadding + (mWidth - mPadding * 2) * (progress / 100.0f), mHeight / 2, cursorRadius, mPaint);
canvas.restore();
}
/**
* 获取进度值
*
* @return
*/
public int getProgress() {
return progress;
}
/**
* 设置进度值
*
* @param progress
*/
public void setProgress(int progress) {
if (progress <= 100) {
this.progress = progress;
invalidate();
}
}
/**
* sp转换成px
*
* @param dpValue
* @return
*/
private int dp2px(int dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
}
/**
* sp转换成px
*
* @param spValue
* @return
*/
private int sp2px(int spValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics());
}
}
布局文件:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_custom_view2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#c7e2e2e2"
android:orientation="vertical"
tools:context="com.devin.customviewdemo.activity.CustomView2Activity">
<Button
android:id="@+id/btn_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始" />
<TextView
android:id="@+id/txt_val"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#da650b"
android:textSize="20sp"
android:layout_height="wrap_content" />
<com.devin.customviewdemo.customview.ProgressBarView
android:id="@+id/progress_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00ffffff"
app:cursor_color="#cf870b"
app:cursor_radius="5dp"
app:padding="10dp"
app:rule_color="#924daae8"
app:rule_height="2dp" />
</LinearLayout>
最后是在程序中使用。为了看到效果,我们需要线程休眠。
/**
* 自定义进度条的使用
*/
public class CustomView2Activity extends AppCompatActivity {
@Bind(R.id.progress_1)
ProgressBarView progress1;
@Bind(R.id.btn_1)
Button btn1;
@Bind(R.id.txt_val)
TextView txtVal;
private int progress = 0;
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
mHandler.sendEmptyMessage(0);
}
};
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (progress >= 100) {
removeCallbacks(mRunnable);
}
progress1.setProgress(progress++);
txtVal.setText(""+progress1.getProgress());
postDelayed(mRunnable, 200);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_view2);
ButterKnife.bind(this);
initView();
}
private void initView() {
}
@OnClick(R.id.btn_1)
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_1:
progress = 0;
mRunnable.run();
break;
default:
break;
}
}
}
运行结果:
类似这样的自定义进度条,多数人会选择直接继承ProgressBar,而我是喜欢纯粹的用View来绘制。两者基本上是一样的,只不过不具备progressBar的优点。进度条还可以用两个控件叠加来绘制,这样需要继承帧布局,不过这样扩展起来要更方便一些。留待后一步研究。