最近在看一些關于Material Design的東西,還記得在博客[《你所不知道的Activity轉場動畫——ActivityOptions》](http://blog.csdn.net/qibin0506/article/details/48129139)中,我們介紹了一種優雅的activity過度動畫。如果大家看了最后給出的參考鏈接,會發現還有很多內容是值得我們學習的,所以這篇博客,我們來學習一下這一頁上剩下的東西。
### 一、觸摸反饋
大家都知道,在Material Design中,觸摸反饋的效果非常絢麗,是一種漣漪的效果,令我們高興的是,這種效果也是可以自定義的。
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:dividerPadding="20dp"
tools:context=".MainActivity">
<Button
android:background="?android:attr/selectableItemBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:background="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</LinearLayout>
~~~
上面的代碼,我們定義了兩個Button,不同的是它們的background屬性,`selectableItemBackground`代表了`當點擊后會在當前Button區域發生一個漣漪效果`,而`selectableItemBackgroundBorderless`的效果`不會局限于Button本身的大小`。當然,雖然是大白話,但是還是不容易理解,我們來看看效果就一目了然了。

恩,效果很贊,但是這個背景能不能自定義呢?答案是當然能了,那么這里要引進一個新的`Drawable`-`RippleDrawable`了。`RippleDrawable`是android5新增加的一種`Drawable`,它的效果就是我們一直在提及的漣漪效果,下面我們來學習一下`RippleDrawable`的時候。既然`RippleDrawable`也是一種`Drawable`,那么它肯定也是可以在xml中定義的,來看代碼,
~~~
<?xml version="1.0" encoding="utf-8"?>
<!-- drawable/ripple_no_mask.xml-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#FF00FF00">
<item android:drawable="@mipmap/ic_launcher" />
</ripple>
<?xml version="1.0" encoding="utf-8"?>
<!-- drawable/ripple_mask.xml-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#FF00FF00">
<item android:id="@android:id/mask" android:drawable="@mipmap/ic_launcher" />
</ripple>
~~~
我們在drawable目錄中創建兩個xml文件,這兩個drawable都是ripple類型的,可以看到他們的根節點都是一個ripple。這兩個文件唯一的區別在于第二個的id指定了一個id是`@android:id/mask`,這兩者的區別在于,
> 如果不指定id為`@android:id/mask`,那么在顯示的時候會首頁顯示出item指定的drawable。
如果指定id為`@android:id/mask`,那么默認是不會顯示該drawable,而是在點擊的時候出現。
如何使用呢? 只需要指定控件的background就ok.
~~~
<Button
android:background="@drawable/ripple_no_mask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:background="@drawable/ripple_mask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
~~~
看一下效果怎么樣,

通過觀察效果,我們可以發現,
> 1. 漣漪的效果是在我們給item的drawable的非透明區域發生的。
> 1. 當我們指定item的id為`@android:id/mask`時,默認背景是不顯示的。
ok,到這里,我們已經學會自定義點擊的漣漪效果了。我們繼續學習,接下來就來到了一個更贊的效果。
### 二、Reveal Effect 顯示效果
新的sdk給我們提供了一個類-`ViewAnimationUtils`,該類就提供了一個靜態的方法`createCircularReveal`,通過這個方法,我們可以給任何一個layout或者view一個漣漪的顯示或者消失過程。首先我們來看看要實現的效果,

效果確實很贊,代碼應該怎么寫呢? 其實也很簡單,主要還是我們將要學習的`ViewAnimationUtils`和它唯一的方法`createCircularReveal`,
~~~
public void change(View view) {
int centerX = mImageView.getWidth() / 2;
int centerY = mImageView.getHeight() / 2;
int maxRadius = Math.max(mImageView.getWidth(), mImageView.getHeight());
if(mImageView.getVisibility() == View.VISIBLE) {
Animator anim = ViewAnimationUtils.createCircularReveal(mImageView,
centerX, centerY, maxRadius, 0);
anim.setDuration(1000);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mImageView.setVisibility(View.GONE);
}
});
anim.start();
}else {
Animator anim = ViewAnimationUtils.createCircularReveal(mImageView,
centerX, centerY, 0, maxRadius);
anim.setDuration(1000);
mImageView.setVisibility(View.VISIBLE);
anim.start();
}
}
~~~
在解釋代碼之前,我們來看看這個方法的詳細說明:
> public static Animator createCircularReveal (View view, int centerX, int centerY, float startRadius, float endRadius)
參數部分,
> 1. View view, 指定了為哪個View執行動畫
> 1. int centerX, 漣漪效果的中心x軸位置
> 1. int centerY, 漣漪效果的中心y軸位置
> 1. float startRadius, 開始的半徑
> 1. float endRadius, 結束的半徑
在解釋完了參數之后,發現也不難,那么下面我們就來看上面的代碼了。
首先,我們計算了centerX、centerY,還有一個最大的半徑,最小的半徑就不需要計算了,因為我們知道我們需要的效果就是到0.
接下來一個判斷是判斷ImageView的顯示狀態,如果為顯示狀態,那么肯定就是一個從有到無的動畫,如果是隱藏狀態,那么就是一個從無到有的動畫。
第一個狀態中,我們首先調用`ViewAnimationUtils.createCircularReveal()`方法創建了一個`Animator`,半徑是從`maxRadius`到0的變化。
然后就是對`Animator`的操作了,并且監聽了動畫的結束,在動畫結束后,我們會將該View的狀態設置為隱藏,最后啟動動畫,這里就是我們剛才看到的那個隱藏的效果了。
那么顯示呢?其實和隱藏是一樣的,只不過顯示半徑是從0到`maxRadius`。
### 三、Activity過度動畫
這一部分我們在前面的博客[《你所不知道的Activity轉場動畫——ActivityOptions》](http://blog.csdn.net/qibin0506/article/details/48129139)
中介紹過了,大家可以去參考這篇博客,這里就不再重復介紹了。
### 四、路徑動畫
什么是路徑動畫? 其實在官方文檔上,這一節叫自作`Curved Motion`,我把它叫做路徑動畫,是因為這一節的內容的核心是基于`Path`實現的。
Android 5 的sdk中給我們提供了一個新的插值器`PathInterpolator`, 利用該插值器,我們可以輕松的定義出路徑動畫,
~~~
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="0.4"
android:controlY1="0"
android:controlX2="1"
android:controlY2="1"/>
~~~
這段代碼定義了一個x和y分別從0.4和0到1的動畫,不過我認為這種方式局限性太大,所以這里不過多介紹,重點我們放在在java代碼中如果利用Path去構建一個路徑動畫。
在新的sdk中,`ObjectAnimator`多了很多帶有Path參數的方法,這些方法中我們可以傳遞一個`Path`對象,然后我們就可以在動畫中獲取基于這個`Path`的值。
> public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName, Path path);
這個構造中需要我們傳遞兩個屬性值,這里對象這Path中x和y, 最后一個參數就是最重要的那個路徑了。來看看我們的demo,這個demo,我們想讓一段文字沿著一個`Z`的路徑移動,最后還原到原來的位置。
~~~
TextView tv = (TextView) findViewById(R.id.tv);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Path path = new Path();
path.moveTo(100, 100);
path.lineTo(600, 100);
path.lineTo(100, 600);
path.lineTo(600, 600);
path.close();
ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.X, View.Y, path);
anim.setDuration(5000);
anim.start();
}
});
~~~
代碼很簡單,首先我們構建了一個簡單的`Path`,然后利用`ObjectAnimator`的新的`ofFloat`方法為為該View的x和y指定了沿path移動,最后讓動畫跑起來。

