Android 自定義動(dòng)畫

2018-08-02 18:20 更新

編寫: allenlsy - 原文: https://developer.android.com/training/material/animations.html

Material Design中的動(dòng)畫對(duì)用戶的動(dòng)作進(jìn)行反饋,并提供在整個(gè)交互過程中的視覺連續(xù)性。Material 主題為按鈕和Activity切換提供一些默認(rèn)的動(dòng)畫,Android 5.0 (API level 21) 及以上版本支持自定義這些動(dòng)畫并創(chuàng)建新動(dòng)畫:

  • 觸摸反饋
  • 圓形填充
  • Activity 切換動(dòng)畫
  • 曲線形動(dòng)作
  • 視圖狀態(tài)變換

自定義觸摸反饋

Material Design中的觸摸反饋,是在用戶與UI元素交互時(shí),提供視覺上的即時(shí)確認(rèn)。按鈕的默認(rèn)觸摸反饋動(dòng)畫使用了新的RippleDrawable類,它在按鈕狀態(tài)變換時(shí)產(chǎn)生波紋效果。

大多數(shù)情況下,你需要在你的 XML 文件中設(shè)定視圖的背景來實(shí)現(xiàn)這個(gè)功能:

  • ?android:attr/selectableItemBackground 用于有界Ripple動(dòng)畫
  • ?android:attr/selectableItemBackgroundBorderless 用于越出視圖邊界的動(dòng)畫。它會(huì)被繪制在最近的且不是全屏的父視圖上。

Note:selectableItemBackgroundBorderless 是 API level 21 新加入的屬性

另外,你可以使用ripple元素在XML資源文件中定義一個(gè) RippleDrawable

你可以給RippleDrawable賦予一個(gè)顏色。要改變默認(rèn)的觸摸反饋顏色,使用主題的android:colorControlHighlight 屬性。

更多信息,參見RippleDrawable類的API文檔。

使用填充效果(Reveal Effect)

