很早就聽說自定義View,但是一直沒有自己動手做一個,今天,終於開始。
下面,做一個比較簡單的餅狀進度條,展示自定義View的幾個步驟。
首先,總結下自定義View的步驟:
1、定義自定義View的屬性;
2、在View的構造方法中獲得我們自定義的屬性;
3、重寫onMeasure() ;
4、重寫onDraw()。
其次,我們需要將目標定義清楚:
1,目標是餅狀進度條,那麼,基礎的canvas畫圖是需要了解的。
2,既然是提供一個進度條,那麼,是需要自定義View的用戶來動態設置進度值的。
3,還有用戶可設置屬性,包括背景色、前景色,還有一個是view的大小,可以用直徑來表示。
最後,要展示進度條如何使用,我用了一個定時器,每秒推進一次進度。
下面來具體實現:
1、定義自定義View的屬性
在values下面新建一個attr.xml,現在裏面定義我們的屬性,
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="diameter" format="dimension" />
<attr name="backColor" format="color" />
<attr name="frontColor" format="color" />
<declare-styleable name="PieProgressView">
<attr name="backColor" />
<attr name="frontColor" />
<attr name="diameter" />
</declare-styleable>
</resources>
2、在View的構造方法中獲得我們自定義的屬性
public PieProgressView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
/**
* 獲得我們所定義的自定義樣式屬性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PieProgressView, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.PieProgressView_backColor:
// 默認背景顏色設置爲黑色
mBackColor = a.getColor(attr, Color.BLACK);
Log.i("log","mBackColor="+Integer.toHexString(mBackColor));
break;
case R.styleable.PieProgressView_frontColor:
// 默認前景顏色設置爲藍色
mFrontColor = a.getColor(attr, Color.BLUE);
Log.i("log","mFrontColor="+Integer.toHexString(mFrontColor));
break;
case R.styleable.PieProgressView_diameter:
// 默認設置爲40dp
mDiameter = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 40, getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
/**
* 獲得繪製文本的寬和高
*/
mPaint = new Paint();
mBound = new Rect();
progressValue=0;
}
3、重寫onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = 0;
int height = 0;
/**
* 設置寬度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
switch (specMode)
{
case MeasureSpec.EXACTLY:// 明確指定了
width = getPaddingLeft() + getPaddingRight() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般爲WARP_CONTENT
width = getPaddingLeft() + getPaddingRight() + mBound.width();
break;
}
/**
* 設置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode)
{
case MeasureSpec.EXACTLY:// 明確指定了
height = getPaddingTop() + getPaddingBottom() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般爲WARP_CONTENT
height = getPaddingTop() + getPaddingBottom() + mBound.height();
break;
}
//設置直徑的最小值
if(mDiameter<=40){
mDiameter=40;
}
height=mDiameter;
width=mDiameter;
Log.i("log","w="+width+" h="+height);
setMeasuredDimension(width, height);
}
4、重寫onDraw
protected void onDraw(Canvas canvas)
{
mPaint.setColor(Color.YELLOW);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
RectF rect = new RectF(0, 0, width, height);//200, 200);
// Log.i("log","w="+width+" h="+height);
mPaint.setColor(mBackColor);
canvas.drawArc(rect, 0, 360, true, mPaint);
mPaint.setColor(mFrontColor);
canvas.drawArc(rect, 0, progressValue*360/100, true, mPaint);
}
5、提供對外接口
這是一個很重要的對外接口,用於獲取新的進度值:
public void setInputData(int inputData){
progressValue = inputData;
}
6、在佈局文件中定義
在佈局文件中我定義了2個view,用於測試各項自定義屬性:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.customview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.customview.view.PieProgressView
android:id="@+id/pie_progress_view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="10dp"
custom:backColor="#ff205030"
custom:frontColor="#ff60E0E0"
custom:diameter="200dp"
/>
<com.customview.view.PieProgressView
android:id="@+id/pie_progress_view2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/pie_progress_view1"
android:layout_alignBottom="@id/pie_progress_view1"
android:padding="10dp"
custom:backColor="#ff503040"
custom:frontColor="#fff080E0"
custom:diameter="40dp"
/>
</RelativeLayout>
7、在Activity中使用
主要是一個定時器的使用,用於動態更新進度條:
package com.customview;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
import com.customview.view.PieProgressView;
import android.app.Activity;
import android.widget.TextView;
public class MainActivity extends Activity
{
TextView textView;
PieProgressView pie_progress_view1;
PieProgressView pie_progress_view2;
int progressValue=0;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pie_progress_view1 = (PieProgressView)findViewById(R.id.pie_progress_view1);
pie_progress_view2 = (PieProgressView)findViewById(R.id.pie_progress_view2);
timer.schedule(task, 1000, 1000); // 1s後執行task,經過1s再次執行
}
Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1) {
Log.i("log","handler : progressValue="+progressValue);
//通知view,進度值有變化
pie_progress_view1.setInputData(progressValue);
pie_progress_view1.postInvalidate();
pie_progress_view2.setInputData(progressValue*2);
pie_progress_view2.postInvalidate();
progressValue++;
if(progressValue>100){
timer.cancel();
}
}
super.handleMessage(msg);
};
};
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
// 需要做的事:發送消息
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
}
至此,完美收工,效果如下: