自定義View:重繪進度條
最近下大工夫功課自定義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的優點。進度條還可以用兩個控制元件疊加來繪製,這樣需要繼承幀佈局,不過這樣擴充套件起來要更方便一些。留待後一步研究。