閱讀627 返回首頁    go 阿裏雲 go 技術社區[雲棲]


探索安卓中有意義的動畫!

ribot 致力於打造美好且充滿意義的用戶體驗,在這一過程中,動畫不可或缺

google

在 Droidcon London 聽完一場 激勵人心的演講之後, 筆者決定深入研究安卓動畫。本文集中展示了其研究結果,希望使開發者和設計者們意識到,為 Android 應用添加漂亮的動畫並不複雜。

animate

動畫!

如果你想嚐試這些動畫效果,本文所有實例都能在 Github 上的這款 Android 應用 中找到。

筆者非常喜歡動畫效果,因為它不僅提高用戶參與度,還能**迅速**奪人眼球。想想那些以動畫設計著稱的應用,它們使用起來是多麼可心、流暢、*自然*。

falcon pro

Falcon Pro:即使細微的動畫效果也可以對用戶體驗產生巨大影響。

現在,與那些你很喜歡但沒有動畫的應用做一番比較。

medium

Medium: 盡管筆者很**喜愛** medium APP,但它的確缺少恰當的動畫。

小動作也能造就大不同

我們可以從多個方麵利用動畫,從而:

  • 通過導航上下文傳輸用戶;
  • 強化元素的層級結構;
  • 展示屏幕顯示的組件變化。

本文旨在說明,在應用中實現**有意義的**動畫十分簡單可行——那麼,即刻開始吧。

觸覺反饋

在用戶觸摸屏幕時提供反饋,有助於視覺交流,形成互動。這些動畫不應分散用戶的注意力,但又使他們享受其中,獲得清晰的視感,從而鼓勵進一步操作。

安卓框架為此類反饋提供了波紋效果,通過設定視圖背景,即可使用:

?android:attr/selectableItemBackground-在視圖範圍內展示波紋效果;

ripple

波紋在接觸點開始,之後填充整個視圖背景。

?android:attr/selectableItemBackgroundBorderless –將波紋效果延伸至視圖之外。

ripple2

圓形波紋效果在接觸點開始,並沿半徑延伸至視圖之外。

View Property Animator

ViewPropertyAnimator 在 API 12 首次引入,允許我們隻使用一個Animator實例,就可以簡單高效地使多個視圖屬性(並行地)執行動畫操作。

viewproperty

此處將繪製下文提到的所有動畫屬性。

注意: 如果已在視圖中設置了偵聽器,並打算在相同視圖下,實現其他動畫且不使用回調函數,則需要將偵聽器設為 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 事件隊列開始下一次更新時,動畫才會再開始。

alpha

製作 FAB 的 alpha 動畫值

x,y

FAB 的(X 和 Y 軸)坐標動畫

z

FAB 的 Z 坐標動畫

注意: 考慮到向後兼容性,你可以使用ViewCompat 類,來實現在安卓 API 4 以及以上版本的ViewPropertyAnimator 類。

Object Animator

ViewPropertyAnimator 類似,ObjectAnimator 允許我們在目標視圖(代碼和 XML 源文件中)的不同屬性中執行動畫。然而,它們還是有些差異的:

  • 在每個實例中,ObjectAnimator 隻允許對單一屬性執行動畫。例如,**坐標 Y** 隨**坐標 X** 變化;
  • 但是,它允許自定義屬性的動畫,例如視圖的**前景色**。

使用自定義屬性給視圖做縮放動畫,並改變其前景色,可以達成下圖的效果:

custom

使用自定義屬性時,可以通過調用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 有數種,且相互之間的差別微乎其微,建議讀者在設備上一探究竟。

fast1

該視圖以線型動作開始和結束動畫。

fast2

該視圖開始動作很快,逐漸降速直至結束。

linear

該視圖以線型動作開始,逐漸降速直至結束。

accelarate

該視圖在動畫開始時加速,並在接近結束時逐漸減速。

Circular Reveal

CircularReveal 使用剪切的圓形顯示或隱藏一組 UI 元素。該動畫除了帶來視覺上的連續性,還十分賞心悅目,有助於提高用戶參與度。

circular

如上圖所示,在視圖的動畫效果顯示之前,使用 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 轉換允許視圖從屏幕各個方位退出,會使壓縮視圖產生**爆炸**效果。

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

滑動切換使我們**依次**滑動子視圖

這種轉換在切換活動時尤為常見,筆者對向右側滑的流暢感覺情有獨鍾,當然這也很容易創建:

<slide xmlns:andro
    android:interpolator="@android:interpolator/decelerate_cubic"
    android:slideEdge="end"/>

