在上一篇文章中,我們有選擇的更新了文字時鐘小部件、鎖屏小部件和daydream小部件。使其根據Android系統的版本,改變配色從而迎合新的Android系統KitKat的主題。在這篇文章中,我們會來給它增加一些動畫效果。
譯註:想了解daydream相關資訊店開此傳送門,簡單來說就是手機的屏保?
要新增動畫效果,首先定義一組動畫效果。
我們希望為已經存在的文字有設定從左側滑出的動畫效果,所以在res
資料夾下的anim
資料夾(即res/anim
)下新建一個配置檔案out_left.xml
。內容如下:
1 2 3 4 5 6 7 8 |
; html-script: false ]<?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:fromXDelta="0%p" android:toXDelta="-100%p" android:duration="@android:integer/config_longAnimTime"> </translate> |
同時,我們希望新出現的文字帶有從右側滑進來的動畫效果,因此還是在上面相同的目錄(res/anim 動畫資原始檔夾)下新建in_left.xml。內容如下:
1 2 3 4 5 6 7 8 |
; html-script: false ]<?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:fromXDelta="100%p" android:toXDelta="0%p" android:duration="@android:integer/config_longAnimTime"> </translate> |
這裡不會對view
的動畫效果進行說明。如果對於view
動畫這一塊還不夠了解,可以參考Simple Animation這篇文章。
接下來在應用中會多次使用這些動畫,所以一開始就建立它們是明智的做法。
我們從daydream
開始,因為這個最簡單,只需要處理TextView
。所以,最簡單的方案就是使用控制元件TextSwitcher
,它會幫我們完成大量煩雜的工作。
我們需要在layout
程式碼中用TextSwitcher
控制元件替換TextView
。layout weight
(控制佈局中各個控制元件所佔空間的比例)保持不變:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
; html-script: false ]<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextSwitcher android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:gravity="bottom" android:inAnimation="@anim/in_left" android:outAnimation="@anim/out_left" android:animateFirstView="false" android:id="@+id/hours"/> <TextSwitcher android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="-20dp" android:layout_marginBottom="-20dp" android:inAnimation="@anim/in_left" android:outAnimation="@anim/out_left" android:animateFirstView="false" android:id="@+id/tens"/> <TextSwitcher android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:inAnimation="@anim/in_left" android:outAnimation="@anim/out_left" android:animateFirstView="false" android:id="@+id/minutes"/> </LinearLayout> |
我們可以建立一對靜態Textview
作為TextSwitcher
的子控制元件(即用兩個TextView
填充TextSwitcher
),但一旦建立完成之後的程式碼就要遵守這種做法。為了展現不同的方案,我們採用工廠模式建立這些子View
。
TextSwitcher
繼承自ViewSwitcher
,而ViewSwitcher
又是ViewAnimator
的子類。ViewSwticher
支援使用工廠模式建立新的view
。每次動畫開始時,就會通過工廠模式
建立一個新的view
。原來的view
在動畫效果滑出後,新的view
就會使用動畫效果滑入。之後,原來view
就從ViewSwitcher
中移除,這樣能夠被系統執行垃圾回收。
呼叫TextSwitcher
的setText()
方法時,會自動完成上述基本操作。我們需要做的是提供建立TextView
的物件工廠,這也是必須實現的。這種實現非常適合我們的要求,因為需要定義不同風格以適應小時、分鐘的十位、以及個位部分的TextView
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
; html-script: false ]private TextSwitcher hours; private TextSwitcher tens; private TextSwitcher minutes; @Override public void onAttachedToWindow() { super.onAttachedToWindow(); setInteractive( false ); setFullscreen( true ); setScreenBright( false ); setContentView( R.layout.daydream ); int animTime = getResources().getInteger( android.R.integer.config_shortAnimTime); hours = (TextSwitcher) findViewById(R.id.hours); hours.setFactory(new ViewSwitcher.ViewFactory() { @Override public View makeView() { TextView tv = new TextView( new ContextThemeWrapper( TextClockDaydream.this, R.style.hoursTextDaydream)); tv.setLayoutParams(new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT, Gravity.BOTTOM)); tv.setGravity(Gravity.BOTTOM); tv.setTextAppearance(TextClockDaydream.this, R.style.hoursTextDaydream); return tv; } }); tens = (TextSwitcher) findViewById(R.id.tens); tens.getInAnimation().setStartOffset(animTime); tens.getOutAnimation().setStartOffset(animTime); ViewSwitcher.ViewFactory minutesFactory = new ViewSwitcher.ViewFactory() { @Override public View makeView() { TextView tv = new TextView( new ContextThemeWrapper( TextClockDaydream.this, R.style.minutesTextDaydream)); tv.setLayoutParams(new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL)); tv.setTextAppearance(TextClockDaydream.this, R.style.minutesTextDaydream); return tv; } }; tens.setFactory(minutesFactory); minutes = (TextSwitcher) findViewById(R.id.minutes); minutes.getInAnimation().setStartOffset(animTime * 2); minutes.getOutAnimation().setStartOffset(animTime * 2); minutes.setFactory(minutesFactory); } |
你可能會注意到,我們人為地將各TextView
控制元件設定了不同的動畫啟動延遲。這是因為,我們希望通過視覺效果來對不同的控制元件做出區分。如果所有動畫都是同步進行,看起來會非常呆滯和刻板。但是如果錯開動畫的開始時間,看上去則會更為有趣。
我們還需要修改一下時間更新邏輯,使其只在真正發生時更新。如果不這麼做則會出現,即使時間沒有實際改變也會啟動動畫效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
; html-script: false ]private void updateTime( Calendar date ) { String[] words = TimeToWords.timeToWords( date ); if (hasTextChanged(hours, words[0])) { hours.setText(words[0]); } if (hasTextChanged(tens, words[1])) { tens.setText(words[1]); } String mins = words.length == 2 ? " " : words[2]; if (hasTextChanged(minutes, mins)) { minutes.setText(mins); } } private boolean hasTextChanged(TextSwitcher switcher, String newText) { boolean changed = newText != null; if (switcher != null && switcher.getCurrentView() != null) { View current = switcher.getCurrentView(); if (current instanceof TextView) { CharSequence currentText = ((TextView) current).getText(); if (currentText != null) { changed = !currentText.toString().equals(newText); } } } return changed; } |
好了,是時候看看我們的成果了。當時間改變時,我們會看到這樣的動畫效果:效果視訊
在本文的開始提到了,向daydream新增動畫效果是最簡單的。一旦我們要對小部件和鎖屏部件新增動畫效果,事情就變的有一些複雜了。因為我們不能通過佈局直接控制對應的view
,因此需要通過RemoteViews
完成。同時,TextSwitcher
也不支援小部件和鎖屏部件,所以我們需要採取非傳統的策略。下一篇文章,我們會來討論如何給這些部件也加上動畫效果。
本文完整原始碼可以在這裡找到,文字時鐘App可以從Google Play市場下載。與本文對應的2.01版本已經發布,未來幾週會新增一些新功能。