目录
Android高级动画(1)http://www.jianshu.com/p/48554844a2db
Android高级动画(2)http://www.jianshu.com/p/89cfd9042b1e
Android高级动画(3)http://www.jianshu.com/p/d6cc8d218900
Android高级动画(4)http://www.jianshu.com/p/91f8363c3a8c
来点硬货
前面一篇文章已经讲了Android中大部分的动画框架,回顾一下有:Tween动画,属性动画,帧动画,CircularReveal,Activity转场动画,5.0新转场动画,Interpolator插值器,5.0转场动画分为Explode、Slide、Fade、Share四种模式。合理且充分利用这些动画,我们已经可以做出很多优美的效果了。
但是今天这篇文章我们来讲讲大名鼎鼎的矢量动画,它颠覆了前面所有的动画。前面的动画都是对控件做动画,而矢量动画是对图形做动画,矢量动画可以做出前面任何一个动画框架都做不到的效果。好了,NB就先不吹了,开始我们的学习吧。
从矢量图形说起
我们平时看到的图片大多数都是位图,英文名叫 bitmap,位图对应的格式就是 .bmp,看过bmp的人都知道,体积那叫一个大啊。。。一张小小的Logo都能2M,于是jpg,png这些压缩格式就出现了,优秀的压缩算法极大地减少了图片体积。配合索引位图、灰度图等手段,图片可以压缩的非常小,世界一下子变得美好~
但是,开心没多久,问题又来了。不管你的压缩算法有所优秀,位图有2个天生的缺点无法避免:
(1)图片放大会失真
(2)图片尺寸越大,体积越大
不管是做Android开发还是IOS开发,我们都需要适配不同分辨率的手机,也就意味着同一个ImageView在不同手机上的图片分辨率是不同的,如果我们只用一套图片,那必然存在放大失真问题。统一的解决方案就是为每一种分辨屏幕准备一套图片。这样失真的问题解决了,但是图片体积又大了。
似乎两者是不可兼得的,怎么办呢?
靓仔矢量图登场
矢量图不同于位图是用像素描述图像的,它是用数学曲线描述图形。所以一张图片就是对应着一系列的数学曲线,所以图片的显示尺寸和图片体积无关。(这里为什么说显示尺寸,因为矢量图根本就没有所谓的尺寸,就看你把它显示成多大),它的体积就是文本文件的大小。并且矢量图可以无限拉伸不失真。
先来看一个Android中使用矢量图的例子:
爱心哇,这个爱心有点漂亮~
代码实现:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportHeight="32"
android:viewportWidth="32">
<path
android:fillColor="#e11c00"
android:pathData="M20.5,9.5 c-1.955,0,-3.83,1.268,-4.5,3
c-0.67,-1.732,-2.547,-3,-4.5,-3 C8.957,9.5,7,11.432,7,14
c0,3.53,3.793,6.257,9,11.5 c5.207,-5.242,9,-7.97,9,-11.5
C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>
(PS:这个文件非常小,只有670字节,连1KB都不到。而且我们只需要这一个文件,就可以适配所有分辨率,无限拉伸不失真。)
根节点是vector,width和height属性是显示大小,但是实际上这个大小是可以根据控件改变的。viewportHeight和viewportWidth也是宽高,它是定义曲线函数时所参照的宽高。子节点path就是定义绘制内容的,fillColor是填充颜色,pathData是描绘路径。那么问题来了,pathData中的这一串字母数字是什么东东?
SVG
说到这,SVG必须得登场了。SVG就是标准的矢量图格式,Android中使用矢量图虽然没有直接使用SVG图片,但是基本格式是和SVG一样的。
SVG语法
SVG的语法太复杂了,这里不可能全部讲一遍。为了说明问题,我们就讲几个最基础的命令。
M:新建起点,参数x,y(M20, 30)
L:连接直线,参数x,y(L30, 20)
H:纵坐标不变,横向连线,参数x(H20)
V:横坐标不变,纵向连线,参数y(V30)
Q:二次贝塞尔曲线,参数x1,y1,x2,y2(Q10,20,30,40)
C:三次贝塞尔曲线,参数x1,y1,x2,y2,x3,y3(C10,20,30,40,50, 60)
Z:连接首尾,闭合曲线,无参数
掌握以上这些基本命令之后,我们基本上就可以画出90%的图形了。比如上面demo只用到了三个命令:M、C、Z,我们整个系列所有demo用到的命令也就只有M、L、C、Z。
(至于什么是二次贝塞尔,什么是三次贝塞尔,如果不了解的话请自行百度,不能再拓展了,否则这篇文章要突破万字了。)
让矢量图形动起来
虽然我们已经可以绘制漂亮的矢量图形了,但是我们这个系列是Android高级动画啊,得动起来,Android中怎样才能让矢量图形动起来呢?
animated-vector登场
animated-vector从名字上看就是动起来的vector,先看示例:
animated-vector初始显示的是三条横线,然后从三条横线的状态变化到箭头,同时整体旋转360度。
代码如下:
(1)首先是layout文件,一个普通的ImageView,src指向一个drawable
<ImageView
android:id="@+id/imgBtn"
android:layout_width="200dp"
android:layout_height="200dp"
android:onClick="startAnim"
android:src="@drawable/animvectordrawable" />
(2)drawable根节点是一个animated-vector,drawable参数用于指定初始显示的样子,下面两个target子节点用于指定动画,第一个target是指定了旋转动画,第二个target指定了path转变动画。下面我们分别来看下初始的drawable和两个target。
<!-- 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>
(3)初始drawable,这个根节点vector就是定义了宽高,第三层path节点就是初始显示的矢量图形,它有填充色和path路径。
<!-- vectordrawable.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="300dp"
android:width="300dp"
android:viewportHeight="70"
android:viewportWidth="70" >
<group
android:name="rotationGroup"
android:pivotX="35"
android:pivotY="35"
android:rotation="0.0" >
<path
android:name="v"
android:fillColor="#000000"
android:pathData="M10,10 L60,10 L60,20 L10,20 Z M10,30 L60,30
L60,40 L10,40 Z M10,50 L60,50 L60,60 L10,60 Z" />
</group>
</vector>
但是外面又包了一层group,这个是干什么用的呢?
animated-vector规定,可以有多个动画同时进行,但是一个对象上只能加载一个动画。上面的例子可以看到三条线图形转变成箭头图形,同时旋转360度,那就要有两个动画,一个做path变换,一个做旋转。但是两个动画不能同时放在一个对象上,所以必须用group包一层,把path变换动画放在path对象上,把旋转动画放在group对象上,从而实现整体的效果。
(4)target1,这就是一个简单的属性动画,旋转360度
<!-- rotation.xml -->
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1500"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360" />
(5)target2,这个动画是最神奇的地方,它用于从一个path变换到另一个path,valueFrom指定变换前的path,valueTo指定变换后的path,propertyName和valueType在这个例子中是固定写法。
<!-- path_morph.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="1500"
android:propertyName="pathData"
android:valueFrom="M10,10 L60,10 L60,20 L10,20 Z M10,30 L60,30 L60,40
L10,40 Z M10,50 L60,50 L60,60 L10,60 Z"
android:valueTo="M5,35 L40,0 L47.072,7.072 L12.072,42.072 Z M10,30 L60,30 L60,40
L10,40 Z M12.072,27.928 L47.072,62.928 L40,70 L5,35 Z"
android:valueType="pathType" />
</set>
这里需要重点提下valueFrom和valueTo
valueFrom和valueTo分别指定了变换前的path和变换后的path,它要求前后两个path必须是同形path
(PS:如果两个path拥有相同的命令数,并且对应位置的命令符相同,那么这两个path我们称之为同形path。
如:
M10,15 L20,20 L25,15 C10,20,20,20,30,10 L50,50 Z
M20,30 L10,10 L15,25 C25,10,30,30,10,20 L35,35 Z
)
(6)java代码启动动画
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void startAnim(View view) {
Drawable drawable = imgBtn.getDrawable();
((Animatable) drawable).start();
}
至此,一个简单的animated-vector就完成了,估计有人要吐槽了。
“我的天,一个动画要写这么多代码?”
是的,这一点木有办法,矢量动画本身就比较复杂。但是别伤心,因为更复杂的还在后面呢。。。
矢量选择器
animated-vector已经很强大了是吧,但是肯定有人发现问题了,animated-vector只能从一个path变换到另一个path,不能反向再变回来。如果我需要在两个path之间来回变换该怎么办呢?
靓仔2animated-selector登场。
selector我们大家都很熟悉了,用于一个按钮的点击效果。animated-selector类似,也是用于两个状态的切换,只不过animated-selector是在两个path之间来回切换显示。
先看演示:
PathMorphing是不是很酷炫!迫不及待地想知道是怎么实现的。
(1)首先是Layout,一个普通的 ImageView,src指向一个 drawable
<ImageView
android:id="@+id/iv_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:src="@drawable/heart_twitter" />
(2)再看drawable,drawbale是一个animated-selector,子节点是两个item和两个transition。
<?xml version="1.0" encoding="utf-8"?>
<animated-selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/state_on"
android:drawable="@drawable/ic_twitter"
android:state_checked="true"/>
<item android:id="@+id/state_off"
android:drawable="@drawable/ic_heart" />
<transition
android:fromId="@id/state_off"
android:toId="@id/state_on"
android:drawable="@drawable/avd_heart_to_twitter" />
<transition
android:fromId="@id/state_on"
android:toId="@id/state_off"
android:drawable="@drawable/avd_twitter_to_heart" />
</animated-selector>
两个item分别指定了两种状态下要显示的样子,两个transition分别指定了当状态切换时所做的动画。
具体来说:第一个item指定的是state_on时显示的样子,第二个item指定的是state_off时显示的样子。第一个transition指定的是从off切换到on时所做的动画,第二个transition指定的是从on切换到off时所做的动画。
下面来分别看下两个item和两个transition
(3)两个item
<?xml version="1.0" encoding="UTF-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<group
android:name="groupTwitter"
android:pivotX="12"
android:pivotY="12">
<path
android:name="twitter"
android:fillColor="#C2185B"
android:pathData="M 22.46,6.0 l 0.0,0.0 C 21.69,6.35,20.86,6.58,20.0,6.69
C 20.88,6.16,21.56,5.32,21.88,4.31 c 0.0,0.0,0.0,0.0,0.0,0.0
C 21.05,4.81,20.13,5.16,19.16,5.36 C 18.37,4.5,17.26,4.0,16.0,4.0
c 0.0,0.0,0.0,0.0,0.0,0.0 L 16.0,4.0 C 13.65,4.0,11.73,5.92,11.73,8.29
C 11.73,8.63,11.77,8.96,11.84,9.27 C 8.28,9.09,5.11,7.38,3.0,4.79
C 2.63,5.42,2.42,6.16,2.42,6.94 C 2.42,8.43,3.17,9.75,4.33,10.5
C 3.62,10.5,2.96,10.3,2.38,10.0 C 2.38,10.0,2.38,10.0,2.38,10.03
C 2.38,12.11,3.86,13.85,5.82,14.24 C 5.46,14.34,5.08,14.39,4.69,14.39
C 4.42,14.39,4.15,14.36,3.89,14.31 C 4.43,16.0,6.0,17.26,7.89,17.29
C 6.43,18.45,4.58,19.13,2.56,19.13 C 2.22,19.13,1.88,19.11,1.54,19.07
C 3.44,20.29,5.7,21.0,8.12,21.0 C 16.0,21.0,20.33,14.46,20.33,8.79
C 20.33,8.6,20.33,8.42,20.32,8.23 C 21.16,7.63,21.88,6.87,22.46,6.0 L 22.46,6.0"/>
</group>
</vector>
<?xml version="1.0" encoding="UTF-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0" >
<group
android:name="groupHeart"
android:pivotX="12"
android:pivotY="12">
<path
android:name="heart"
android:fillColor="#C2185B"
android:pathData="M 12.0,21.35 l -1.45,-1.32 C 5.4,15.36,2.0,12.28,2.0,8.5
C 2.0,5.42,4.42,3.0,7.5,3.0 c 1.74,0.0,3.41,0.81,4.5,2.09
C 13.09,3.81,14.76,3.0,16.5,3.0 C 19.58,3.0,22.0,5.42,22.0,8.5
c 0.0,3.78,-3.4,6.86,-8.55,11.54 L 12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 L 12.0,21.35"/>
</group>
</vector>
根节点是一个vector,里面是一个group包着一个path,和前面说的一样,这个group不是必须的,往往是为了加载动画而增加的。path是定义路径。
(PS:这里有人可能会有疑问,这些“爱心”、“Twitter”的path是怎么生成的呢?这里先提前简单地解释下:对于简单的图形,我们可以手动计算,比如上面三条横线变成箭头的例子,就是手动计算点坐标的。对于复杂的图形,比如Twitter和爱心,手动计算不现实,我们可以找一些辅助软件来生成。)
(4)两个transition
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_heart">
<target android:name="groupHeart">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:valueFrom="-360"
android:valueTo="0"
android:duration="1000" />
</aapt:attr>
</target>
<target android:name="heart">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:propertyName="pathData"
android:valueFrom="M 12.0,21.35 l -1.45,-1.32 C 5.4,15.36,2.0,12.28,2.0,8.5 C 2.0,5.42,4.42,3.0,7.5,3.0
c 1.74,0.0,3.41,0.81,4.5,2.09 C 13.09,3.81,14.76,3.0,16.5,3.0 C 19.58,3.0,22.0,5.42,22.0,8.5
c 0.0,3.78,-3.4,6.86,-8.55,11.54 L 12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 L 12.0,21.35"
android:valueTo="M 22.46,6.0 l 0.0,0.0 C 21.69,6.35,20.86,6.58,20.0,6.69 C 20.88,6.16,21.56,5.32,21.88,4.31
c 0.0,0.0,0.0,0.0,0.0,0.0 C 21.05,4.81,20.13,5.16,19.16,5.36 C 18.37,4.5,17.26,4.0,16.0,4.0
c 0.0,0.0,0.0,0.0,0.0,0.0 L 16.0,4.0 C 13.65,4.0,11.73,5.92,11.73,8.29 C 11.73,8.63,11.77,8.96,11.84,9.27
C 8.28,9.09,5.11,7.38,3.0,4.79 C 2.63,5.42,2.42,6.16,2.42,6.94 C 2.42,8.43,3.17,9.75,4.33,10.5
C 3.62,10.5,2.96,10.3,2.38,10.0 C 2.38,10.0,2.38,10.0,2.38,10.03 C 2.38,12.11,3.86,13.85,5.82,14.24
C 5.46,14.34,5.08,14.39,4.69,14.39 C 4.42,14.39,4.15,14.36,3.89,14.31 C 4.43,16.0,6.0,17.26,7.89,17.29
C 6.43,18.45,4.58,19.13,2.56,19.13 C 2.22,19.13,1.88,19.11,1.54,19.07 C 3.44,20.29,5.7,21.0,8.12,21.0
C 16.0,21.0,20.33,14.46,20.33,8.79 C 20.33,8.6,20.33,8.42,20.32,8.23 C 21.16,7.63,21.88,6.87,22.46,6.0 L 22.46,6.0"
android:valueType="pathType" />
</aapt:attr>
</target>
</animated-vector>
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_twitter">
<target android:name="groupTwitter">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="-360"
android:duration="1000" />
</aapt:attr>
</target>
<target android:name="twitter">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:propertyName="pathData"
android:valueFrom="M 22.46,6.0 l 0.0,0.0 C 21.69,6.35,20.86,6.58,20.0,6.69 C 20.88,6.16,21.56,5.32,21.88,4.31
c 0.0,0.0,0.0,0.0,0.0,0.0 C 21.05,4.81,20.13,5.16,19.16,5.36 C 18.37,4.5,17.26,4.0,16.0,4.0
c 0.0,0.0,0.0,0.0,0.0,0.0 L 16.0,4.0 C 13.65,4.0,11.73,5.92,11.73,8.29 C 11.73,8.63,11.77,8.96,11.84,9.27
C 8.28,9.09,5.11,7.38,3.0,4.79 C 2.63,5.42,2.42,6.16,2.42,6.94 C 2.42,8.43,3.17,9.75,4.33,10.5
C 3.62,10.5,2.96,10.3,2.38,10.0 C 2.38,10.0,2.38,10.0,2.38,10.03 C 2.38,12.11,3.86,13.85,5.82,14.24
C 5.46,14.34,5.08,14.39,4.69,14.39 C 4.42,14.39,4.15,14.36,3.89,14.31 C 4.43,16.0,6.0,17.26,7.89,17.29
C 6.43,18.45,4.58,19.13,2.56,19.13 C 2.22,19.13,1.88,19.11,1.54,19.07 C 3.44,20.29,5.7,21.0,8.12,21.0
C 16.0,21.0,20.33,14.46,20.33,8.79 C 20.33,8.6,20.33,8.42,20.32,8.23 C 21.16,7.63,21.88,6.87,22.46,6.0 L 22.46,6.0"
android:valueTo="M 12.0,21.35 l -1.45,-1.32 C 5.4,15.36,2.0,12.28,2.0,8.5 C 2.0,5.42,4.42,3.0,7.5,3.0
c 1.74,0.0,3.41,0.81,4.5,2.09 C 13.09,3.81,14.76,3.0,16.5,3.0 C 19.58,3.0,22.0,5.42,22.0,8.5
c 0.0,3.78,-3.4,6.86,-8.55,11.54 L 12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35
C 12.0,21.35,12.0,21.35,12.0,21.35 L 12.0,21.35"
android:valueType="pathType"/>
</aapt:attr>
</target>
</animated-vector>
两个transition分别定义了从 off 到 on 和从 on 到 off 时切换动画。
根节点是一个animated-vector,它有一个重要的属性drawable,这个属性指定动画前的初始状态,就是一个普通的vector。
下面两个target是定义动画,每一个target都有一个name属性,指定动画作用于哪个对象上,这个对象就是上面drawable里定义的名字。<aapt:attr>这一层就是固定写法,我也不知道为什么要定义这一层~[/尴尬]。再下面就是ObjectAnmator,定义具体的动画。通过propertyName来区分动画类型,rotation是旋转,pathData是path转换。旋转动画就不说了,path动画转换前面也分析过了。
OK,至此我们已经把动画都定义好了。因为比较复杂,我们再来捋一遍。
(1)首先定义一个animated-selector,它定义两个item,对应两种状态on、off的显示,再定义两个transition用于状态变化时启动动画。
(2)两个item是vector类型,定义要显示的path。
(3)两个transition是animated-vector类型,定义从一个状态到另一个状态时的动画,在指定动画时要注意,一个对象上只能加载一个动画,如果动画个数比对象个数多,要用group把对象包裹起来。
可是问题来了,这样只是定义好了动画,但是还是动不起来啊。因为animated-selector怎么知道View的状态变化了呢?所以还差最后一步,把View的状态和animated-selector关联起来。
private boolean isTwitterChecked = false;
public void onTwitterClick(View view) {
isTwitterChecked = !isTwitterChecked;
final int[] stateSet = {android.R.attr.state_checked * (isTwitterChecked ? 1 : -1)};
imageView.setImageState(stateSet, true);
}
好啦,这样当我们点击图片时,通过调用imageView.setImageState就可以切换状态,从而切换 Twitter 和 heart 的显示。再来欣赏下动画吧。
PathMorphingtrimPath
trimPath也是一种动画类型,它是通过对路径的裁剪实现的动画。先看示例:
TrimPath效果还是比较酷炫的,代码实现和上面Twitter基本类似。直接贴代码:
(1)animated-selector基本和上面类似,就不分析了
<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/state_on"
android:drawable="@drawable/bar"
android:state_checked="true"/>
<item android:id="@+id/state_off"
android:drawable="@drawable/search" />
<transition
android:fromId="@id/state_off"
android:toId="@id/state_on"
android:drawable="@drawable/avd_search_to_bar" />
<transition
android:fromId="@id/state_on"
android:toId="@id/state_off"
android:drawable="@drawable/avd_bar_to_search" />
</animated-selector>
(2)两个item也基本类似,也不分析了
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="300dp"
android:height="48dp"
android:viewportWidth="150"
android:viewportHeight="24">
<path
android:name="search"
android:pathData="M141,17 A9,9 0 1,1 142,16 L149,22"
android:strokeWidth="2"
android:strokeColor="#f51035"
android:strokeAlpha="0.8"
android:strokeLineCap="round"
android:trimPathStart="1"/>
<path
android:name="bar"
android:pathData="M10,22 L149,22"
android:strokeWidth="2"
android:strokeColor="#f51035"
android:strokeAlpha="0.8"
android:strokeLineCap="round" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="300dp"
android:height="48dp"
android:viewportWidth="150"
android:viewportHeight="24">
<path
android:name="search"
android:pathData="M141,17 A9,9 0 1,1 142,16 L149,22"
android:strokeWidth="2"
android:strokeColor="#f51035"
android:strokeAlpha="0.8"
android:strokeLineCap="round" />
<path
android:name="bar"
android:pathData="M10,22 L149,22"
android:strokeWidth="2"
android:strokeColor="#f51035"
android:strokeAlpha="0.8"
android:strokeLineCap="round"
android:trimPathStart="1"/>
</vector>
(3)两个transition,这里和前面稍有不同。我们拿最后一个分析下。
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/bar">
<target android:name="search">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="trimPathStart"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:duration="1000" />
</aapt:attr>
</target>
<target android:name="bar">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="trimPathStart"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:duration="1000" />
</aapt:attr>
</target>
</animated-vector>
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/search">
<target android:name="search">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="trimPathStart"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:duration="1000" />
</aapt:attr>
</target>
<target android:name="bar">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="trimPathStart"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:duration="1000" />
</aapt:attr>
</target>
</animated-vector>
首先根节点是一个animated-vector,有一个drawable属性,下面包含两个target子节点,整体结构上没有变化。区别就在target中的ObjectAnimator。
propertyName是trimPathStart,表示这是一个trimPath类型的动画(还有trimPathEnd,方向相反)。trimPath的原理是从一段path上裁剪出一小部分显示,通过改变裁剪的长度,形成一个渐变的动画。
上面的demo中,其实是有两段path,一段是放大镜,一段是横线。就像这样:
image初始状态,横线显示的长度是0,所以我们只能看到一个放大镜:
image动画开始后,放大镜裁剪的部分逐渐变小,横线裁剪的部分逐渐变大,直至放大镜消失,只剩横线。
imagetrimPath动画的写法也基本是固定的
<objectAnimator
android:propertyName="trimPathStart"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:duration="1000" />
valueFrom和valueTo表示裁剪的起始点和结束点,valueType是float类型,duration是1000毫秒。这样就实现了放大镜和横线切换显示的动画啦。
总结
这一篇我们基本讲完了Android中的矢量动画,这块知识点都不难,就是太乱。我尽量把思路捋的顺一点了,用问题引出问题的方式把所有知识点串起来,这样更容易理清关系。如果完整看到这里的话你一定会发现还是有问题,Android系统提供的vector、animated-vector、animated-selector虽然很强大,但是有一个致命的缺点,就是只能在xml中写死,不能通过java代码动态构建,并且我们不能控制动画的过程。所以这又是个头疼的问题。怎么办呢,下一个靓仔在哪里?
下一篇
下一篇应该是这个系列总结篇,我们会在系统矢量动画的基础上封装一些自己的库,实现一些额外的功能。最后我们还会封装一个通用动画库,简化动画的使用。
网友评论
欢迎订阅《Android技术杂货铺》https://toutiao.io/subjects/70141
代码 感谢大神