在這裏:

  • 聲明了 slide 轉換;
  • 設置切換的**slideEdge**為**end**(右側),從而實現從右側開始滑動——若想要底部滑動將設置為 bottom

漸變

漸變切換使活動轉換出現淡入或淡出的效果。

fade

在視圖中使用漸變動畫操作簡單,且效果宜人。

創建此切換的操作比之前的切換更加簡單:

<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微秒最為合適。

共享元素轉換

共享元素轉換方便我們為頁麵間的共享視圖製作動畫,使動畫更為人性化,並給用戶帶來更好的視覺感受。

shared

這裏,第一個頁麵中的視圖縮小並平移至第二個頁麵的標題圖片位置。

在布局中,必須使用 transitionName 屬性將所有共享視圖**聯係**起來——這表明了視圖間的轉換關係。下圖展示了之前動畫中的共享視圖:

link

這些都是共享視圖,意味著它們會在每次頁麵轉換過程中形成動畫。

為了完成如上轉換,我們首先要聲明共享轉換名稱,可以通過使用 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());

sliding

轉換的同時滑動這些視圖,有助於**完成**轉換。

以上就是兩個視圖間的轉換,那麼在第二個頁麵中從底部滑入的視圖怎麼辦呢?

(它們就是左邊的那些視圖)

其實這個實現過程也很簡單,如下:

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 動畫創建自己的自定義轉換。例如,將共享元素轉換衍伸,成為轉換視圖變體——當我們需要顯示對話框(或者類似的彈框視圖)時,自定義轉換就會非常有用。具體如下所示:

custom2

該動畫可以在組件狀態間**引導**用戶的注意力。

先來簡單了解一下上圖發生了什麼:

  • 首先創建一個SharedTransition,傳入壓縮視圖與轉換名稱以引用共享組件。
  • 然後創建ArcMotion 實例,使兩個視圖轉換時形成曲線動畫效果。
  • 接下來擴展 ChangeBounds 以創建自定義轉換,改變(morph)兩個形狀(對於button 和 FAB ,有兩個不同的類)。此處重寫了類中的多個方法,以便為所需屬性做動畫。最後,使用 ViewPropertyAnimator 調整對話框的透明度,使用 ObjectAnimator 調整兩個視圖間的色彩,使用 AnimatorSet 實例將兩種動畫效果整合在一起。

動態矢量圖片 ##

API 21中(Lollipop),AnimatedVectorDrawable 可用於製定VectorDrawable 屬性的動畫,生成動態圖片。

vectors

在圖片上做幾種不同的動畫並不容易。

那麼如何完成呢,請看下圖:

vector2

該圖由幾個不同文件組成,首先創建兩個獨立的矢量文件,每個都包含如下屬性:

  • 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>

vector3

該矢量由 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>

vectors

該矢量由ic_remove.xml 文件(如下所示)生成

接下來聲明 Animated Vector Drawable 文件,其中包含 Vector Drawable 和每個圖片狀態動畫(**Add** 或 Remove)的聲明。檢查從 AddRemove 的矢量動畫,聲明一個**目標文件(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" />

執行相反的動畫(從 RemoveAdd)所需的操作方法相同,隻不過將動畫值反置。

rotating

完成後的動態矢量圖,效果很不錯吧?

使用OneAPM分析UI卡頓

使用 OneAPM 可以快速定位分析UI性能,Mobile Insight的卡頓可以直觀地展示這些信息。
卡頓趨勢圖
可以分析繪製APP卡頓趨勢圖,精確定位每1秒內的繪圖刷新信號中斷的次數,從多維度分析卡頓現象,如APP版本、操作係統版本的分布情況等。
卡頓分布
卡頓詳情列表展示:訪問時間,發生卡頓時的流暢度,耗時,發生卡頓時的設備信息,APP版本,操作係統及版本,CPU信息
通過分析該頁麵信息可以清楚了解到卡頓來源,以便針對性快速優化。
卡頓詳情

動畫卡頓原因

動畫卡頓的原因大概有這樣三種,這些因素將直接影響動畫的性能,導致卡頓。即:

  1. 手勢滑動速度
  2. 幀率
  3. 觸摸事件響應的速度

手勢滑動、幀率是跟各種手機設備有直接的關係,各種各樣的硬件設備會表現出不一樣的性能,如果從這個方麵入手考慮優化,就十分需要 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

  上一篇:go 【全球獨家首發】阿裏雲數加大數據產品手冊V1.0(純附件)
  下一篇:go 五種令人沮喪的告警垃圾及處理辦法!