創(chuàng)建Material Design風(fēng)格的Android應(yīng)用--使用自定義動(dòng)畫

本人所有文章首先發(fā)布于個(gè)人博客,歡迎關(guān)注,地址:http://blog.isming.me

動(dòng)畫在Material Design設(shè)計(jì)中給用戶反饋放用戶點(diǎn)擊時(shí),并且在程序用戶界面中提供連貫的視覺。Material主題為按鈕(Button)和activity的轉(zhuǎn)換提供了一些默認(rèn)的動(dòng)畫,在android5.0(api 21)和更高的版本,你可以自定義這些動(dòng)畫和創(chuàng)建一個(gè)新動(dòng)畫:

  • Touch feedback(觸摸反饋)
  • Circular Reveal(循環(huán)揭露效果)
  • Activity transitions(Activity轉(zhuǎn)換效果)
  • Curved motion(曲線運(yùn)動(dòng))
  • View state changes (視圖狀態(tài)改變)

自定義觸摸反饋

觸摸反饋在Material Design中在觸摸點(diǎn)提供了一個(gè)即時(shí)視覺確認(rèn)當(dāng)用戶作用在UI元素。按鈕的默認(rèn)觸摸反饋動(dòng)畫是使用了新的RippleDrawable類,它會(huì)是波紋效果在不同狀態(tài)間變換。

大多數(shù)情況下,我們可以使用這個(gè)功能通過在xml文件中定義背景:

?android:attr/selectableItemBackground 有界限的波紋
?android:attr/selectableItemBackgroundBorderless 可以超出視圖區(qū)域的波紋
?android:attr/selectableItemBackgroundBorderless 是21新添加的api

另外,還以使用ripple元素定義RippleDrawable作為一個(gè)xml資源。

你可以給RippleDrawable對(duì)象分配一個(gè)顏色。使用主題的android:colorControlHighlight屬性可以改變默認(rèn)的觸摸反饋顏色。

更多信息,查看RippleDrawable類的api指南。

使用揭露效果

揭露動(dòng)畫為用戶提供視覺上的持續(xù)性擋顯示或者隱藏一組界面元素。ViewAnimationUtils.createCircularReveal()方法使你可以使用動(dòng)畫效果來揭露或者隱藏一個(gè)視圖。

這樣揭露一個(gè)先前隱藏的視圖:

// previously invisible view
View myView = findViewById(R.id.my_view);

// get the center for the clipping circle
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;

// get the final radius for the clipping circle
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());

// create the animator for this view (the start radius is zero)
Animator anim =
    ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);

// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();

這樣隱藏一個(gè)先前顯示的視圖:

// previously visible view
final View myView = findViewById(R.id.my_view);

// get the center for the clipping circle
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;

// get the initial radius for the clipping circle
int initialRadius = myView.getWidth();

// create the animation (the final radius is zero)
Animator anim =
    ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);

// make the view invisible when the animation is done
anim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        myView.setVisibility(View.INVISIBLE);
    }
});

// start the animation
anim.start();

自定義activity轉(zhuǎn)換效果

activity間轉(zhuǎn)換在Material Design程序中提供不同狀態(tài)間的視覺連接通過在公用元素上動(dòng)作或者轉(zhuǎn)換。你可以為進(jìn)入或退出的轉(zhuǎn)換自定義動(dòng)畫,共享元素在不同的activity之間的轉(zhuǎn)換效果。
進(jìn)入過渡決定activity中的視圖怎樣進(jìn)入場(chǎng)景。比如在爆裂進(jìn)入過渡效果中,視圖從屏幕外面飛向屏幕中間進(jìn)入場(chǎng)景。

退出過渡決定activity中的視圖怎樣退出場(chǎng)景。比如,在爆裂退出過渡效果中,視圖從中間向遠(yuǎn)處退出場(chǎng)景。
共享元素過渡決定兩個(gè)activity之間共享的視圖怎么在兩個(gè)activity之間過渡。比如,兩個(gè)activity有一個(gè)相同的圖片,在不同的位置和不同的大小,changeImageTransform(圖片變換變化)讓共享元素平滑的平移和縮放圖片在兩個(gè)activity之間。

android 5.0(api 21)提供以下進(jìn)入和退出效果:

  • explode(爆裂) - 從場(chǎng)景中間移動(dòng)視圖進(jìn)入或者退出
  • slide(滑動(dòng)) - 視圖從場(chǎng)景的一個(gè)邊緣進(jìn)入或者退出
  • fade(淡入淡出) - 從場(chǎng)景添加或者移除一個(gè)視圖通過改變他的透明

