Android Material Design Animation (一)


1 动画的规范

Material Design 是 Google 总结的一套具有创新性和符合科学的视觉规范,其中包含动画、排版、控件、资源、样式等,包含的内容比较全,感兴趣的同学可以查看官方文档 Material design

对于动画,Material Design 定义的一套规则,这里简单总结如下:

  • 自然的加减速

    将动画的主体 View 看做是一个实际物体,有质量和重量,那么做出来的动画更符合人们的认知。不仅动画的速度改变要平滑,加速度的改变也要平滑,位移动画减速到停止的瞬间,移动速度的减慢速度也在变慢。

    image
  • 自然的入场和出场

    image
  • 用户输入反馈

    • 满足波纹效果的触屏反馈,一种点击水面的触感

      image
    • 改变控件高度的触屏反馈

      image
  • 生动的场景切换动画

    image
  • 连贯的图标渐变动画

    image

2 动画的实现

贴心的 Google 大神么不仅定义了这些规范,也提供相关的类和方法帮助我们实现他们的规范。根据动画的类别和提供的类和属性等,大概整理下,这里将 Material Design Animation 分为 6 类,分别是:

  • Touch Feedback (触摸反馈)

  • Reveal Effect (揭露效果)

  • Activity Transitions ( Activity 切换效果 )

  • Curved Motion (曲线运动)

  • View State Changes (视图状态改变)

  • Animate View Drawables (可绘矢量动画)

2.1 Touch Feedback (触摸反馈)

触摸反馈比较好理解,大家在做项目的时候,经常碰到视觉同学会要求我们做出来的按钮在点击的时候,修改外观,比如置灰显示。下面这段 xml 代码,就是解决这个问题的一种常规办法,相信 Android 小伙伴们都非常熟悉,当然不使用 xml 的话,也可以自定义在 Java 代码中自定义使用 ColorStateList,来达到相同的效果。不过这种常规实现的效果中规中矩,不算酷炫。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@mipmap/ic_btn_pressed" android:state_pressed="true"/>
    <item android:drawable="@mipmap/ic_btn"/>
</selector>

为了实现上面规范中讲述的波纹效果,可以使用 RippleDrawable 类,简单的我们可以在 xml 文件中定义。

  • ?android:attr/selectableItemBackground - 有界限的波纹

    <Button
        style="@style/style_touch_feedback_btn"
        android:background="?android:attr/selectableItemBackground"
        android:id="@+id/btn_normal" />
    
  • ?android:attr/selectableItemBackgroundBorderless - 延伸到view之外的波纹

    <Button
        style="@style/style_touch_feedback_btn"
        android:background="?android:attr/selectableItemBackgroundBorderless"
        android:id="@+id/btn_borderless"/>
    
  • 使用RippleDrawer自定义的类型

    <Button
        style="@style/style_touch_feedback_btn"
        android:background="@drawable/custom_ripple"
        android:id="@+id/btn_ripple"/>
    

    custom_ripple.xml

    <?xml version="1.0" encoding="utf-8"?>
    <!-- A blue ripple drawn atop a drawable resource. -->
    <ripple
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ff0000ff">
    
        <item android:drawable="@drawable/bg_loadmore0" />
    </ripple>
    
  • 在 Android 21 以上是默认有波纹效果的,可以使用 android:background="@null" 或者指定其他颜色,取消波纹

    <Button
        style="@style/style_touch_feedback_btn"
        android:background="?android:attr/selectableItemBackground"
        android:id="@+id/btn_normal" />
    

整体显示效果如下:

image

另外,因为 Touch Feedback 是 Android 5.0 上默认支持的,如果需要在低版本的 Android 应用中也要实现的话,那可以参见 RippleEffect

2.2 Reveal Effect (揭露效果)

Android 5.0 引入了 ViewAnimationUtils.createCircularReveal() 接口,能很方便的实现圆形缩放效果。

private static void circularRevealBox(View view) {
    Rect rect = new Rect();
    view.getLocalVisibleRect(rect);
    int cx = rect.left + rect.width()/2;
    int cy = rect.top + rect.height()/2;

    // get the final radius for the clipping circle
    int finalRadius = view.getWidth();

    Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, 0, finalRadius);
    anim.setDuration(1000);
    anim.start();

    view.setVisibility(View.VISIBLE);
}

效果如下:

image

2.3 Curved Motion (曲线运动)

2.3.1 使用 TypeEvaluator 实现

在 Android 5.0 之前,如果需要实现曲线位移运动,则需要自己写过程插值器 TypeEvaluator,代码如下:

