轉自:http://ysl-paradise.blogspot.com/2009/07/android-analogclock-1.html
Android Source Code Internals - Alarm Clock, AnalogClock (1)
鬧鐘的原始程式碼在 這裡
。
一執行這個鬧鐘程式,畫面上第一眼看到的,就是一個大大的時鐘。因此我們今天,就先從這個時鐘下手。讓我們一探究竟,看他是如何實現的。
要瞭解這個時鐘,是用什麼元件做出來的。有兩個方法,第一個就是用 SDK 內自帶的 Hierarchy Viewer
工具;另一個,就是直接觀察 res/layout 中的畫面設計檔。這次,我們直接用第二個方法。
翻開 AlarmClock/res/layout
目錄的內容,你會發現有好幾個 clock_xxx.xml 的檔案。打開其中的 clock_droid2.xml
檔。內容如下所示:
- <?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
- <
AnalogClock
- xmlns:android
=
"http://schemas.android.com/apk/res/android"
- android:id
=
"@+id/clock"
- style
=
"@style/analogClock"
- android:dial
=
"@drawable/clockdroid2_dial"
- android:hand_hour
=
"@drawable/clockdroid2_hour"
- android:hand_minute
=
"@drawable/clockdroid2_minute"
/>
<?xml version="1.0" encoding="utf-8"?><br /><AnalogClock<br /> xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:id="@+id/clock"<br /> style="@style/analogClock"<br /> android:dial="@drawable/clockdroid2_dial"<br /> android:hand_hour="@drawable/clockdroid2_hour"<br /> android:hand_minute="@drawable/clockdroid2_minute"/><br />
嗯,看起來,這個時鐘就是用 android.widget.AnalogClock 這個元件直接實現的。只要透過
android:dial, android:hand_hour, android:hand_minute 這三個 XML
屬性,指定鐘面,時針與分針的圖檔,一個完整的時鐘,就會顯現出來。最上方的圖片,就是這個時鐘顯示的樣子。
都不需要任何額外的設定,這個時鐘元件就會自己依照目前的時間,將時、分針顯示在正確的位置,那他是如何定時更新時、分針位置?另外,注意
到了嗎,時、分針的圖檔,只有一份。從這就猜到,這個時鐘還會自動將時、分針圖旋轉至正確的角度,貼在螢幕上。讓我們來看看,這個
AnalogClock 元件是如何實現這些功能的。
打開 AnalogClock 元件原始碼
,扣掉最前面的註解不算,整個原始碼也才不到 230 行。這麼少的程式碼,卻能將這個時鐘功能做得這麼完整。這也是我常最推薦,一定要看的元件原始碼之ㄧ。
這次,我們要看的重點有兩個。
- 在元件程式中,要如何隨時都知道時間的改變?
- 在 Android 中,要如何將圖片做任意角度的旋轉,並貼在螢幕上?
在元件程式中,要如何隨時都知道時間的改變?
原來在 onAttachedToWindow()
中,AnalogClock 透過 registerReceiver() 註冊了三個由系統發出的 Actions,ACTION_TIME_TICK
, ACTION_TIME_CHANGED
及 ACTION_TIMEZONE_CHANGED
。
在 Android 中,系統會透過 Intent 傳送某些系統通知訊息,這些通知訊息又稱為 Action。在 Intent
類別的說明中,你可以找到所有的 Action 定義。
在你的程式中,想要收到這些系統通知訊息,有兩個方法。第一個方法就是在 AndroidManifest.xml 中定義 receiver
的類別,這個方法在日後的介紹中會提到。第二種方法就是利用 registerReceiver(),向系統註冊接收的類別。由於
AnaloClock 是以元件的方式存在,如果每次使用這個元件,都還要在 AndroidManifest.xml 中定義,那就太麻煩了。因此,
AnaloClock 採用的是第二種方法。
其中,ACTION_TIME_TICK,就是由系統在每分整,傳送出來的 Action。ACTION_TIME_CHANGED
則是當使用者更改系統時間時,所送出的 Action。當你的手機跨越時區,系統則會送出 ACTION_TIMEZONE_CHANGED
給有註冊的程式。
- //#100 ~ #106
- IntentFilter filter = new
IntentFilter();
- filter.addAction(Intent.ACTION_TIME_TICK);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- getContext().registerReceiver(mIntentReceiver, filter, null
, mHandler);
//#100 ~ #106<br />IntentFilter filter = new IntentFilter();<br />filter.addAction(Intent.ACTION_TIME_TICK);<br />filter.addAction(Intent.ACTION_TIME_CHANGED);<br />filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);<br />getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);<br />
一但這些 Actions 發生時,他會呼叫在程式最後 new 出來的 BroadcastReceiver
物件, mIntentReceiver,中的 onReceive() 回呼函式。
首先,在 onReceive() 中,先檢查如果是時區有改變的話,當然你要重新設定 mCalendar 變數。
- //#233 ~ #245
- private
final
BroadcastReceiver mIntentReceiver =
- new
BroadcastReceiver() {
- @Override
- public
void
onReceive(Context context, Intent intent) {
- if
(intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
- String tz = intent.getStringExtra("time-zone"
);
- mCalendar = new
Time(TimeZone.getTimeZone(tz).getID());
- }
-
- onTimeChanged();
-
- invalidate();
- }
- };
//#233 ~ #245<br />private final BroadcastReceiver mIntentReceiver =<br />new BroadcastReceiver() {<br /> @Override<br /> public void onReceive(Context context, Intent intent) {<br /> if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {<br /> String tz = intent.getStringExtra("time-zone");<br /> mCalendar = new Time(TimeZone.getTimeZone(tz).getID());<br /> }<br /> onTimeChanged();</p><p> invalidate();<br /> }<br />};<br />
onTimeChanged() 函式,只是重新抓出最新的時、分資料。
- //#221 ~ #231
- private
void
onTimeChanged() {
- mCalendar.setToNow();
-
- int
hour = mCalendar.hour;
- int
minute = mCalendar.minute;
- int
second = mCalendar.second;
-
- mMinutes = minute + second / 60
.0f;
- mHour = hour + mMinutes / 60
.0f;
- mChanged = true
;
- }
//#221 ~ #231<br />private void onTimeChanged() {<br /> mCalendar.setToNow();<br /> int hour = mCalendar.hour;<br /> int minute = mCalendar.minute;<br /> int second = mCalendar.second;<br /> mMinutes = minute + second / 60.0f;<br /> mHour = hour + mMinutes / 60.0f;<br /> mChanged = true;<br />}<br />
在 onReceive() 的最後,再呼叫 View.invalidate()。這個動作,會觸發 View.onDraw() 函式的呼叫,並在螢幕上畫上最新的時,分針。
在 Android 中,要如何將圖片做任意角度的旋轉,並貼在螢幕上?
Android 的 2D 繪圖功能,其實是很強的,尤其是 matrix
這部份。透過 matrix,你就可以輕易地,控制整個繪圖座標體系的位移、旋轉、傾斜,放大等功能。舉個例子來說,如果你要將圖片旋轉 30
度後,再貼在螢幕上。傳統的作法,都是直接先將圖片的內容,逐點旋轉 30 度。但是在 Android
平台上,你不用這麼麻煩。你只要將整個座標系統轉個 30 度,之後不管你是畫線,還是貼圖,最後呈現在螢幕上的都是轉 30 度的效果。
想要瞭解 AnalogClock 是如何選轉時、分針圖片,那看 onDraw() 這個函式就對了。我將 onDraw() 中,和這部份選轉時、分針圖片相關的,列在下面。其中的 canvas.rotate()
是關鍵,這個函式就是在將座標系統轉個角度。看完這個例子,有沒有發覺要在 Android 上旋轉、放大或縮小圖片,其實都是只要兩三行程式,就可搞定的事。
- //#159 ~ #219
- @Override
- protected
void
onDraw(Canvas canvas) {
- ...
- canvas.save();
- canvas.rotate(mHour / 12
.0f *
360
.0f, x, y);
- final
Drawable hourHand = mHourHand;
- if
(changed) {
- w = hourHand.getIntrinsicWidth();
- h = hourHand.getIntrinsicHeight();
- hourHand.setBounds(x - (w / 2
), y - (h /
2
), x + (w /
2
), y + (h /
2
));
- }
- hourHand.draw(canvas);
- canvas.restore();
-
- canvas.save();
- canvas.rotate(mMinutes / 60
.0f *
360
.0f, x, y);
- final
Drawable minuteHand = mMinuteHand;
- if
(changed) {
- w = minuteHand.getIntrinsicWidth();
- h = minuteHand.getIntrinsicHeight();
- minuteHand.setBounds(x - (w / 2
), y - (h /
2
), x + (w /
2
), y + (h /
2
));
- }
- minuteHand.draw(canvas);
- canvas.restore();
- ...
- }
//#159 ~ #219<br />@Override<br />protected void onDraw(Canvas canvas) {<br /> ...<br /> canvas.save();<br /> canvas.rotate(mHour / 12.0f * 360.0f, x, y);<br /> final Drawable hourHand = mHourHand;<br /> if (changed) {<br /> w = hourHand.getIntrinsicWidth();<br /> h = hourHand.getIntrinsicHeight();<br /> hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));<br /> }<br /> hourHand.draw(canvas);<br /> canvas.restore();<br /> canvas.save();<br /> canvas.rotate(mMinutes / 60.0f * 360.0f, x, y);<br /> final Drawable minuteHand = mMinuteHand;<br /> if (changed) {<br /> w = minuteHand.getIntrinsicWidth();<br /> h = minuteHand.getIntrinsicHeight();<br /> minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));<br /> }<br /> minuteHand.draw(canvas);<br /> canvas.restore();<br /> ...<br />}<br />
AnalogClock 就剩 constructors,與其他 onMeasure(), onSizeChanged(), onDetachedFromWindow() 等函式還沒提到,我想這部份就留給你自己去發掘。