填充效果在UI元素出現(xiàn)或隱藏時(shí),為用戶提供視覺連續(xù)性。ViewAnimationUtils.createCircularReveal()方法可以使用一個(gè)附著在視圖上的圓形,顯示或隱藏這個(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 = myView.getWidth();

// create and start the animator for this view
// (the start radius is zero)
Animator anim =
    ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
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切換效果

Figure 1 - A transition with shared elements.

Material Design中的Activity切換,當(dāng)不同Activity之間擁有共有元素,則可以通過不同狀態(tài)之間的動(dòng)畫和形變提供視覺上的連續(xù)性。你可以為共有元素設(shè)定進(jìn)入和退出Activity時(shí)的自定義動(dòng)畫。

  • 入場(chǎng)變換決定視圖如何入場(chǎng)。比如,在爆炸式入場(chǎng)變換中,視圖從場(chǎng)外飛到屏幕中央。
  • 出場(chǎng)變換決定視圖如何退出。比如,在爆炸式出場(chǎng)變換中,視圖從屏幕中央飛出場(chǎng)外。
  • 共有元素的變換決定一個(gè)共有視圖在兩個(gè)Activity之間如何變換。比如,如果兩個(gè)activity有同一張圖片,但是放在不同位置,以及擁有不同大小,變更圖片 變換會(huì)流暢的把圖片移到相應(yīng)位置,同時(shí)縮放圖片大小。

Android 5.0 (API level 21) 支持這些入場(chǎng)和退出變換:

  • 爆炸 - 把視圖移入或移出場(chǎng)景的中間
  • 滑動(dòng) - 把視圖從場(chǎng)景邊緣移入或移出
  • 淡入淡出 - 通過改變透明度添加或移除元素

任何繼承于 Visibility 類的變換,都支持被用于入場(chǎng)或退出變換。更多信息,請(qǐng)參見 Transition 類的API文檔。

Android 5.0 (API level 21) 還支持這些共有元素變換效果:

  • changeBounds - 對(duì)目標(biāo)視圖的外邊界進(jìn)行動(dòng)畫
  • chagneClipBounds - 對(duì)目標(biāo)視圖的附著物的外邊界進(jìn)行動(dòng)畫
  • changeTransform - 對(duì)目標(biāo)視圖進(jìn)行縮放和旋轉(zhuǎn)
  • changeImageTransform - 對(duì)目標(biāo)圖片進(jìn)行縮放

當(dāng)你在應(yīng)用中進(jìn)行activity 變換時(shí),默認(rèn)的淡入淡出效果會(huì)被用在進(jìn)入和退出activity的過程中。

自定義切換

首先,當(dāng)你繼承Material主題的style時(shí),要通過android:windowContentTransitions屬性來開啟窗口內(nèi)容變換功能。你也可以在style定義中聲明進(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 類。更多信息,請(qǐng)參見 Transition類的API文檔。

要在代碼中啟用窗口內(nèi)容切換,調(diào)用Window.requestFeature()函數(shù):

// 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());

要聲明變換類型,就要在Transition對(duì)象上調(diào)用以下函數(shù):

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

setExitTransition() 和 setSharedElementExitTransition() 函數(shù)為activity定義了退出變換效果。setEnterTransition() 和 setSharedElementEnterTransition() 函數(shù)定義了進(jìn)入activity的變換效果。

要獲得切換的全部效果,你必須在出入的兩個(gè)activity中都開啟窗口內(nèi)容切換。否則,調(diào)用的activity會(huì)使用退出效果,但是接著你會(huì)看到一個(gè)傳統(tǒng)的窗口切換(比如縮放或淡入淡出)。

要盡早開始入場(chǎng)切換,可以在被調(diào)用的Activity上使用Window.setAllowEnterTransitionOverlap() 。它可以使你擁有更戲劇性的入場(chǎng)切換。

使用切換啟動(dòng)一個(gè)Activity

如果你開啟Activity入場(chǎng)和退出效果,那么當(dāng)你在用如下方法開始Activity時(shí),切換效果會(huì)被應(yīng)用:

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

如果你為第二個(gè)Activity設(shè)定了入場(chǎng)變換,變換也會(huì)在activity開始時(shí)被啟用。要在開始另一個(gè)acitivity時(shí)禁用變換,可以給bundle的選項(xiàng)提供一個(gè)null對(duì)象:

啟動(dòng)一個(gè)擁有共用元素的Activity

要在兩個(gè)擁有共用元素的activity間進(jìn)行切換動(dòng)畫:

  1. 在主題中開啟窗口內(nèi)容切換
  2. 在style中定義共有元素切換
  3. 將切換定義為一個(gè)XML 資源文件
  4. 使用android:transitionName屬性在兩個(gè)layout文件中給共有元素賦予同一個(gè)名字
  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中定義共有元素。

要在第二個(gè)activity結(jié)束時(shí)進(jìn)行逆向的場(chǎng)景切換動(dòng)畫,調(diào)用Activity.finishAfterTransition()方法,而不是Activity.finish()。

開始一個(gè)擁有多個(gè)共有元素的Activity

要在擁有多個(gè)共有元素的activity之間使用變換動(dòng)畫,就要用android:transitionName屬性在兩個(gè)layout中定義這個(gè)共有元素(或在兩個(gè)Activity中使用View.setTransitionName()方法),再創(chuàng)建ActivityOptions對(duì)象:

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

使用曲線動(dòng)畫

Material Design中的動(dòng)畫可以表示為基于時(shí)間插值和空間移動(dòng)模式的曲線。在Android 5.0 (API level 21)以上版本中,你可以為動(dòng)畫定義時(shí)間曲線和曲線動(dòng)畫模式。

PathInterpolator類是一個(gè)基于貝澤爾曲線或Path對(duì)象的新的插值方法。插值方法 是一個(gè)定義在 1x1 正方形中的曲線函數(shù)圖像,其始末兩點(diǎn)分別在(0,0)和(1,1),一個(gè)用構(gòu)造函數(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標(biāo)準(zhǔn)中,系統(tǒng)提供了三種基本的曲線:

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

你可以將一個(gè)PathInterpolator對(duì)象傳給Animator.setInterpolator()方法。

ObjectAnimator類有一個(gè)新的構(gòu)造函數(shù),使你可以沿一條路徑使用多個(gè)屬性來在坐標(biāo)系中進(jìn)行變換。比如,以下animator(動(dòng)畫器,譯者注)使用一個(gè)Path對(duì)象來改變一個(gè)試圖的X和Y屬性:

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

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

StateListAnimator 類是你可以定義在視圖狀態(tài)改變啟動(dòng)的Animator(動(dòng)畫器,譯者注)。以下例子展示如何在XML文件中定義StateListAnimator

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

要把視圖改變Animator關(guān)聯(lián)到一個(gè)視圖,就要在XML資源文件的selector元素上定義一個(gè)Animator,并把此Animator賦值給視圖的 android:stateListAnimator 屬性。要想在Java代碼中將狀態(tài)列表Animator賦值給視圖,使用AnimationInflater.loadStateListAnimator() 函數(shù),并用View.setStateListAnimator()函數(shù)把Animator賦值給你的視圖。

當(dāng)你的主題繼承于Material Theme的時(shí)候,Button默認(rèn)會(huì)有一個(gè)Z值動(dòng)畫。為了避免Button的Z值動(dòng)畫,設(shè)定它的android:stateListAnimator屬性為@null。

AnimatedStateListDrawable類使你可以創(chuàng)建一個(gè)在視圖狀態(tài)變化之間顯示動(dòng)畫的drawable。有一些Android 5.0系統(tǒng)組件默認(rèn)已經(jīng)使用了這些動(dòng)畫。下面的例展示如何在XML資源文件中定義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)畫矢量 Drawables

矢量Drawable是可以無(wú)損縮放的。AnimatedVectorDrawable類是你可以操作矢量Drawable。

你通常在3個(gè)XML文件中定義動(dòng)畫矢量Drawable:

  • res/drawable/中用<vector>定義一個(gè)矢量drawable
  • res/drawable/中用<animated-vector>定義一個(gè)動(dòng)畫矢量drawable
  • 在`res/anim/'中定義一個(gè)或多個(gè)Animator

動(dòng)畫矢量drawable可以用在<group><path>元素的屬性上。<group>元素定義了一些path或者subgroup,<path>定義了一條被繪畫的路徑。

當(dāng)你想要定義一個(gè)動(dòng)畫的矢量drawable時(shí),使用android:name 屬性來為group和path賦值一個(gè)唯一的名字(name),這樣你可以通過animator的定義找到他們。比如:

<!-- 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)畫矢量drawable的定義是通過name屬性來找到視圖組(group)和路徑(path)的:

<!-- 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)畫的定義代表ObjectAnimator或者AnimatorSet對(duì)象。例子中第一個(gè)animator將目標(biāo)組旋轉(zhuǎn)了360度。

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

第二個(gè)animator將矢量drawable的路徑從一個(gè)形狀(morph)變形到另一個(gè)。兩個(gè)路徑都必須是可以形變的:他們必須有相同數(shù)量的命令,每個(gè)命令必須有相同數(shù)量的參數(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>

更多信息,請(qǐng)參考AnimatedVectorDrawable)的API指南。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)