public class PathEvaluator implements TypeEvaluator<PathPoint> {
    @Override
    public PathPoint evaluate(float t, PathPoint startValue, PathPoint endValue) {
        float x, y;
        if (endValue.mOperation == PathPoint.CURVE) {
            float oneMinusT = 1 - t;
            x = oneMinusT * oneMinusT * oneMinusT * startValue.mX +
                    3 * oneMinusT * oneMinusT * t * endValue.mControl0X +
                    3 * oneMinusT * t * t * endValue.mControl1X +
                    t * t * t * endValue.mX;
            y = oneMinusT * oneMinusT * oneMinusT * startValue.mY +
                    3 * oneMinusT * oneMinusT * t * endValue.mControl0Y +
                    3 * oneMinusT * t * t * endValue.mControl1Y +
                    t * t * t * endValue.mY;
        } else if (endValue.mOperation == PathPoint.LINE) {
            x = startValue.mX + t * (endValue.mX - startValue.mX);
            y = startValue.mY + t * (endValue.mY - startValue.mY);
        } else {
            x = endValue.mX;
            y = endValue.mY;
        }
        return PathPoint.moveTo(x, y);
    }
}

自定义的动画路径类 AnimatorPath

public class AnimatorPath {
    
    // The points in the path
    ArrayList<PathPoint> mPoints = new ArrayList<PathPoint>();


    /**
     * Move from the current path point to the new one
     * specified by x and y. This will create a discontinuity if this point is
     * neither the first point in the path nor the same as the previous point
     * in the path.
     */
    public void moveTo(float x, float y) {
        mPoints.add(PathPoint.moveTo(x, y));
    }

    /**
     * Create a straight line from the current path point to the new one
     * specified by x and y.
     */
    public void lineTo(float x, float y) {
        mPoints.add(PathPoint.lineTo(x, y));
    }

    /**
     * Create a cubic Bezier curve from the current path point to the new one
     * specified by x and y. The curve uses the current path location as the first anchor
     * point, the control points (c0X, c0Y) and (c1X, c1Y), and (x, y) as the end anchor point.
     */
    public void curveTo(float c0X, float c0Y, float c1X, float c1Y, float x, float y) {
        mPoints.add(PathPoint.curveTo(c0X, c0Y, c1X, c1Y, x, y));
    }

    /**
     * Returns a Collection of PathPoint objects that describe all points in the path.
     */
    public Collection<PathPoint> getPoints() {
        return mPoints;
    }
}

之后曲线动画设置和启动代码如下

// Set up the path we're animating along
AnimatorPath path = new AnimatorPath();
path.moveTo(0, 0);
path.lineTo(0, 300);
path.curveTo(100, 0, 300, 900, 400, 500);

// Set up the animation
final ObjectAnimator anim = ObjectAnimator.ofObject(this, "buttonLoc",
        new PathEvaluator(), path.getPoints().toArray());
anim.setDuration(3000);
anim.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        mMaterialButton.setVisibility(View.INVISIBLE);
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        mMaterialButton.setVisibility(View.VISIBLE);
    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
});

效果如下:

image

2.3.2 使用 PathInterpolator 和 ObjectAnimator.ofFloat 接口实现

在 Android 5.0 提供了

  1. PathInterpolator: 以三次 bezier 曲线中间 2 个控制点的坐标来定义动画的速率快慢

  2. ObjectAnimator.ofFloat(T target, Property<T, Float> xProperty, Property<T, Float> yProperty, Path path): 中间的 path 参数定义位移动画的路径。

Path path = new Path();
//path.moveTo(0, 100);
path.lineTo(0, 400);
path.cubicTo(100, 0, 300, 900, 500, 600);

PathInterpolator pathInterpolator = new PathInterpolator(0.8f, 0f, 1f, 1f);
final ObjectAnimator mAnimator = ObjectAnimator.ofFloat(mMaterialButton, View.X, View.Y, path);
mAnimator.setInterpolator(pathInterpolator);
mAnimator.setDuration(3000);

效果如下:

image

2.4 View State Changes (视图状态改变)

