**轉載請注明本文出自Cym的博客([http://blog.csdn.net/cym492224103](http://blog.csdn.net/cym492224103)**),謝謝支持!**
**
**
前言
用戶跟你的App進行交互時,Material Design中的動畫給予用戶動作的反饋和提供視覺的一致感。
包括之前我學習過的:
[Activity transitions(Activity過渡效果)](http://blog.csdn.net/cym492224103/article/details/41761267)
[Animate Vector Drawables(可繪矢量動畫)](http://blog.csdn.net/cym492224103/article/details/41677825)
除我們已經學習過的動畫之外,Material Design還給我們提供了什么動畫?
Touch feedback(觸摸反饋)
Reveal effect(揭露效果)
Curved motion(曲線運動)
View state changes?(視圖狀態改變)
**Touch feedback**(觸摸反饋)
當用戶與用戶界面進行交互時,materialdesign中的觸摸反饋在觸摸點上提供了一種瞬時視覺確認。按鈕的默認觸摸反饋動畫使用新的[RippleDrawable](http://developer.android.com/reference/android/graphics/drawable/RippleDrawable.html)類,它使用漣漪(波紋)效應在不同狀態間轉換。
在大多數情況下,你應該在你的布局XML文件中使用如下的方法去指定視圖的背景:
?android:attr/selectableItemBackground?(有界波紋)
?android:attr/selectableItemBackgroundBorderless?(無界波紋)
注意:selectableItemBackgroundBorderless是API級別21上的新屬性。
效果如下:

layout:
~~~
<Button android:layout_width="100dp" android:layout_height="100dp"
android:background="?android:attr/selectableItemBackground"
android:text="有界"
android:textColor="@android:color/white"
android:colorControlHighlight="@android:color/holo_red_dark"/>
<Button android:layout_width="100dp" android:layout_height="100dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:textColor="@android:color/white"
android:text="無界"/>
~~~
或者,你可以定義一個[RippleDrawable](http://developer.android.com/reference/android/graphics/drawable/RippleDrawable.html)作為波紋元素的XML資源
~~~
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/accent_dark">
<item>
<shape
android:shape="oval">
<solid android:color="?android:colorAccent" />
</shape>
</item>
</ripple>
~~~
你可以給RippleDrawable對象指定一種顏色。要更改默認的觸摸反饋顏色,使用主題的android:colorControlHighlight屬性**。**
**Reveal effect**(揭示效果)
使用[ViewAnimationUtils.createCircularReveal()](http://developer.android.com/reference/android/view/ViewAnimationUtils.html#createCircularReveal(android.view.View,)?方法

~~~
public Animator createAnimation(View v, Boolean isFirst) {
Animator animator;
if (isFirst) {
animator = ViewAnimationUtils.createCircularReveal(
v,// 操作的視圖
0,// 動畫開始的中心點X
0,// 動畫開始的中心點Y
v.getWidth(),// 動畫開始半徑
0);// 動畫結束半徑
} else {
animator = ViewAnimationUtils.createCircularReveal(
v,// 操作的視圖
0,// 動畫開始的中心點X
0,// 動畫開始的中心點Y
0,// 動畫開始半徑
v.getWidth());// 動畫結束半徑
}
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(500);
return animator;
}
static boolean isFirst = false;
@Override
public void onClick(View v) {
createAnimation(myView, isFirst).start();
isFirst = !isFirst;
}
~~~
**Curved motion**(曲線運動)
Material design中的動畫依靠曲線,這個曲線適用于時間插值器和控件運動模式。
[PathInterpolator](http://developer.android.com/reference/android/view/animation/PathInterpolator.html)類是一個基于貝塞爾曲線(Bézier curve)或路徑([Path](http://developer.android.com/reference/android/graphics/Path.html))對象上的新的插值器。
在materialdesign規范中,系統提供了三個基本的曲線:
@interpolator/fast_out_linear_in.xml
@interpolator/fast_out_slow_in.xml
@interpolator/linear_out_slow_in.xml
你可以傳遞一個[PathInterpolator](http://developer.android.com/reference/android/view/animation/PathInterpolator.html)對象給[Animator.setInterpolator()](http://developer.android.com/reference/android/animation/Animator.html#setInterpolator(android.animation.TimeInterpolator))方法。
[ObjectAnimator](http://developer.android.com/reference/android/animation/ObjectAnimator.html)類有了新的構造方法,使你能夠一次能同時使用兩個或多個屬性去繪制動畫的路徑。例如,下面的動畫使用一個Path對象進行視圖X和Y屬性的動畫繪制:
~~~
ObjectAnimator mAnimator;
mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
...
mAnimator.start();
~~~
在Android 5.0 提供的API Demos -》Animation/Path Animations 就有一個例子使用了曲線動畫:

Path Animations 源碼:
~~~
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.animation;
import android.animation.ObjectAnimator;
import android.animation.TypeConverter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.util.Property;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.RadioGroup;
import com.example.android.apis.R;
/**This application demonstrates the use of Path animation. */
public class PathAnimations extends Activity implements
RadioGroup.OnCheckedChangeListener, View.OnLayoutChangeListener {
final static Path sTraversalPath = new Path();
final static float TRAVERSE_PATH_SIZE = 7.0f;
final static Property<PathAnimations, Point> POINT_PROPERTY
= new Property<PathAnimations, Point>(Point.class, "point") {
@Override
public Point get(PathAnimations object) {
View v = object.findViewById(R.id.moved_item);
return new Point(Math.round(v.getX()), Math.round(v.getY()));
}
@Override
public void set(PathAnimations object, Point value) {
object.setCoordinates(value.x, value.y);
}
};
static {
float inverse_sqrt8 = FloatMath.sqrt(0.125f);
RectF bounds = new RectF(1, 1, 3, 3);
sTraversalPath.addArc(bounds, 45, 180);
sTraversalPath.addArc(bounds, 225, 180);
bounds.set(1.5f + inverse_sqrt8, 1.5f + inverse_sqrt8, 2.5f + inverse_sqrt8,
2.5f + inverse_sqrt8);
sTraversalPath.addArc(bounds, 45, 180);
sTraversalPath.addArc(bounds, 225, 180);
bounds.set(4, 1, 6, 3);
sTraversalPath.addArc(bounds, 135, -180);
sTraversalPath.addArc(bounds, -45, -180);
bounds.set(4.5f - inverse_sqrt8, 1.5f + inverse_sqrt8, 5.5f - inverse_sqrt8, 2.5f + inverse_sqrt8);
sTraversalPath.addArc(bounds, 135, -180);
sTraversalPath.addArc(bounds, -45, -180);
sTraversalPath.addCircle(3.5f, 3.5f, 0.5f, Path.Direction.CCW);
sTraversalPath.addArc(new RectF(1, 2, 6, 6), 0, 180);
}
private CanvasView mCanvasView;
private ObjectAnimator mAnimator;
/**Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.path_animations);
mCanvasView = (CanvasView) findViewById(R.id.canvas);
mCanvasView.addOnLayoutChangeListener(this);
((RadioGroup) findViewById(R.id.path_animation_type)).setOnCheckedChangeListener(this);
}
public void setCoordinates(int x, int y) {
changeCoordinates((float) x, (float) y);
}
public void changeCoordinates(float x, float y) {
View v = findViewById(R.id.moved_item);
v.setX(x);
v.setY(y);
}
public void setPoint(PointF point) {
changeCoordinates(point.x, point.y);
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
startAnimator(checkedId);
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
int checkedId = ((RadioGroup)findViewById(R.id.path_animation_type)).getCheckedRadioButtonId();
if (checkedId != RadioGroup.NO_ID) {
startAnimator(checkedId);
}
}
private void startAnimator(int checkedId) {
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
View view = findViewById(R.id.moved_item);
Path path = mCanvasView.getPath();
if (path.isEmpty()) {
return;
}
switch (checkedId) {
case R.id.named_components:
// Use the named "x" and "y" properties for individual (x, y)
// coordinates of the Path and set them on the view object.
// The setX(float) and setY(float) methods are called on view.
// An int version of this method also exists for animating
// int Properties.
mAnimator = ObjectAnimator.ofFloat(view, "x", "y", path);
break;
case R.id.property_components:
// Use two Properties for individual (x, y) coordinates of the Path
// and set them on the view object.
// An int version of this method also exists for animating
// int Properties.
mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
break;
case R.id.multi_int:
// Use a multi-int setter to animate along a Path. The method
// setCoordinates(int x, int y) is called on this during the animation.
// Either "setCoordinates" or "coordinates" are acceptable parameters
// because the "set" can be implied.
mAnimator = ObjectAnimator.ofMultiInt(this, "setCoordinates", path);
break;
case R.id.multi_float:
// Use a multi-float setter to animate along a Path. The method
// changeCoordinates(float x, float y) is called on this during the animation.
mAnimator = ObjectAnimator.ofMultiFloat(this, "changeCoordinates", path);
break;
case R.id.named_setter:
// Use the named "point" property to animate along the Path.
// There must be a method setPoint(PointF) on the animated object.
// Because setPoint takes a PointF parameter, no TypeConverter is necessary.
// In this case, the animated object is PathAnimations.
mAnimator = ObjectAnimator.ofObject(this, "point", null, path);
break;
case R.id.property_setter:
// Use the POINT_PROPERTY property to animate along the Path.
// POINT_PROPERTY takes a Point, not a PointF, so the TypeConverter
// PointFToPointConverter is necessary.
mAnimator = ObjectAnimator.ofObject(this, POINT_PROPERTY,
new PointFToPointConverter(), path);
break;
}
mAnimator.setDuration(10000);
mAnimator.setRepeatMode(Animation.RESTART);
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.start();
}
public static class CanvasView extends FrameLayout {
Path mPath = new Path();
Paint mPathPaint = new Paint();
public CanvasView(Context context) {
super(context);
init();
}
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CanvasView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setWillNotDraw(false);
mPathPaint.setColor(0xFFFF0000);
mPathPaint.setStrokeWidth(2.0f);
mPathPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
Matrix scale = new Matrix();
float scaleWidth = (right-left)/TRAVERSE_PATH_SIZE;
float scaleHeight= (bottom-top)/TRAVERSE_PATH_SIZE;
scale.setScale(scaleWidth, scaleHeight);
sTraversalPath.transform(scale, mPath);
}
}
public Path getPath() {
return mPath;
}
@Override
public void draw(Canvas canvas) {
canvas.drawPath(mPath, mPathPaint);
super.draw(canvas);
}
}
private static class PointFToPointConverter extends TypeConverter<PointF, Point> {
Point mPoint = new Point();
public PointFToPointConverter() {
super(PointF.class, Point.class);
}
@Override
public Point convert(PointF value) {
mPoint.set(Math.round(value.x), Math.round(value.y));
return mPoint;
}
}
}
~~~
layout:
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioGroup android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/path_animation_type"
>
<RadioButton android:id="@+id/named_components"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Named Components"/>
<RadioButton android:id="@+id/property_components"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Property Components"/>
<RadioButton android:id="@+id/multi_int"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Multi-int"/>
<RadioButton android:id="@+id/multi_float"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Multi-float"/>
<RadioButton android:id="@+id/named_setter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Named Property"/>
<RadioButton android:id="@+id/property_setter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Property"/>
</RadioGroup>
</ScrollView>
<view class="com.example.android.apis.animation.PathAnimations$CanvasView"
android:id="@+id/canvas"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1">
<ImageView android:id="@+id/moved_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/frog"/>
</view>
</LinearLayout>
~~~
**View state changes(視圖狀態改變)**
[StateListAnimator](http://developer.android.com/reference/android/animation/StateListAnimator.html)類可以讓你定義動畫集,能在view狀態改變時工作。下面的實例顯示了如何定義一個XML資源的[StateListAnimator](http://developer.android.com/reference/android/animation/StateListAnimator.html)。
使用步驟
1.定義一個XML資源的[StateListAnimator](http://developer.android.com/reference/android/animation/StateListAnimator.html)
~~~
<?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:valueTo="10"
android:valueType="floatType"/>
<objectAnimator android:propertyName="rotationX"
android:duration="@android:integer/config_shortAnimTime"
android:valueTo="360"
android:valueType="floatType"/>
</set>
</item>
<item
android:state_pressed="false"
>
<set>
<objectAnimator android:propertyName="translationZ"
android:duration="10000"
android:valueTo="0"
android:valueType="floatType"/>
<objectAnimator android:propertyName="rotationX"
android:duration="@android:integer/config_shortAnimTime"
android:valueTo="0"
android:valueType="floatType"/>
</set>
</item>
</selector>
~~~
定義了翻轉的效果的xml,
配置兩種方式:
1.layout:android:stateListAnimator屬性將其分配給你的視圖,
2.代碼:使用[AnimationInflater.loadStateListAnimator()](http://developer.android.com/reference/android/animation/AnimatorInflater.html#loadStateListAnimator(android.content.Context,)方法,并且通過[View.setStateListAnimator()](http://developer.android.com/reference/android/view/View.html#setStateListAnimator(android.animation.StateListAnimator))方法分配動畫到你的視圖上。
效果如下:

當然動畫任你自己定義,如果只定義Z軸的話也可以輕松的實現此效果:

[AnimatedStateListDrawable](http://developer.android.com/reference/android/graphics/drawable/AnimatedStateListDrawable.html)類讓你去創建drawable資源,該資源在相關聯的視圖的狀態更改時展示動畫。一些Android5.0中的系統控件使用這些默認的動畫。下面的例子顯示了如何定義一個[AnimatedStateListDrawable](http://developer.android.com/reference/android/graphics/drawable/AnimatedStateListDrawable.html)作為XML資源:
~~~
<!-- 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>
~~~
- 前言
- ym—— Android 5.0學習之創建模擬器
- ym—— Android 5.0學習之使用Material主題
- ym—— Android 5.0學習之使用Palette
- ym—— Android 5.0學習之AnimatedVectorDrawable
- ym—— Android 5.0學習之ListView升級版RecyclerView
- ym—— Android 5.0學習之CardView
- ym—— Android 5.0學習之Activity過渡動畫
- ym—— Android 5.0學習之定義陰影
- ym—— Android 5.0學習之動畫
- ym—— Android 5.0學習之Tinting和Clipping
- ym—— Android 5.0學習之感想篇(含Demo)