所有過渡效果都繼承Visibility類,因此支持作為一個(gè)進(jìn)入或者退出過渡效果。

更多細(xì)節(jié),看Transition類的api指南。

Android5.0(api 21)也支持共享元素過渡效果:

  • changeBounds - 改變目標(biāo)視圖的布局邊界
  • changeClipBounds - 裁剪目標(biāo)視圖邊界
  • changeTransform - 改變目標(biāo)視圖的縮放比例和旋轉(zhuǎn)角度
  • changeImageTransform - 改變目標(biāo)圖片的大小和縮放比例

當(dāng)你在程序中開啟activity間的過渡動(dòng)畫時(shí),默認(rèn)的交叉淡入淡出效果會(huì)在兩個(gè)activity之間激活。

共享元素動(dòng)畫
共享元素動(dòng)畫

一個(gè)共享元素過渡效果

指定過渡效果

首先,使用在從material theme繼承的樣式中,使用android:windowContentTransitions屬性開啟窗口內(nèi)內(nèi)容過渡效果。也可以在樣式定義中第一進(jìn)入,退出,共享元素的效果:

<style name="BaseAppTheme" parent="android:Theme.Material">
  <!-- enable window content transitions -->
  <item name="android:windowContentTransitions">true</item>

  <!-- specify enter and exit transitions -->
  <item name="android:windowEnterTransition">@transition/explode</item>
  <item name="android:windowExitTransition">@transition/explode</item>

  <!-- specify shared element transitions -->
  <item name="android:windowSharedElementEnterTransition">
    @transition/change_image_transform</item>
  <item name="android:windowSharedElementExitTransition">
    @transition/change_image_transform</item>
</style>

示例中的過渡change_image_transform定義如下:

<!-- res/transition/change_image_transform.xml -->
<!-- (see also Shared Transitions below) -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
  <changeImageTransform/>
</transitionSet>

changeImageTransform元素對(duì)應(yīng)ChangeImageTransform類。更多信息,查看Transition的api指南。

在代碼中啟用窗口內(nèi)容過渡效果,使用Window.requestFeature()方法:

// inside your activity (if you did not enable transitions in your theme)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

// set an exit transition
getWindow().setExitTransition(new Explode());

在代碼中定義過渡效果,使用下面的方法,并傳一個(gè)Transition對(duì)象:

  • Window.setEnterTransition()
  • Window.setExitTransition()
  • Window.setSharedElementEnterTransition()
  • Window.setSharedElementExitTransition()

setExitTransition()setSharedElementExitTransition()方法為調(diào)用的activity定義退出過渡效果,setEnterTransition()setSharedElementEnterTransition()方法為調(diào)用的activity定義進(jìn)入過渡效果。

為了達(dá)到完整的過渡效果,必須在進(jìn)入的和退出的兩個(gè)activity上都啟用window內(nèi)容過渡。否則,正調(diào)用的activity會(huì)開始退出過渡,你就會(huì)看到窗口過渡效果(比如縮放,或者淡出)。

更快的開始一個(gè)進(jìn)入過渡,使用Window.setAllowEnterTransitionOverlap()方法在被調(diào)用的activity。這讓你有更加激動(dòng)人心的進(jìn)入過渡效果。

打開activity使用過渡

如果你為一個(gè)activity開啟過渡并且設(shè)置了一個(gè)退出過渡效果,過渡效果會(huì)在你打開其他activity的時(shí)候激活,像這樣:

startActivity(intent,
        ActivityOptions.makeSceneTransitionAnimation(this).toBundle());

如果你給第二個(gè)activity設(shè)置了進(jìn)入過渡動(dòng)畫,過渡也會(huì)在第二個(gè)activity啟動(dòng)的時(shí)候激活。當(dāng)你啟動(dòng)其他的activity時(shí),如果需要禁用過渡效果,提供一個(gè)為null的bundle選項(xiàng)。

打開一個(gè)activity包含一個(gè)共享元素

使用一個(gè)場(chǎng)景過渡動(dòng)畫在兩個(gè)activity之間包括一個(gè)共享元素:

  1. 在theme中開啟窗口內(nèi)容過渡效果
  2. 在style中指定一個(gè)共享元素過渡效果
  3. 在xml中定義過渡樣式
  4. 在兩個(gè)activity的樣式文件中給共享元素分配一個(gè)相同的名字使用android:transitionName屬性
  5. 使用ActivityOptions.makeSceneTransitionAnimation()方法。