在 Android 5.0 新提供了 StateListAnimatorAnimatedStateListDrawable 类用于实现视图状态改变动画

  1. StateListAnimator

    res/anim/anim_view_state_changes.xml

    <?xml version="1.0" encoding="utf-8"?>
    <!-- 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="4dp"
                    android:valueType="floatType"/>
            </set>
        </item>
    
        <item
            android:state_pressed="false">
            <set>
                <objectAnimator android:propertyName="translationZ"
                    android:duration="100"
                    android:valueTo="0"
                    android:valueType="floatType"/>
            </set>
        </item>
    </selector> 
    

    说明 :定义了一个按下时,控件 Z 值变大的动画;松开时,控件 Z 值变小的动画。其中 android 中 Z 值的改变是通过控件的阴影大小来体现

    具体应用:

    • xml 中定义
    <TextView
        android:text="animate_view_state_changes"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:stateListAnimator="@anim/anim_view_state_changes"
    
    • java 中定义
    TextView textView = (TextView)findViewById(R.id.animate_view_state_changes);
    StateListAnimator stateLAnim = AnimatorInflater.loadStateListAnimator(this,
            R.anim.anim_view_state_changes);
    textView.setStateListAnimator(stateLAnim);
    

    效果图如下:

    image
  2. AnimatedStateListDrawable

    res/drawable/my_anim_state_drawable.xml

    <?xml version="1.0" encoding="utf-8"?>
    <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/bg_loadmore5"
            android:state_pressed="true"/>
    
        <item android:id="@+id/focused"
            android:drawable="@drawable/bg_loadmore5"
            android:state_focused="true"/>
    
        <item android:id="@+id/normal"
            android:drawable="@drawable/bg_loadmore0"/>
    
        <!-- specify a transition -->
        <transition android:fromId="@+id/normal" android:toId="@+id/pressed">
            <animation-list>
                <item android:duration="15" android:drawable="@drawable/bg_loadmore0"/>
                <item android:duration="15" android:drawable="@drawable/bg_loadmore1"/>
                <item android:duration="15" android:drawable="@drawable/bg_loadmore2"/>
                <item android:duration="15" android:drawable="@drawable/bg_loadmore3"/>
                <item android:duration="15" android:drawable="@drawable/bg_loadmore4"/>
                <item android:duration="15" android:drawable="@drawable/bg_loadmore5"/>
            </animation-list>
        </transition>
    </animated-selector>
    

    说明 :当处于press状态的时候,animation-list 正着走一遍,drawable使用最后一个;当处于press状态的时候,animation-list反着走一遍,drawable使用第一个

    具体应用:

    <TextView
        android:text="my_anim_state_drawable"
        android:layout_width="250dp"
        android:layout_height="100dp"
        android:background="@drawable/my_anim_state_drawable" />
    

    效果图如下:

    image

2.5 Animate View Drawables (可绘矢量动画)

通常在三种xml定义的矢量图

  1. 静态矢量图,使用 <vector> 元素的矢量图,在 res/drawable/

    矢量图可以定义的属性元素有<group>和<path>,<group>定义了一个<path>的集合,或者子<group>,<path>定义绘制的路径。

    Google 官方示例代码

    <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>
    
  2. 动态矢量图,使用 <objectAnimator> 元素,在 res/animator

    <objectAnimator
        xmlns:android="http://schemas.android.com/apk/res/android"
        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" />
    

    其中,valueFromvalueTo 是矢量动画的关键,分别指定了矢量图的起始路径。

    这里的矢量指令是 SVG 标准指令,规则如下:

    • 代码中的 M 表示 MoveTo; l 表示 LineTo;z` 表示收尾闭合

    • 矢量图 path 从一个图形到另一个,fromto 的路径必须一致:相同数量的命令和相同数量的每个命令的参数

    • 全部的指令使用一个字母表示,如 Ml

    • 指令中的逗号、空格符可以被省略,如 M 100 100 L 200 20M100 100L200 200

    • 连续使用的相同指令可以被省略,如M 100 200 L 200 100 L -100 -200 → M 100 200 L 200 100 -100 -200

    • 大写指令代码绝对坐标,小写指令代码相对坐标

    • Unicode U+0046 FULL STOP (“.”) 是唯一被允许的小数点。如数字 13,000.56 是不合法的

    • 进一步的详细细节请参见 SVG Paths

  1. 一个或多个 object animator,使用 <animated-vector> 元素,在 res/drawable/

    示例代码

    res/drawable/face.xml 如下:

    <vector
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="200dp"
        android:width="200dp"
        android:viewportHeight="100"
        android:viewportWidth="100" >
      <path
          android:fillColor="@color/yellow"
          android:pathData="@string/path_circle"/>
      <path
          android:fillColor="@android:color/black"
          android:pathData="@string/path_face_left_eye"/>
      <path
          android:fillColor="@android:color/black"
          android:pathData="@string/path_face_right_eye"/>
      <path
          android:name="mouth"
          android:strokeColor="@android:color/black"
          android:strokeWidth="@integer/stroke_width"
          android:strokeLineCap="round"
          android:pathData="@string/path_face_mouth_sad"/>
    </vector>
    

    res/drawable/avd.xml 如下:

    <?xml version="1.0" encoding="utf-8"?>
    <animated-vector
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/face" >
    
      <target
          android:name="mouth"
          android:animation="@anim/smile" />
    
    </animated-vector>
    

    说明<target> 标签里面的 name 标签指定 face.xml 中必须一个 name 相吻合

    具体应用

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/avd" />
    

    效果图如下:

    image

2.6 Activity Transitions ( Activity 切换效果 )

Andorid Material Design Animation(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,732评论 25 707
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,697评论 0 10
  • 触摸反馈 触摸反馈(Touch Feedback)是 material design 给用户在与UI元素交互时提供...
    tiger桂阅读 635评论 0 0
  • 渐渐的,每日必刷的朋友圈也懒得去发动态。不想关注别人的生活,也不想自己的生活被打扰。或许,会有一两个死党过来追问,...
    刘二丫儿阅读 262评论 0 1
  • 韩老师, 左手支撑,身子侧躺,做撑起,放下,撑起放下的动作
    大莲莲阅读 187评论 0 0