### 五、View狀態動畫
在之前,我們在使用`selector`定義狀態時,僅僅只能提供一個生硬的狀態變化,在新的sdk中這里發生了改變,我們完全可以定義一個帶動畫效果的狀態!
首先我們定義一個drawable文件,
~~~
<?xml version="1.0" encoding="utf-8"?>
<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:valueFrom="0"
android:valueTo="20dp"
android:valueType="floatType"/>
</set>
</item>
<item>
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="100"
android:valueTo="0"
android:valueType="floatType"/>
</set>
</item>
</selector>
~~~
還是我們熟悉的`selector`,不過它的item是一個`objectAnimator`, 這里我們指定了`translationZ`的變化,然后我們在布局中使用它,
~~~
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:stateListAnimator="@drawable/view_state"
android:text="@string/hello_world" />
~~~
這里我們引入了一個新的屬性`android:stateListAnimator`,我們通過這個屬性來設置我們剛才定義的`selector`, 來看看效果,

仔細觀察效果,我們在點擊的過程中Button的`translationZ`有一個動畫的效果。
說到這里,如果我們可以給它的背景一個動畫就更爽了, 當然這也是可以辦到的!首先還是定義一個drawablew文件,不過這里的文件是一個`AnimatedStateListDrawable`,我們在xml中使用`animated-selector`。
~~~
<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/press"
android:state_pressed="true"
android:drawable="@android:color/holo_blue_dark" />
<item
android:id="@+id/focus"
android:state_focused="true"
android:drawable="@android:color/holo_purple" />
<item
android:id="@+id/normal"
android:drawable="@android:color/holo_red_dark" />
<transition
android:fromId="@id/normal"
android:toId="@id/press">
<animation-list>
<item android:duration="200" android:drawable="@android:color/holo_red_dark" />
<item android:duration="200" android:drawable="@android:color/holo_blue_dark" />
</animation-list>
</transition>
</animated-selector>
~~~
在這里,我們首先定義了三個item, 對應了三種狀態,并且都制定了id, 接下來是一個`transition`節點,這個節點下我們制定了`fromId`和`toId`,這里代表了動畫從哪個狀態到哪個狀態執行,這個節點下又是一個`animation-list`節點,我們指定了兩個`item`,這里要注意一下這兩個`item`的執行順序,如果是從`fromId`到`toId`,這里是從正常狀態到按下,則會正序執行,如果從`toId`到`fromId`,這里是從按下狀態到正常,則會反序執行。這里我們給每個item 200ms的停留時間。

