Android高级动画(2)

来点硬货

前面一篇文章已经讲了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必须是同形pathPS:如果两个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之间来回变换该怎么办呢?

靓仔2

animated-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 的显示。再来欣赏下动画吧。

PathMorphing

trimPath

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

动画开始后,放大镜裁剪的部分逐渐变小,横线裁剪的部分逐渐变大,直至放大镜消失,只剩横线。

image

trimPath动画的写法也基本是固定的

<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代码动态构建,并且我们不能控制动画的过程。所以这又是个头疼的问题。怎么办呢,下一个靓仔在哪里?

下一篇

下一篇应该是这个系列总结篇,我们会在系统矢量动画的基础上封装一些自己的库,实现一些额外的功能。最后我们还会封装一个通用动画库,简化动画的使用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏快乐八哥

Cmd Markdown编辑器简明语法手册

标签: Cmd-Markdown 1. 斜体和粗体 使用 * 和 ** 表示斜体和粗体。 示例: 这是 斜体,这是 粗体。 2. 分级标题 使用 === 表示一...

21970
来自专栏Android机器圈

Android之MaterialDesign应用技术2-仿支付宝上滑搜索框缓慢消失

PS:在这之前也就是上一篇介绍了MaterialDesign一些滑动删除、标题栏的悬浮效果等,如果没看过第一篇的小火鸡可以看一下,因为这篇是接着上一篇写的,有一...

416100
来自专栏calmound

Maya

建立酒杯的方法(CV曲线) surface(曲面)-- creat cv curve tool-- control vertex(调整图形)[再次creat c...

42080
来自专栏糊一笑

移动端效果之Picker

写在前面 接着前面的移动端效果的研究,这次来看看picker选择器的实现原理 移动端效果之Swiper 移动端效果之CellSwiper 移动端效果之Index...

43430
来自专栏小蠢驴iOS专题

xib之cell高度计算--简单实现cell高度由控件数量自定义

34180
来自专栏应用案例

D3.js 满足你对数据可视化的一切幻想

D3.js D3的全称是Data-Driven Documents(数据驱动的文档),是一个用来做数据可视化的JavaScript函数库,而JavaScript...

619100
来自专栏程序员宝库

Canvas 动画之支付宝价格拖动选择

源码:https://github.com/supperjet/H5-Animation/tree/master/%E6%94%AF%E4%BB%98%E5%A...

396100
来自专栏阮一峰的网络日志

CSS3常用功能的写法

但是,不同的浏览器有不同的CSS3实现,兼容性是一个大问题。上周的YDN介绍了CSS3 Please网站,该网站总结了一些常用功能的写法。

20920
来自专栏冰霜之地

由FlexBox算法强力驱动的Weex布局引擎

在上篇文章里面谈了Weex在iOS客户端工作的基本流程。这篇文章将会详细的分析Weex是如何高性能的布局原生界面的,之后还会与现有的布局方法进行对比,看看Wee...

24340
来自专栏青玉伏案

iOS开发之各种动画各种页面切面效果

注:其中有些效果调用了CATransition的Private API, 仅供娱乐。 补充:还是有好多小伙伴问那些可以在AppStore中使用,调用私有API的...

222100

扫码关注云+社区

领取腾讯云代金券