💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
最近在看一些关于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本身的大小`。当然,虽然是大白话,但是还是不容易理解,我们来看看效果就一目了然了。 ![](https://box.kancloud.cn/2016-02-18_56c55b3c7e677.jpg "") 恩,效果很赞,但是这个背景能不能自定义呢?答案是当然能了,那么这里要引进一个新的`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" /> ~~~ 看一下效果怎么样, ![](https://box.kancloud.cn/2016-02-18_56c55b3cb903c.jpg "") 通过观察效果,我们可以发现, > 1. 涟漪的效果是在我们给item的drawable的非透明区域发生的。 > 1. 当我们指定item的id为`@android:id/mask`时,默认背景是不显示的。 ok,到这里,我们已经学会自定义点击的涟漪效果了。我们继续学习,接下来就来到了一个更赞的效果。 ### 二、Reveal Effect 显示效果 新的sdk给我们提供了一个类-`ViewAnimationUtils`,该类就提供了一个静态的方法`createCircularReveal`,通过这个方法,我们可以给任何一个layout或者view一个涟漪的显示或者消失过程。首先我们来看看要实现的效果, ![](https://box.kancloud.cn/2016-02-18_56c55b3ceb081.jpg "") 效果确实很赞,代码应该怎么写呢? 其实也很简单,主要还是我们将要学习的`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移动,最后让动画跑起来。 ![](https://box.kancloud.cn/2016-02-18_56c55b3d67496.jpg "") ### 五、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`, 来看看效果, ![](https://box.kancloud.cn/2016-02-18_56c55b3d88703.jpg "") 仔细观察效果,我们在点击的过程中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的停留时间。 ![](https://box.kancloud.cn/2016-02-18_56c55b3daeb8a.jpg "") 从效果中可能看不出我何时点击何时松开的,这里每个颜色都会有一段停留时间的。 ### 六、 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。 这是一个什么形状呢? ![](https://box.kancloud.cn/2016-02-18_56c55b3dd00f0.jpg "") 当然,我们可以借助一些工具很轻松的实现一些`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`方法! 最后,我们来看看这时候的效果。 ![](https://box.kancloud.cn/2016-02-18_56c55b3de0fcb.jpg "") 有一个旋转的动画, 在旋转的过程中还伴随着颜色的变化,棒棒哒。 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)