從效果中可能看不出我何時點擊何時松開的,這里每個顏色都會有一段停留時間的。
### 六、 SVG矢量動畫
在新的sdk中, Google終于提供了原生的對SVG的支持,而這一切都是一個`VectorDrawable`的功勞, 當然, `VectorDrawable`也是一個`Drawable`,
所以我們還是可以在xml中定義它,
~~~
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportHeight="600"
android:viewportWidth="600">
<group
android:name="group"
android:pivotX="300"
android:pivotY="300"
android:rotation="0">
<path
android:name="path"
android:fillColor="#FFFF0000"
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z"/>
</group>
</vector>
~~~
這里我們定義了一個`drawable`文件,它的根節點是一個`vector`,代表著它是一個`VectorDrawable`,看看它的內容,又一個`group`和多個`path`組成,一個`group`可以包含多個`path`,當然這里我們僅僅寫了一個,值得注意的是`path`的`android:pathData`屬性,這里定義了我們圖形的形狀。怎么使用呢? 就像正常的圖片一樣,給`ImageView`的`src`屬性賦值就ok。 這是一個什么形狀呢?

當然,我們可以借助一些工具很輕松的實現一些`pathData`。
好了, 現在僅僅是一個簡單的圖形,并沒有動畫效果, 現在我們想讓這個圖形動起來,而且顏色也要不斷變化,該怎么做?這里又要引入一個新的東西-`animated-vector`。
利用它,我們可以根據上面書寫的name值指定不同的動畫了。
首先,我們定義兩個動畫,一個是旋轉的動畫,一個是顏色值變化的動畫。
~~~
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType"/>
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:propertyName="fillColor"
android:valueFrom="#FFFF0000"
android:valueTo="#FF000000"
android:valueType="intType"/>
~~~
這里我們定義了兩個`Animator`,需要注意的是他的`android:propertyName`的值, 仔細觀察一下,他們分別就是上面我們定義的那個`vector`的`group`
和`path`標簽的其中一個屬性,這兩個動畫分別是一個旋轉和顏色變化的動畫, 那怎么用呢? 我們還需要一個`animated-vector`的`drawable`文件。
~~~
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/svg">
<target
android:animation="@animator/rotation"
android:name="group" />
<target
android:animation="@animator/color"
android:name="path" />
</animated-vector>
~~~
在這個文件中,我們指定了`drawable`為我們先前定義的那個`vector`文件, 并且寫了兩個`target`標簽,這里很簡單,只有兩個屬性,他們分別指定了所用的動畫和這個動畫該給誰(還記得我們之前給`group`和`path`指定的`name`屬性嗎)! ok, 現在將`ImageView`的`src`指定為這個`animated-vector`文件, 運行一下,你可能會感到沮喪,因為沒有任何動畫! 那怎么讓動畫跑起來呢? 很簡單。
~~~
final ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Drawable d = iv.getDrawable();
if(d instanceof Animatable) ((Animatable) d).start();
}
});
~~~
我們在點擊`ImageView`的時候,去獲取這個`ImageView`的圖片資源,然后判斷它是不是`Animatable`類型的,如果是,直接調用它的`start`方法!
最后,我們來看看這時候的效果。

有一個旋轉的動畫, 在旋轉的過程中還伴隨著顏色的變化,棒棒噠。 ok, 到這里,Material Design動畫,我們就學習完了
最后是demo的代碼下載和參考資料。
[demo代碼下載,戳這里](http://download.csdn.net/detail/qibin0506/9172775)
參考資料:[https://developer.android.com/training/material/animations.html](https://developer.android.com/training/material/animations.html)
- 前言
- 高逼格UI-ASD(Android Support Design)
- AndroidSupportDesign之TabLayout使用詳解
- RecyclerView的高級用法——定制動畫
- Android官方數據綁定框架DataBinding(一)
- Android官方數據綁定框架DataBinding(二)
- 你所不知道的Activity轉場動畫——ActivityOptions
- RecyclerView+ImageLoader打造多選圖庫
- Android Material Design動畫
- RecyclerView添加Header的正確方式
- CoordinatorLayout高級用法-自定義Behavior