Android 原始碼徹底研究系列 - 鬧鐘程式, AnalogClock (1)

【ysl的程式设计天堂的blog一直有看,作者的深厚技术功底与娓娓道来的清楚表达给我留下了深刻印象,苦于此blog在国内无法访问,特地将一系列经典内容转贴过来,以供学习】

本文转自: http://ysl-paradise.blogspot.com/2009/07/android-analogclock-1.html

 

 

Android 原始碼徹底研究系列 - 鬧鐘程式, AnalogClock (1)_第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"/>

嗯,看起來,這個時鐘就是用 android.widget.AnalogClock 這個元件直接實現的。只要透過 android:dial, android:hand_hour, android:hand_minute 這三個 XML 屬性,指定鐘面,時針與分針的圖檔,一個完整的時鐘,就會顯現出來。最上方的圖片,就是這個時鐘顯示的樣子。

 

都不需要任何額外的設定,這個時鐘元件就會自己依照目前的時間,將時、分針顯示在正確的位置,那他是如何定時更新時、分針位置?另外,注意 到了嗎,時、分針的圖檔,只有一份。從這就猜到,這個時鐘還會自動將時、分針圖旋轉至正確的角度,貼在螢幕上。讓我們來看看,這個 AnalogClock 元件是如何實現這些功能的。

打開 AnalogClock 元件原始碼 ,扣掉最前面的註解不算,整個原始碼也才不到 230 行。這麼少的程式碼,卻能將這個時鐘功能做得這麼完整。這也是我常最推薦,一定要看的元件原始碼之ㄧ。

這次,我們要看的重點有兩個。

  1. 在元件程式中,要如何隨時都知道時間的改變?
  2. 在 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);

一但這些 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(); } };

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; }

在 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(); ... }

AnalogClock 就剩 constructors,與其他 onMeasure(), onSizeChanged(), onDetachedFromWindow() 等函式還沒提到,我想這部份就留給你自己去發掘。

你可能感兴趣的:(Android 原始碼徹底研究系列 - 鬧鐘程式, AnalogClock (1))