// get the element that receives the click event
final View imgContainerView = findViewById(R.id.img_container);

// get the common element for the transition in this activity
final View androidRobotView = findViewById(R.id.image_small);

// define a click listener
imgContainerView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, Activity2.class);
        // create the transition animation - the images in the layouts
        // of both activities are defined with android:transitionName="robot"
        ActivityOptions options = ActivityOptions
            .makeSceneTransitionAnimation(this, androidRobotView, "robot");
        // start the new activity
        startActivity(intent, options.toBundle());
    }
});

對(duì)于在代碼中生成的動(dòng)態(tài)共享視圖,使用`View.setTransitionName()方法在兩個(gè)activity中給指定相同的名字。

當(dāng)完成第二個(gè)activity的時(shí)候,如果需要逆轉(zhuǎn)該過渡動(dòng)畫,使用Activity.finishAfterTransition()方法代替Activity.finish()

打開一個(gè)activity包含多個(gè)共享元素

使用一個(gè)場(chǎng)景過渡動(dòng)畫在兩個(gè)activity之間包括多于一個(gè)共享元素,在兩個(gè)activity中定義所有的的共享元素使用android:transitionName屬性(或使用View.setTransitionName()方法在所有的activity中),并且創(chuàng)建一個(gè)像下面這樣的ActivityOptions對(duì)象:

ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
        Pair.create(view1, "agreedName1"),
        Pair.create(view2, "agreedName2"));

使用曲線運(yùn)動(dòng)

Material Design中,動(dòng)畫依賴時(shí)間插值和空間移動(dòng)模式曲線。在android5.0(api 21)和更高版本,你可以為動(dòng)畫自定義時(shí)間曲線和移動(dòng)曲線。

PathInterpolator類是一個(gè)新的基于貝塞爾曲線或Path對(duì)象的插值器。這個(gè)插值器在1*1的正方形上定義了曲線運(yùn)動(dòng),以(0,0)和(1,1)點(diǎn)作為錨點(diǎn),根據(jù)夠照參數(shù)控制點(diǎn)。你也可以使用xml文件的定義一個(gè)路徑插值器,如:

<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:controlX1="0.4"
    android:controlY1="0"
    android:controlX2="1"
    android:controlY2="1"/>

Material Design設(shè)計(jì)規(guī)范中,系統(tǒng)提供了三個(gè)基本曲線的xml資源:

  • @interpolator/fast_out_linear_in.xml
  • @interpolator/fast_out_slow_in.xml
  • @interpolator/linear_out_slow_in.xml

我們可以給Animator.setInterpolator()傳一個(gè)PathInterpolator對(duì)象來設(shè)置。

ObjectAnimator類有新的構(gòu)造方法,你可以一次使用兩個(gè)或者屬性使用path獨(dú)立于坐標(biāo)動(dòng)畫。比如,下面的動(dòng)畫使用一個(gè)Path對(duì)象去動(dòng)作一個(gè)視圖的x和y屬性:

ObjectAnimator mAnimator;
mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
...
mAnimator.start();

視圖狀態(tài)改變動(dòng)畫

StateListAnimator定義動(dòng)畫當(dāng)視圖的狀態(tài)改變的時(shí)候運(yùn)行,下面的例子是怎么在xml中定義一個(gè)StateListAnimator動(dòng)畫:

<!-- animate the translationZ property of a view when pressed -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_pressed="true">
    <set>
      <objectAnimator android:propertyName="translationZ"
        android:duration="@android:integer/config_shortAnimTime"
        android:valueTo="2dp"
        android:valueType="floatType"/>
        <!-- you could have other objectAnimator elements
             here for "x" and "y", or other properties -->
    </set>
  </item>
  <item android:state_enabled="true"
    android:state_pressed="false"
    android:state_focused="true">
    <set>
      <objectAnimator android:propertyName="translationZ"
        android:duration="100"
        android:valueTo="0"
        android:valueType="floatType"/>
    </set>
  </item>
</selector>

給視圖附加自定義的視圖狀態(tài)動(dòng)畫,使用selector元素在xml文件中定義一個(gè)動(dòng)畫祥例子中這樣,給視圖分配動(dòng)畫使用android:stateListAnimator屬性。在代碼中使用,使用AnimationInflater.loadStateListAnimator()方法,并且使用View.setStateListAnimator()方法。

當(dāng)你的主題是繼承的Material主題,按鈕默認(rèn)有一個(gè)Z動(dòng)畫。如果需要避免這個(gè)動(dòng)畫,設(shè)置android:stateListAnimator屬性為@null即可。

AnimatedStateListDrawable類讓你創(chuàng)建可繪制圖在相關(guān)聯(lián)的視圖狀態(tài)改變。android5.0的一些系統(tǒng)組件默認(rèn)使用這些動(dòng)畫。下面的例子是如何在xml文件中定義一個(gè)AnimatedStateListDrawable:

<!-- res/drawable/myanimstatedrawable.xml -->
<animated-selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- provide a different drawable for each state-->
    <item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
        android:state_pressed="true"/>
    <item android:id="@+id/focused" android:drawable="@drawable/drawableF"
        android:state_focused="true"/>
    <item android:id="@id/default"
        android:drawable="@drawable/drawableD"/>

    <!-- specify a transition -->
    <transition android:fromId="@+id/default" android:toId="@+id/pressed">
        <animation-list>
            <item android:duration="15" android:drawable="@drawable/dt1"/>
            <item android:duration="15" android:drawable="@drawable/dt2"/>
            ...
        </animation-list>
    </transition>
    ...
</animated-selector>

可繪矢量動(dòng)畫

可繪制矢量圖在拉伸時(shí)不會(huì)失真。AnimatedVectorDrawable類讓你可以在可繪制矢量圖上面作用動(dòng)畫。

通常需要在三個(gè)xml文件中定義可動(dòng)的矢量圖:

一個(gè)矢量圖使用<vector>元素,放在res/drawable/下。
一個(gè)可動(dòng)的矢量圖使用<animated-vector>元素,放在res/drawable/下。
一個(gè)或更多個(gè)動(dòng)畫對(duì)象使用<objectAnimator>元素,放在res/anim/下。

可動(dòng)矢量圖可以使用<group><path>元素。<group>元素定義一系列路徑或者子組,<path>元素定義可繪圖的路徑。

當(dāng)你定義了一個(gè)想要作用動(dòng)畫的矢量可繪制圖,使用android:name屬性給每個(gè)group和path指定一個(gè)唯一的名字,這樣你可以從動(dòng)畫的定義中找到他們。比如:

<!-- res/drawable/vectordrawable.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="64dp"
    android:width="64dp"
    android:viewportHeight="600"
    android:viewportWidth="600">
    <group
        android:name="rotationGroup"
        android:pivotX="300.0"
        android:pivotY="300.0"
        android:rotation="45.0" >
        <path
            android:name="v"
            android:fillColor="#000000"
            android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
    </group>
</vector>

可動(dòng)的矢量繪制通過剛剛說到定義的名字,來找到這些path和group:

<!-- res/drawable/animvectordrawable.xml -->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:drawable="@drawable/vectordrawable" >
    <target
        android:name="rotationGroup"
        android:animation="@anim/rotation" />
    <target
        android:name="v"
        android:animation="@anim/path_morph" />
</animated-vector>

動(dòng)畫的定義表現(xiàn)在ObjectAnimator和AnimatorSet對(duì)象中。第一個(gè)動(dòng)畫在這個(gè)例子中是讓目標(biāo)組旋轉(zhuǎn)360度:

<!-- res/anim/rotation.xml -->
<objectAnimator
    android:duration="6000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360" />

第二個(gè)動(dòng)畫例子是把矢量可繪圖從一個(gè)形狀變成另一種。所有的路徑必須兼容變換:他們必須有相同數(shù)量的命令,每個(gè)命令要有相同的參數(shù)。

<!-- res/anim/path_morph.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="3000"
        android:propertyName="pathData"
        android:valueFrom="M300,70 l 0,-70 70,70 0,0   -70,70z"
        android:valueTo="M300,70 l 0,-70 70,0  0,140 -70,0 z"
        android:valueType="pathType" />
</set>

更多的信息,看AnimatedVectorDrawable的api指南。

PS后記

這個(gè)系列終于寫完了,說實(shí)話基本上大部分都是翻譯的谷歌的官方文檔。因?yàn)闀r(shí)間問題,再加上自己的英語夠爛,最近越來慢。不過,這樣一下來,加上自己的一些代碼練習(xí),對(duì)于Material設(shè)計(jì)算是能夠基本使用了。可惜,大部分的style還都不能向下兼容,只好等5了。

網(wǎng)上有一些大神進(jìn)來已經(jīng)開源了一些開源組件,大家可以借此曲線救國,下次有空在專門整理一下。

本文參考: http://developer.android.com/training/material/animations.html

原文地址:http://blog.isming.me/2014/11/13/creating-app-with-material-design-five-animations/,轉(zhuǎn)載請(qǐng)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容