探索安卓中有意義的動畫!
ribot 致力於打造美好且充滿意義的用戶體驗,在這一過程中,動畫不可或缺。
在 Droidcon London 聽完一場 激勵人心的演講之後, 筆者決定深入研究安卓動畫。本文集中展示了其研究結果,希望使開發者和設計者們意識到,為 Android 應用添加漂亮的動畫並不複雜。
動畫!
如果你想嚐試這些動畫效果,本文所有實例都能在 Github 上的這款 Android 應用 中找到。
筆者非常喜歡動畫效果,因為它不僅提高用戶參與度,還能**迅速**奪人眼球。想想那些以動畫設計著稱的應用,它們使用起來是多麼可心、流暢、*自然*。
Falcon Pro:即使細微的動畫效果也可以對用戶體驗產生巨大影響。
現在,與那些你很喜歡但沒有動畫的應用做一番比較。
Medium: 盡管筆者很**喜愛** medium APP,但它的確缺少恰當的動畫。
小動作也能造就大不同
我們可以從多個方麵利用動畫,從而:
- 通過導航上下文傳輸用戶;
- 強化元素的層級結構;
- 展示屏幕顯示的組件變化。
本文旨在說明,在應用中實現**有意義的**動畫十分簡單可行——那麼,即刻開始吧。
觸覺反饋
在用戶觸摸屏幕時提供反饋,有助於視覺交流,形成互動。這些動畫不應分散用戶的注意力,但又使他們享受其中,獲得清晰的視感,從而鼓勵進一步操作。
安卓框架為此類反饋提供了波紋效果,通過設定視圖背景,即可使用:
?android:attr/selectableItemBackground-在視圖範圍內展示波紋效果;
波紋在接觸點開始,之後填充整個視圖背景。
?android:attr/selectableItemBackgroundBorderless –將波紋效果延伸至視圖之外。
圓形波紋效果在接觸點開始,並沿半徑延伸至視圖之外。
View Property Animator
ViewPropertyAnimator 在 API 12 首次引入,允許我們隻使用一個Animator實例,就可以簡單高效地使多個視圖屬性(並行地)執行動畫操作。
此處將繪製下文提到的所有動畫屬性。
- alpha() –設定動畫的 alpha 值;
- scaleX() 與 scaleY() – 將視圖縮放於 X 和/或 Y 坐標軸;
- translationZ()-在 Z 軸上平移視圖;
- setDuration() –設置動畫時長;
- setStartDelay() –設置動畫延時;
- setInterpolator() – 設置動畫插值;
- setListener() – 為動畫的**開始**、**結束**、**重複**、**取消**設置偵聽器。
注意: 如果已在視圖中設置了偵聽器,並打算在相同視圖下,實現其他動畫且不使用回調函數,則需要將偵聽器設為 null。
用程序實現時,簡單又整潔:
mButton.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.translationZ(10f)
.setInterpolator(new FastOutSlowInInterpolator())
.setStartDelay(200)
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) { }
@Override
public void onAnimationCancel(Animator animation) { }
@Override
public void onAnimationRepeat(Animator animation) { }
})
.start();
注意: 其實我們不需要在動畫生成器中調用 start( ) 方法,因為在停止聲明的同時,動畫就會自動啟動。在這種情況下,隻有在 UI toolkit 事件隊列開始下一次更新時,動畫才會再開始。
製作 FAB 的 alpha 動畫值
FAB 的(X 和 Y 軸)坐標動畫
FAB 的 Z 坐標動畫
注意: 考慮到向後兼容性,你可以使用ViewCompat 類,來實現在安卓 API 4 以及以上版本的ViewPropertyAnimator 類。
Object Animator
和 ViewPropertyAnimator 類似,ObjectAnimator 允許我們在目標視圖(代碼和 XML 源文件中)的不同屬性中執行動畫。然而,它們還是有些差異的:
- 在每個實例中,ObjectAnimator 隻允許對單一屬性執行動畫。例如,**坐標 Y** 隨**坐標 X** 變化;
- 但是,它允許自定義屬性的動畫,例如視圖的**前景色**。
使用自定義屬性給視圖做縮放動畫,並改變其前景色,可以達成下圖的效果:
使用自定義屬性時,可以通過調用ObjectAnimator.ofInt(),創建一個ObjectAnimator實例,此處我們聲明:
- view – 應用動畫的視圖;
- property – 設定動畫的屬性;
- start color – 動畫視圖的初始顏色;
- target color – 動畫視圖的目標顏色。
接下來,設置評估器(此處使用ArgbEvaluator 設置顏色動畫),設置延遲並執行 start( )。
private void animateForegroundColor(@ColorInt final int targetColor) {
ObjectAnimator animator =
ObjectAnimator.ofInt(YOUR_VIEW, FOREGROUND_COLOR, Color.TRANSPARENT, targetColor);
animator.setEvaluator(new ArgbEvaluator());
animator.setStartDelay(DELAY_COLOR_CHANGE);
animator.start();
}
接下來,使用相似的方法做視圖縮放的動畫,主要區別在於:
- 使用ObjectAnimator.ofFloat() 創建 ObjectAnimator 實例,因為在調整視圖大小時,並沒有改動整型值;
- 使用 View.*SCALE_X* 和 View.*SCALE_Y* 視圖屬性,而非自定義屬性。
private void resizeView() {
final float widthHeightRatio = (float) getHeight() / (float) getWidth();
resizeViewProperty(View.SCALE_X, .5f, 200);
resizeViewProperty(View.SCALE_Y, .5f / widthHeightRatio, 250);
}
private void resizeViewProperty(Property<View, Float> property,
float targetScale,
int durationOffset) {
ObjectAnimator animator = ObjectAnimator.ofFloat(this, property, 1f, targetScale);
animator.setInterpolator(new LinearOutSlowInInterpolator());
animator.setStartDelay(DELAY_COLOR_CHANGE + durationOffset);
animator.start();
}
最後,將調整完大小的視圖移開屏幕。在這種情況下,使用AdapterViewFlipper 容納離屏視圖,可以對 ViewFlipper 實例調用showNext()方法,後者會使用(定義好的動畫處理該過程。接著,下一個視圖也會使用定義好的入場動畫,自動出現在屏幕上。
Interpolators
Interpolator 可用於定義動畫的變化率,意味著動畫的速度、加速度、行為都可以改變。可用的 interpolator 有數種,且相互之間的差別微乎其微,建議讀者在設備上一探究竟。
- No Interpolator -該視圖動畫沒有變化率;
- Fast-Out Linear-In
該視圖以線型動作開始和結束動畫。
該視圖開始動作很快,逐漸降速直至結束。
該視圖以線型動作開始,逐漸降速直至結束。
該視圖在動畫開始時加速,並在接近結束時逐漸減速。
- Accelerate –視圖逐漸加速直到動畫結束;
- Decelerate –視圖逐漸減速直到動畫結束;
- Anticipate –在以標準方式開始動畫之前,視圖先進行輕微反轉;
- Anticipate-Overshoot –與 Anticipate 類似,但動畫過程中,回拉動作更為誇張;
- BounceInterpolator – 視圖動畫結束之前會有‘反彈’效果;
- LinearInterpolator – 視圖以線型平滑的動畫開始,直到結束;
- OvershootInterpolator – 視圖動畫先放大給定值,再縮回原值。
Circular Reveal
CircularReveal 使用剪切的圓形顯示或隱藏一組 UI 元素。該動畫除了帶來視覺上的連續性,還十分賞心悅目,有助於提高用戶參與度。
如上圖所示,在視圖的動畫效果顯示之前,使用 ViewPropertyAnimator 隱藏浮動操作圖標。隻需定義如下屬性就可以配置 circular reveal:
- startView – CircularReveal 的開始視圖(即壓縮視圖);
- centerX –點擊視圖的 X軸中心;
- centerY -點擊視圖的 Y軸中心;
- targetView –要顯示的視圖;
- finalRadius –剪切圓的半徑,大小等於以 X 中心和 Y 中心為直角邊的三角形的斜邊的值。
int centerX = (startView.getLeft() + startView.getRight()) / 2;
int centerY = (startView.getTop() + startView.getBottom()) / 2;
float finalRadius = (float) Math.hypot((double) centerX, (double) centerY);
Animator mCircularReveal = ViewAnimationUtils.createCircularReveal(
targetView, centerX, centerY, 0, finalRadius);
窗口轉換
定製用於活動間導航的轉換,可使用戶對應用狀態產生更為強烈的視覺聯係。默認情況可定製如下轉換:
- enter –決定活動視圖如何進入場景;
- exit -決定活動視圖如何退出場景;
- reenter –決定活動視圖**退出**後如何再度進入;
- shared elements –決定活動間如何共享視圖轉換。
自 API 21起,還有如下幾種新的轉換方式:
爆炸
Explode 轉換允許視圖從屏幕各個方位退出,會使壓縮視圖產生**爆炸**效果。
在網格布局中**爆炸**效果尤其好。
這種效果易於實現——首先,需要在 res/transition 目錄中創建如下轉換:
<explode xmlns:andro
android:duration="300"/>
具體做法如下:
- 聲明 explode 轉換;
- 設置**持續時間**為300毫秒。
接下來,需要將此設置為活動的轉換。既可以將其添加到活動主題:
<style name="AppTheme.Explode" parent="AppTheme.NoActionBar">
<item name="android:windowExitTransition">@transition/slide_explode</item>
<item name="android:windowReenterTransition">@android:transition/slide_top</item>
</style>
也可以編程的方式解決:
Transition explode = TransitionInflater.from(this).inflateTransition(R.transition.explode);
getWindow().setEnterTransition(explode);
滑動
滑動切換可以使活動從屏幕右側或底部滑入/出。可能你以前有過*類似的*效果,但是這個新切換更加靈活。
滑動切換使我們**依次**滑動子視圖
這種轉換在切換活動時尤為常見,筆者對向右側滑的流暢感覺情有獨鍾,當然這也很容易創建:
<slide xmlns:andro
android:interpolator="@android:interpolator/decelerate_cubic"
android:slideEdge="end"/>
在這裏:
- 聲明了 slide 轉換;
- 設置切換的**slideEdge**為**end**(右側),從而實現從右側開始滑動——若想要底部滑動將設置為 bottom。
漸變
漸變切換使活動轉換出現淡入或淡出的效果。
在視圖中使用漸變動畫操作簡單,且效果宜人。
創建此切換的操作比之前的切換更加簡單:
<fade xmlns:andro
android:duration="300"/>
在這裏:
- 聲明了 fade 轉換;
- 設置**持續時間**為300毫秒。
優化轉換
實驗的同時,筆者發現了一些可以改善上述轉換效果的方法。
允許窗口頁麵轉換——需要在主題中啟用下列屬性,主題都來源於一個資料主題:
<item name="android:windowContentTransitions">true</item>
啟用/禁用轉換重疊——上一轉換過程結束,新的頁麵動畫才會開始,這樣就會形成時延。在不同的案例中,若**啟用**如下屬性,轉換過程都會更加流暢自然:
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
排除特定視圖轉換—有時我們並不想讓活動中的所有視圖參與動畫,而且大多數情況下,工具欄和狀態欄是造成轉換故障主因。所幸,可以排除特定的視圖,使之無法轉換:
<explode xmlns:andro
android:duration="200">
<targets>
<target android:excludeId="@android:id/navigationBarBackground"/>
<target android:excludeId="@android:id/statusBarBackground"/>
</targets>
</explode>
工具欄和操作欄——當使用操作欄的活動向使用工具欄的活動轉換時(反之亦然),轉換過程總是磕磕絆絆。為此,應當確保轉換中的兩個活動都使用相同的組件。
轉換持續時間——既不能讓用戶等太久,也不能讓動畫轉換過快。這取決於轉換持續時間,最好通過試驗敲定恰當的時間。筆者發現,多數情況下200-500微秒最為合適。
共享元素轉換
共享元素轉換方便我們為頁麵間的共享視圖製作動畫,使動畫更為人性化,並給用戶帶來更好的視覺感受。
這裏,第一個頁麵中的視圖縮小並平移至第二個頁麵的標題圖片位置。
在布局中,必須使用 transitionName 屬性將所有共享視圖**聯係**起來——這表明了視圖間的轉換關係。下圖展示了之前動畫中的共享視圖:
這些都是共享視圖,意味著它們會在每次頁麵轉換過程中形成動畫。
為了完成如上轉換,我們首先要聲明共享轉換名稱,可以通過使用 XML 布局中的 transitionName 屬性來完成。
屏幕 1)
<RelativeLayout>
<LinearLayout>
<View
android:
android:transitionName="@string/transition_view"/>
<!-- Your other views -->
</LinearLayout>
</RelativeLayout>
屏幕**2**)
<LinearLayout>
<View
android:
android:transitionName="@string/transition_view"/>
<View
android:/>
<TextView
android:/>
<TextView
android:/>
</LinearLayout>
之後,在頁麵1中創建 Pair 對象,使之包含轉換視圖與其 transitionName。然後將其傳給頁麵選擇實例(ActivityOptionsCompat),由此兩個頁麵都得知了共享組件,就可以開始動畫了。
Pair participants = new Pair<>(mSquareView, ViewCompat.getTransitionName(mSquareView));
ActivityOptionsCompat transitionActivityOptions =
ActivityOptionsCompat.makeSceneTransitionAnimation(
SharedTransitionsActivity.this, participants);
ActivityCompat.startActivity(SharedTransitionsActivity.this,
intent, transitionActivityOptions.toBundle());
轉換的同時滑動這些視圖,有助於**完成**轉換。
以上就是兩個視圖間的轉換,那麼在第二個頁麵中從底部滑入的視圖怎麼辦呢?
(它們就是左邊的那些視圖)
其實這個實現過程也很簡單,如下:
Slide slide = new Slide(Gravity.BOTTOM);
slide.addTarget(R.id.view_separator);
slide.addTarget(R.id.text_detail);
slide.addTarget(R.id.text_close);
getWindow().setEnterTransition(slide);
如你所見,創建一個新的**Slide** 轉換:將目標視圖添加到轉換中,並將滑動動作設為入場動畫。
自定義轉換
我們也可以使用之前介紹過的 API 動畫創建自己的自定義轉換。例如,將共享元素轉換衍伸,成為轉換視圖變體——當我們需要顯示對話框(或者類似的彈框視圖)時,自定義轉換就會非常有用。具體如下所示:
該動畫可以在組件狀態間**引導**用戶的注意力。
先來簡單了解一下上圖發生了什麼:
- 首先創建一個SharedTransition,傳入壓縮視圖與轉換名稱以引用共享組件。
- 然後創建ArcMotion 實例,使兩個視圖轉換時形成曲線動畫效果。
- 接下來擴展 ChangeBounds 以創建自定義轉換,改變(morph)兩個形狀(對於button 和 FAB ,有兩個不同的類)。此處重寫了類中的多個方法,以便為所需屬性做動畫。最後,使用 ViewPropertyAnimator 調整對話框的透明度,使用 ObjectAnimator 調整兩個視圖間的色彩,使用 AnimatorSet 實例將兩種動畫效果整合在一起。
動態矢量圖片 ##
在 API 21中(Lollipop),AnimatedVectorDrawable 可用於製定VectorDrawable 屬性的動畫,生成動態圖片。
在圖片上做幾種不同的動畫並不容易。
那麼如何完成呢,請看下圖:
該圖由幾個不同文件組成,首先創建兩個獨立的矢量文件,每個都包含如下屬性:
- Height & Width –矢量圖像的實際大小;
- Viewport Height & Width –聲明描述矢量路徑的虛擬畫布的大小;
- Group name –聲明路徑所屬的組名;
- Pivot X & Y –聲明群組規模和旋轉所使用的中心點;
- Path Fill Color –描述矢量路徑的填充色;
- Path Data –聲明用於繪製矢量的矢量路徑數據。
注意: 所有被引用的屬性都存儲在 general strings file 中,這樣可以保持程序整潔美觀。
<vector xmlns:andro
android:height="56dp"
android:width="56dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<group
android:name="@string/groupAddRemove"
android:pivotX="12"
android:pivotY="12">
<path
android:fillColor="@color/stroke_color"
android:pathData="@string/path_add"/>
</group>
</vector>
該矢量由 ic_add.xml 文件(如下所示)生成
<vector xmlns:andro
android:height="56dp"
android:width="56dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<group
android:name="@string/groupAddRemove"
android:pivotX="12"
android:pivotY="12">
<path
android:fillColor="@color/stroke_color"
android:pathData="@string/path_remove"/>
</group>
</vector>
該矢量由ic_remove.xml 文件(如下所示)生成
接下來聲明 Animated Vector Drawable 文件,其中包含 Vector Drawable 和每個圖片狀態動畫(**Add** 或 Remove)的聲明。檢查從 Add 到 Remove 的矢量動畫,聲明一個**目標文件(target)**以完成:
- 狀態轉換的動畫;
-
圖片旋轉的動畫。
<animated-vector android:drawable="@drawable/ic_add"> <target android:name="@string/add" android:animation="@animator/add_to_remove" /> <target android:name="@string/groupAddRemove" android:animation="@animator/rotate_add_to_remove" /> </animated-vector>
然後創建目標文件中引用的每個文件。
改變圖片狀態
在add_to_remove.xml 文件中,使用ObjectAnimator 改變圖形形狀,其中會用到如下屬性:
- propertyName –執行動畫的屬性;
- valueFrom –矢量路徑的初始值;
- valueTo –矢量路徑的目標值;
- duration –動畫持續時間;
- interpolator –動畫插值器;
- valueType –動畫值類型。
<objectAnimator
xmlns:andro
android:propertyName="pathData"
android:valueFrom="@string/path_add"
android:valueTo="@string/path_remove"
android:duration="@integer/duration"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
形狀旋轉
可使用相似的方法旋轉圖像,隻是會用到旋轉屬性和旋轉值:
<objectAnimator
xmlns:andro
android:propertyName="rotation"
android:valueFrom="-180"
android:valueTo="0"
android:duration="@integer/duration"
android:interpolator="@android:interpolator/fast_out_slow_in" />
執行相反的動畫(從 Remove 到 Add)所需的操作方法相同,隻不過將動畫值反置。
完成後的動態矢量圖,效果很不錯吧?
使用OneAPM分析UI卡頓
使用 OneAPM 可以快速定位分析UI性能,Mobile Insight的卡頓可以直觀地展示這些信息。
可以分析繪製APP卡頓趨勢圖,精確定位每1秒內的繪圖刷新信號中斷的次數,從多維度分析卡頓現象,如APP版本、操作係統版本的分布情況等。
卡頓詳情列表展示:訪問時間,發生卡頓時的流暢度,耗時,發生卡頓時的設備信息,APP版本,操作係統及版本,CPU信息
通過分析該頁麵信息可以清楚了解到卡頓來源,以便針對性快速優化。
動畫卡頓原因
動畫卡頓的原因大概有這樣三種,這些因素將直接影響動畫的性能,導致卡頓。即:
- 手勢滑動速度
- 幀率
- 觸摸事件響應的速度
手勢滑動、幀率是跟各種手機設備有直接的關係,各種各樣的硬件設備會表現出不一樣的性能,如果從這個方麵入手考慮優化,就十分需要 OneAPM Mobile Insight 這樣的從多維度來分析性能的一款工具。
結語
雖然隻是淺談,文本旨在圍繞創建*有意義的*動畫提供有益的視角,使讀者受益。今後,筆者會繼續努力,以求進一步改善應用的外觀與用戶體驗。
原文地址:https://medium.com/ribot-labs/exploring-meaningful-motion-on-android-1cd95a4bc61d
OneAPM Mobile Insight 以真實用戶體驗為度量標準進行 Crash 分析,監控網絡請求及網絡錯誤,提升用戶留存。訪問 OneAPM 官方網站感受更多應用性能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術博客。
最後更新:2017-04-01 13:51:28