本人是非計算機學院純零基礎,學了極客班的安卓微專業一個半月,準備将課程學習中做的作業進行一個整理,歡迎大家指正。
今天記錄的項目是一個類似于安卓原生計時器使用handler來更新UI。當然這種方法肯定不是計時器的正确編寫方法,在時間上會有很大誤差,隻是為了練習自定義控件和Handler的用法。
最後做出的結果如圖所示,在自定義控件中繪制了圓環作為進度條,在進度條内繪制數字來顯示時間。有開始、計次、複位三個按鈕。當按計次按鈕時在按鈕上方的listview中會記錄下目前記錄條數、目前記錄的總時間、目前記錄和上次記錄的間隔時間。
總體的思路就是繪制一個自定義控件,在activity中引用自定義控件,在activity中使用handler不斷發送資訊來更新自定義控件。
話不多說,直接上代碼,由于是初學者,是以代碼會注釋得十分詳細,也很适合初學者看。
public class ProgressBarByMyself extends View {
//目前進度條進度
private static float mProgress;
//總進度
private int mTotalProgress = ;
//畫圓環背景的畫筆
private Paint mCirclePaint;
//畫圓環的畫筆
private Paint mRingPaint;
//畫字型的畫筆
private Paint mTextPaint;
//文字長度
private float mTextWidth;
//文字高度
private float mTextHeight;
//設定毫秒字型的畫筆、文字長度、文字高度
private Paint mTestTextPaint;
private float mTestTextWidth;
private float mTestTextHeight;
//圓環背景顔色
private int mCircleColor;
//圓環顔色
private int mRingColor;
//圓環半徑
private float mRingRadius;
//圓環寬度
private float mStrokeWidth;
//内圓半徑
private float mRadius;
//圓心X坐标
private int mXCenter;
//圓心Y坐标
private int mYCenter;
public ProgressBarByMyself(Context context) {
this(context, null);
}
public ProgressBarByMyself(Context context, AttributeSet attrs) {
this(context, attrs, );
//擷取自定義屬性
}
public ProgressBarByMyself(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//初始化各種屬性
initAttrs(context, attrs);
//擷取畫筆屬性
initVariable();
}
private void initVariable() {
//設定背景圓環的畫筆屬性
//設定抗鋸齒屬性
mCirclePaint.setAntiAlias(true);
//設定顔色
mCirclePaint.setColor(mCircleColor);
//設定畫圖樣式為圓環
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setStrokeWidth(mStrokeWidth);
//設定紅色活動進度條的畫筆屬性
mRingPaint.setAntiAlias(true);
mRingPaint.setColor(mRingColor);
mRingPaint.setStyle(Paint.Style.STROKE);
mRingPaint.setStrokeWidth(mStrokeWidth);
//設定字型的畫筆屬性
mTextPaint.setAntiAlias(true);
mTextPaint.setStyle(Paint.Style.FILL);
//設定透明度和色彩,第一個參數是透明度,後三個參數是色彩
mTextPaint.setARGB(, , , );
//設定毫秒字型的畫筆屬性
mTestTextPaint.setAntiAlias(true);
mTestTextPaint.setStyle(Paint.Style.FILL);
mTestTextPaint.setARGB(, , , );
}
private void initAttrs(Context context, AttributeSet attrs) {
//建構畫筆執行個體
mCirclePaint = new Paint();
mRingPaint = new Paint();
mTextPaint = new Paint();
mTestTextPaint = new Paint();
TypedArray typeArray = context.obtainStyledAttributes(attrs,R.styleable.ProgressBarByMyself);
//設定圓環寬度
mStrokeWidth = typeArray.getDimension(R.styleable.ProgressBarByMyself_strokeWidth, );
//設定背景進度條的顔色
mCircleColor=typeArray.getColor(R.styleable.ProgressBarByMyself_circleColor, );
//設定紅色活動進度條的顔色
mRingColor =typeArray.getColor(R.styleable.ProgressBarByMyself_ringColor, );
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//擷取各屬性的具體數字,mXcenter和mYcenter是圓環的圓心坐标。
mXCenter = getWidth()/;
mYCenter =getHeight()/;
mRadius = getWidth()/;
//設定字型的大小
mTextPaint.setTextSize(mRadius*/);
mTestTextPaint.setTextSize(mRadius/);
mRingRadius = mRadius +mStrokeWidth/;
//繪制出背景圓環
canvas.drawCircle(mXCenter, mYCenter, mRingRadius, mCirclePaint);
//擷取字型屬性
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
mTextHeight = (int) Math.ceil(fm.descent - fm.ascent);
Paint.FontMetrics lf = mTestTextPaint.getFontMetrics();
mTestTextHeight = (int) Math.ceil(fm.descent - fm.ascent);
//設定格式化顯示數字
String text = String.format("%1$03d",((int)mProgress/));
String testtext = String.format("%1$02d",(int)mProgress%);
//設定字型寬度是剛好将自己放滿的寬度
mTextWidth = mTextPaint.measureText(text, , text.length());
mTestTextWidth = mTestTextPaint.measureText(testtext, , testtext.length());
//計算外切矩形的點
RectF Oval = new RectF();
Oval.left = (mXCenter - mRingRadius);
Oval.top=(mYCenter - mRingRadius);
Oval.right = mRingRadius+mXCenter;
Oval.bottom = mRingRadius+mYCenter;
//畫出顯示秒和毫秒的數字以及紅色活動進度條
canvas.drawText(text,mXCenter-mTextWidth*/,mYCenter+mTextHeight/,mTextPaint);
canvas.drawText(testtext,mXCenter+mTextWidth/,mYCenter+mTestTextHeight/,mTestTextPaint);
canvas.drawArc(Oval,-,(mProgress/mTotalProgress*),false,mRingPaint);
}
public static void setProgress(float progress) {
mProgress = progress;
}
public static float getProgress(){
float l=mProgress;
return l;
}
}
在自定義控件中主要畫出的就是計時器的圓形進度條和顯示的數字,其中的mProgress設定的是目前進度條的進度,在Activity中就是通過這一參數才對進度條進行更新,同樣的計次中listview中的資料也是通過擷取這個mProgress來計時的。
接着就上MainActivity的代碼。
public class HandlerActivity extends Activity implements View.OnClickListener {
private ArrayList<TimeCount> timeCounts = new ArrayList<TimeCount>();
//設定Message的辨識代号
public static final int MESSAGE_CODE = ;
//初始化Handler
private TestHandler mTestHandler;
//初始化按鈕
private Button startButton;
private Button timeCountButton;
private Button resetButton;
//初始化自定義控件
public ProgressBarByMyself progressBarByMyself;
//設定進度條狀态辨別,flag的真假顯示了目前是運作還是暫停
private Boolean flag = true;
//用于記錄目前是第幾次計次
private int count = ;
//初始化listView
private ListView listView;
private TimeAdapter adapter;
private float countthistime=;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通過id尋找按鈕和自定義控件
progressBarByMyself = (ProgressBarByMyself) findViewById(R.id.my_progress_view);
startButton = (Button) findViewById(R.id.start_button);
timeCountButton = (Button) findViewById(R.id.time_count_button);
resetButton = (Button) findViewById(R.id.reset_button);
//為按鈕設定點選事件
startButton.setOnClickListener(this);
timeCountButton.setOnClickListener(this);
resetButton.setOnClickListener(this);
//為listview設定擴充卡
adapter= new TimeAdapter(HandlerActivity.this,R.layout.time_countent,timeCounts);
listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_button:
start();
break;
case R.id.time_count_button:
count();
break;
case R.id.reset_button:
reset();
break;
}
}
public void start(){
if (flag) {
//如果之前是暫停狀态,那麼擷取目前進度條的進度資訊然後繼續發送消息
mTestHandler = new TestHandler(this);
Message message = mTestHandler.obtainMessage();
message.arg1 = ;
message.arg2 = ;
message.what = MESSAGE_CODE;
message.obj = (int) progressBarByMyself.getProgress();
mTestHandler.sendMessage(message);
//當flag設定為false即開始狀态
flag = false;
startButton.setText("停止");
} else {
//如果之前是開始狀态,那麼現在将loop裡的消息全部清除,這樣就暫停了
mTestHandler.removeMessages(MESSAGE_CODE);
flag = true;
startButton.setText("開始");
}
}
//計時方法
public void count() {
//向listView的擴充卡傳入目前記錄是第幾條,目前經過總時間,和目前總之間與上次記錄的時間差
TimeCount timeCount= new TimeCount(count,progressBarByMyself.getProgress()/,(progressBarByMyself.getProgress()-countthistime)/);
//将目前記錄的總時間儲存起來,用來下次減的時候用
countthistime=progressBarByMyself.getProgress();
timeCounts.add(timeCount);
//liseView裡加上變化的資料
adapter.notifyDataSetChanged();
//讓表示記錄個數的變量自增
count += ;
}
//重置方法
public void reset(){
//移除loop内的Message
mTestHandler.removeMessages(MESSAGE_CODE);
//将進度條進度設定為零并重畫
progressBarByMyself.setProgress();
progressBarByMyself.invalidate();
//将liseView和擴充卡都清零
timeCounts.clear();
adapter.clear();
count=;
countthistime = ;
//将按鈕和文本都清零
startButton.setText("開始");
flag=true;
}
//建立TestHandler類
public class TestHandler extends Handler {
public WeakReference<HandlerActivity> mHandlerActivityWeakReference;
public TestHandler(HandlerActivity activity) {
mHandlerActivityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
HandlerActivity handlerActivity = mHandlerActivityWeakReference.get();
// 接收消息
switch (msg.what) {
case MESSAGE_CODE:
int value = (int) msg.obj;
//将接受到的時間值設定為進度條的進度值并讓控件重畫
progressBarByMyself.setProgress(value);
progressBarByMyself.invalidate();
//更改Message資訊
msg = Message.obtain();
msg.arg1 = ;
msg.arg2 = ;
msg.what = MESSAGE_CODE;
msg.obj = value + ;
//每隔10ms發一次消息
sendMessageDelayed(msg, );
break;
}
}
}
}
在主布局中,通過開始、計次和清零按鈕來對UI進行控制。
當點選開始按鈕時,Message消息開始每隔10ms發送一次,接受到消息之後就将消息裡的value值給mProgress,然後進行UI更新。
當計時器開始計時後,開始按鈕的文本就更換為暫停,點選暫停後,就會将MessageQueue裡的Message全部清空,這樣就不會繼續接受和發送了,進度條也就不動了。
當點選計次按鈕時,會将目前的次數count、進度條的進度mProgress、本次記錄的進度與上次記錄的進度相減這三個資訊傳入adapter,同時将目前進度記錄下來用來下次計算。為了讓最後的記錄始終能最先看到,listview裡要設定一個屬性android:stackFromBottom=”true”這樣每次listview更新後會自動滾動到最下方。
接下來貼上Adapter和計次類。
public class TimeCount {
//初始化目前記錄條數、目前總時間、本次與上次記錄的時間差
private int mNumber;
private float time;
private float alltime;
private static float Alltime;
//擷取到目前記錄條數、目前總時間、本次與上次記錄的時間差
public TimeCount(int count, float progress, float v) {
this.mNumber = count;
this.alltime = progress;
this.time = v;
}
public int getmNumber() {
return mNumber;
}
public float getTime() {
return time;
}
public float getAlltime() {
return alltime;
}
}
public class TimeAdapter extends ArrayAdapter<TimeCount> {
private int resourceId;
public TimeAdapter(Context context, int textViewResourceId, List<TimeCount> objects) {
super(context,textViewResourceId, objects);
resourceId = textViewResourceId;
}
@Override
public View getView( int position,View convertView, ViewGroup parent) {
//擷取位置資訊
TimeCount timeCount = getItem(position);
View view;
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
//計次參數
TextView number= (TextView) view.findViewById(R.id.number);
//計次時間間隔
TextView time= (TextView) view.findViewById(R.id.time);
//目前經過的總時間
TextView alltime= (TextView) view.findViewById(R.id.all_time);
//擷取到HandlerActivity傳入TimeCount裡的記錄條數、目前總時間、兩次記錄時間差得資訊
number.setText(String.format("# %1$02d",timeCount.getmNumber()));
time.setText(timeCount.getTime()+"");
alltime.setText(timeCount.getAlltime()+"");
return view;
}
}
由于本人是新手,是以一些命名和格式可能做得不規範,歡迎大家指正。