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

來源:互聯網
上載者:User

 

 

轉自: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
檔。內容如下所示:

  1. <?
    xml
     
    version
    =
    "1.0"
     
    encoding
    =
    "utf-8"
    ?>
      
  2. <
    AnalogClock
       
  3.  xmlns:android
    =
    "http://schemas.android.com/apk/res/android"
      
  4.  android:id
    =
    "@+id/clock"
      
  5.  style
    =
    "@style/analogClock"
      
  6.  android:dial
    =
    "@drawable/clockdroid2_dial"
      
  7.  android:hand_hour
    =
    "@drawable/clockdroid2_hour"
      
  8.  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 行。這麼少的程式碼,卻能將這個時鐘功能做得這麼完整。這也是我常最推薦,一定要看的元件原始碼之ㄧ。

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

  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
給有註冊的程式。

  1. //#100 ~ #106
      
  2. IntentFilter filter = new
     IntentFilter();  
  3. filter.addAction(Intent.ACTION_TIME_TICK);  
  4. filter.addAction(Intent.ACTION_TIME_CHANGED);  
  5. filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);  
  6. 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 變數。

  1. //#233 ~ #245
      
  2. private
     
    final
     BroadcastReceiver mIntentReceiver =   
  3. new
     BroadcastReceiver() {  
  4.  @Override
      
  5.  public
     
    void
     onReceive(Context context, Intent intent) {  
  6.   if
     (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {  
  7.    String tz = intent.getStringExtra("time-zone"
    );  
  8.    mCalendar = new
     Time(TimeZone.getTimeZone(tz).getID());  
  9.   }  
  10.   
  11.   onTimeChanged();  
  12.           
  13.   invalidate();  
  14.  }  
  15. };  

//#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() 函式,只是重新抓出最新的時、分資料。

  1. //#221 ~ #231
      
  2. private
     
    void
     onTimeChanged() {  
  3.  mCalendar.setToNow();  
  4.   
  5.  int
     hour = mCalendar.hour;  
  6.  int
     minute = mCalendar.minute;  
  7.  int
     second = mCalendar.second;  
  8.   
  9.  mMinutes = minute + second / 60
    .0f;  
  10.  mHour = hour + mMinutes / 60
    .0f;  
  11.  mChanged = true
    ;  
  12. }    

//#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 上旋轉、放大或縮小圖片,其實都是只要兩三行程式,就可搞定的事。

  1. //#159 ~ #219
      
  2. @Override
      
  3. protected
     
    void
     onDraw(Canvas canvas) {  
  4.  ...  
  5.  canvas.save();  
  6.  canvas.rotate(mHour / 12
    .0f * 
    360
    .0f, x, y);  
  7.  final
     Drawable hourHand = mHourHand;  
  8.  if
     (changed) {  
  9.      w = hourHand.getIntrinsicWidth();  
  10.      h = hourHand.getIntrinsicHeight();  
  11.      hourHand.setBounds(x - (w / 2
    ), y - (h / 
    2
    ), x + (w / 
    2
    ), y + (h / 
    2
    ));  
  12.  }  
  13.  hourHand.draw(canvas);  
  14.  canvas.restore();  
  15.   
  16.  canvas.save();  
  17.  canvas.rotate(mMinutes / 60
    .0f * 
    360
    .0f, x, y);  
  18.  final
     Drawable minuteHand = mMinuteHand;  
  19.  if
     (changed) {  
  20.      w = minuteHand.getIntrinsicWidth();  
  21.      h = minuteHand.getIntrinsicHeight();  
  22.      minuteHand.setBounds(x - (w / 2
    ), y - (h / 
    2
    ), x + (w / 
    2
    ), y + (h / 
    2
    ));  
  23.  }  
  24.  minuteHand.draw(canvas);  
  25.  canvas.restore();  
  26.  ...  
  27. }  

//#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() 等函式還沒提到,我想這部份就留給你自己去發掘。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.