在Android 4.4(KitKat)中,谷歌新增了很多不錯的東西。現在我們來看看android.transition框架。
多年來,android不斷改進現有的動畫工具供開發者使用。在HoneyComb版本中,提供了很多不錯的API用於建立豐富、複雜的動畫。在此基礎上,KitKat的android.transition讓我們可以通過一種更直觀的方式定義動畫效果。
Scene和Transition
先從Scene和Transition概念說起。Scene定義了介面的當前狀態資訊,而Transition定義了介面之間的切換。
可以從佈局檔案中載入Scene定義,示例如下:
1 2 |
; html-script: false ] scene = Scene.getSceneForLayout(container, R.layout.example, context); |
其中container
在Scene中是一個包含了所有view的ViewGroup。如果是在fragment中,Scene就是傳入onCreateView()
方法的引數。使用Transition的最簡單方式就是使用TransitionManager
處理,示例如下:
1 2 |
; html-script: false ] TransitionManager.go(scene); |
如果在TransitionManager
中不明確需要指定哪個Transition,就會預設使用AutoTransition
,這個我們會後面介紹。也可以用inflater
載入現有的view來建立Scene,示例如下:
1 2 3 |
; html-script: false ] View view = inflater.inflate(R.layout.example, container, false); Scene scene = new Scene(container, (ViewGroup)view); |
Andorid.Transition實踐
我們來看一個更詳細的示例,首先從專案主頁下載示例程式碼AndroidTransitionExample。這已經是一個已完成的專案了,所以也可以用git checkout
檢出程式碼(以下是詳細解釋)。
首先新建只包含一個Fragment的專案,這樣可以更容易記錄一些資訊。我們為TransitionFragment
新建一個xml佈局檔案,叫做fragment_transition_scene_1.xml。接著往裡面新增一個TextView
,然後在TextView
下面再新增一個Button,如下:
fragment_transition_scene_1.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
; html-script: false ] <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/goButton" android:text="@string/button_go" android:layout_below="@id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> |
你一定猜得到,我們接下來還要新建另一個xml佈局檔案,fragment_transition_scene_2.xml。它和上一個佈局檔案基本一樣,只是把Button移到佈局底部。示例如下:
1 2 3 4 5 6 7 8 9 10 |
; html-script: false ] ... <Button android:id="@+id/goButton" android:text="@string/button_go" android:layout_below="@id/textView" android:layout_alignParentBottom="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /> ... |
這是兩個佈局的螢幕截圖:
為了看Transition的效果,我們從第二個佈局檔案中建立Scene。點選goButton的時候展示Transition的效果。我們先修改一下TransitionFragment.onCreateView()
方法的程式碼,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
; html-script: false ] @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_transition_scene_1, container, false); final Scene scene = Scene.getSceneForLayout(container, R.layout.fragment_transition_scene_2, getActivity()); Button goButton = (Button)rootView.findViewById(R.id.goButton); goButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { TransitionManager.go(scene); } }); return rootView; } |
這是點選goButton的效果:
為什麼會產生這樣的效果呢?因為如果view有相同的ID,就會被當做是同一個view,然後也會被改變(通過改變bounds),也就是說,在Scene切換時,view的位置和大小也會改變,需要注意的是兩個佈局檔案的RelativeLayout
也要有相同的ID。
使用GIT實戰
如果你對整個例子程式碼感興趣的話,也可以利用GIT檢出程式碼。從AndroidTransitionExample複製一份即可,你可以用GUI git客戶端列出專案的歷史版本,也可以定位到某個具體的提交點檢出。
當然,你也可以使用命令列。使用cd
命令切換到專案資料夾,然後執行以下命令:
1 2 |
; html-script: false ] git log --oneline |
此時,你會得到專案的提交列表,像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
; html-script: false ] cc40873 load the transition manager from the XML file c2a25d4 inflate the transition from the XML file 9871bfa add the return transition 1de57f0 use AnticipateOvershootInterpolator fbcc465 slow motion transitions 6ea37f7 extract method goToScene 34e0f8f restore transition by adding LinearLayout with id 2b000b3 example of change bounds not working when changing hierarchy 6b4629c example of transitioning from Button to ImageView 092ebe0 added layout_weight to button 1e7c5be modified layout for scene 2 d94b907 Android Studio updated IML files 24f9a74 Create README.md 0667c36 simple transition 4265f50 factor out TransitionFragment 280f123 initial commit |
以上貼出的對應於simple transition這個提交點,執行以下程式碼可以把專案設定成這個狀態:
1 2 |
; html-script: false ] git checkout 0667c36 |
執行 git checkout
,你可以跳到任意一個提交點上,下面的插入文字會給出指示操作。
修改佈局檔案
我們來修改一下第二個佈局檔案,首先把RelativeLayout
換成LinearLayout
,然後我們來介紹一個在第一個佈局檔案中沒有出現的view,最後我們重新排布一下這些view,程式碼如下:
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 |
; html-script: false ] <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scene" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal"> <TextView android:id="@+id/textView" android:text="@string/hello_world" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="@string/hello_world" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/goButton" android:text="@string/button_go" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> |
我們目前處於“1e7c5be modified layout for scene 2”提交點,在命令列執行git checkout 1e7c5be
就可以切換上去。可以看到,Transition仍然起作用,在第一個Scene裡面,不存在的view竟然出現在螢幕上,然後隨著Button和TextView的移動而逐漸消失。
我們詳細看看AutoTransition,事實證明,它只是TransitionSet
的子類,只是它給自己定義了一個執行序列,分別是fading out、changing bounds、fading in。
我們注意到,在第二個Transition中,AutoTransition會改變bounds,目前我們只看到了Button和TextView改變了位置,如果我們改變view的大小會出現什麼情況呢?在Button中新增layout_weight屬性來看看效果:
1 2 3 4 5 6 7 |
; html-script: false ] <Button android:id="@+id/goButton" android:text="@string/button_go" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> |
現在Button在轉變的過程中既改變了位置也改變了大小。
現在切換到“added layout_weight to button”這個點,命令:git checkout 092ebe0
。現在再做一些變化效果,在Scene中把Button轉變成ImageView
:
1 2 3 4 5 6 7 8 |
; html-script: false ] <ImageView android:id="@+id/goButton" android:text="@string/button_go" android:layout_weight="1" android:src="@drawable/bnr_hat" android:layout_width="wrap_content" android:layout_height="wrap_content" /> |
此時切換到“example of transitioning from Button to ImageView”這個點,命令: git checkout 6b4629c
。看看從Button變成ImageView
的效果:
仔細看你會發現,第一個Button首先被一張切割過的圖片替換了,然後逐漸移動到最終的位置,逐漸改變大小。
如果我們改變所有views的巢狀結構,比如在Button外面套一層LinearLayout
,那麼Bounds就不會改變了。transition manager要求在Scene的佈局檔案中,同一層級的view要有和之前相同的ID:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
; html-script: false ] <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:src="@drawable/bnr_hat" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/goButton" android:text="@string/button_go" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> |
此時切換到“example of change bounds not working when changing hierarchy”這個點,命令:git checkout 2b000b3
。
如果我們在第一個Scene中繼續用LinearLayout
包含Button,那麼還是不行。想讓它起作用的話,可以給LinearLayout
一個相同的ID。這會得到兩種不同的效果。
切換到“restore transition by adding LinearLayout with id”,命令:git checkout 34e0f8f
下一篇我們我們繼續研究怎麼控制Transition,以及如何從xml檔案中載入Transition。