Android中的Transition(一)
讓所有的一切都動起來
谷歌在Material Design有這樣一個闡述:動畫不再僅是iOS才有。這裡有一種新概念叫。
Motion提供了另外一層含義:讓呈現在使用者面前的物件沒有被破壞連貫性,即使它們改變或者重組。在Material Design中的Motion被用來描述空間的關係、功能和意圖。
事實上,建立動畫的過程是需要時間的。但只需要呼叫setVisibily(View.VISIBLE)和將業務邏輯移到你的最棒的新特性中,因為它已經超越了所有的界限。但是記住:每一次你忽略了新增有意義的UI過渡,一個可悲的設計師就將出現在這個世界上。
假使我告訴你動畫比你想象的需要的工作更少,你會怎麼樣?你曾聽說過Transitions API嗎?是的,它是谷歌對Activity之間奇幻的動畫所推出的。不幸的是,這僅適用於5.0以上的系統。所以,沒有人實際使用過它。但是想象這種API能夠被高效地使用在不同的情況中, 甚至更令人激動的是,能夠適用在Android舊版本中。
讓我們從一些歷史開始
在4.0中引入了對ViewGroup新的animateLayoutChange引數。但是,即使呼叫getLayoutTransition()和配置一些東西在它裡面,它仍然是不穩定的和不足夠靈活。所以你用它不能做太多事情。
版本4.4Kitkat帶來了場景(Scenes)和過渡(Transitions)觀念。場景在技術上是場景根(佈局容器)中所有檢視的狀態。過渡是將被用於檢視來執行從一個場景到另一個場景平滑過渡的一系列動畫。這些例子會更吸引人嗎?當然。
想象有一個按鈕
在點選之後,想要一行文字出現在按鈕下面。這裡是佈局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/transitions_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DO MAGIC"/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Transitions are awesome!"
android:visibility="gone"/>
</LinearLayout>
在Java程式碼中有一個點選監聽器:
final ViewGroup transitionsContainer = (ViewGroup) view.findViewById(R.id.transitions_container);
final TextView text = (TextView) transitionsContainer.findViewById(R.id.text);
final Button button = (Button) transitionsContainer.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
boolean visible;
@Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition(transitionsContainer);
visible = !visible;
text.setVisibility(visible ? View.VISIBLE : View.GONE);
}
});
執行效果:
還不錯。僅僅一行程式碼就實現了動畫。
我們還通過beginDelayedTransition方法的第二個引數來指定明確被運用的過渡型別。
簡單過渡型別
- ChangeBounds 改變檢視的位置和大小
- Fade 繼承自Visibility類,執行最流行的動畫-漸入漸出
- TransitionSet 一系列過渡動畫集合。它們可以混合一起開始執行,也可以按次序執行,只需要通過呼叫setOrdering方法來設定。
- AutoTransition 它是一個TransitionSet,包含了Fade out,ChangeBounds和Fade in,且順序是次序的。首先,檢視不存在;其次,場景漸出;然後,改變位置和大小;最後,新的檢視漸入。AutoTransition 是預設使用的過渡,不用指定beginDelayedTransition方法的第二個引數。
相容
每個人都想寫一個在每個Android版本上都一致的實現。有希望的是我們仍然使用Transitions API時可以實現它。前不久,我發現了兩個十分相似的庫:
https://github.com/guerwan/TransitionsBackport
https://github.com/pardom/TransitionSupportLibrary
它們已經不再被維護了,且它們都丟失了一些仍然可以相容的東西。因此,基於它們我建立了我自己的庫,而且增加了很多新的東西來相容舊的Android版本。從Lollipop到Marshmallow,所有的API改變也已經被合併進來了。
因此,它就是Transitions-Everywhere,可以相容Android 4.0以上的Transitions API,使用它只需要指定gradle依賴:
dependencies {
compile "com.andkulikov:transitionseverywhere:1.7.0"
}
對於所有相關的類,只需將匯入的包android.transition.*
替換為com.transitionseverywhere.* 就可以了。
谷歌剛釋出了Transitions框架的支援庫(Support Library),我寫了一篇關於它的文章article to compare it with my library,請檢視。
我們還可以做什麼
首先,我們可以在過渡裡面修改動畫的時間、插值器和啟動延遲:
transition.setDuration(300);
transition.setInterpolator(new FastOutSlowInInterpolator());
transition.setStartDelay(200);
接下來看看其它可用的過渡型別。
Slid
與漸變過渡相似,繼承自Visibility類。它幫助場景裡的檢視從一邊滑到另外一邊。下面是 Slide(Gravity.RIGHT)的例子:
Explode and Propagation
Explode非常像Slid,但是檢視在滑動時會用基於過渡中心的一些計算方向(你應該為它提供setEpicenterCallback方法)。
TransitionPropagation為每一個動畫繪畫者計算啟動延遲。例
如,Explode預設使用CircularPropagation。動畫延遲取決於檢視和中心的距離。要使用它,在Transition中呼叫setPropagation。
讓我們看看,我們有一個GridLayoutManager的RecyclerView,而且我們想要在點選任何一個指定的元素之後刪除所有的元素。像這樣:
public void onClick(View clickedView) {
// save rect of view in screen coordinates
final Rect viewRect = new Rect();
clickedView.getGlobalVisibleRect(viewRect);
// create Explode transition with epicenter
Transition explode = new Explode()
.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
return viewRect;
}
});
explode.setDuration(1000);
TransitionManager.beginDelayedTransition(recyclerView, explode);
// remove all views from Recycler View
recyclerView.setAdapter(null);
}
ChangeImageTransform
ChangeImageTransform將會改變圖片矩陣。它運用的場景是在當改變ImageView的scaleType時候。在大多數情況下,你想要與ChangeBounds配對使用來改變位置,大小和scaleType。
TransitionManager.beginDelayedTransition(transitionsContainer, new TransitionSet()
.addTransition(new ChangeBounds())
.addTransition(new ChangeImageTransform()));
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) imageView.getLayoutParams();
params.width = expanded ? FrameLayout.LayoutParams.MATCH_PARENT :
FrameLayout.LayoutParams.WRAP_CONTENT;
params.height = expanded ? FrameLayout.LayoutParams.MATCH_PARENT :
FrameLayout.LayoutParams.WRAP_CONTENT;
imageView.setLayoutParams(params);
imageView.setScaleType(expanded ? ImageView.ScaleType.CENTER_CROP :
ImageView.ScaleType.FIT_CENTER);
expanded = !expanded;
Path (Curved) motion
真實世界的力量,如重力,激發元素的運動是沿著弧線而不是直線。
每一個過渡都操作兩個座標(例如:ChangeBounds改變檢視的位置),我們可以通過setPathMotion方法來運用曲線運動。
ChangeBounds changeBounds = new ChangeBounds();
ArcMotion arcMotion = new ArcMotion();
arcMotion.setMinimumHorizontalAngle(15f);
arcMotion.setMinimumVerticalAngle(0f);
arcMotion.setMinimumVerticalAngle(90f);
changeBounds.setPathMotion(new ArcMotion());
changeBounds.setDuration(500);
TransitionManager.beginDelayedTransition(transitionsContainer,
changeBounds);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) v.getLayoutParams();
params.gravity = isReturnAnimation ? (Gravity.LEFT | Gravity.TOP) :
(Gravity.BOTTOM | Gravity.RIGHT);
v.setLayoutParams(params);
isReturnAnimation = !isReturnAnimation;