Android高级动画(2)

目录

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之间来回变换该怎么办呢?

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

下一篇

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

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

推荐阅读更多精彩内容

  • Android Vector曲折的兼容之路 两年前写书的时候,就在研究Android L提出的Vector,可研究...
    eclipse_xu阅读 34,931评论 30 263
  • 目录 Android高级动画(1)http://www.jianshu.com/p/48554844a2dbAnd...
    大公爵阅读 9,169评论 20 55
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,520评论 25 707
  • 感恩一早同事帮我买好的物品! 感恩清洁工一早将楼道打扫的非常干净,让我们有舒适的环境! 感恩客户的来电,对我充分的...
    我不叫许仲斌阅读 256评论 0 3
  • [JVM] 虽然大部分情况下不需要我们直接对Java内存方面进行直接操作,但是了解其中的原理和调优还是比较重要的,...
    ChaosCoffee阅读 318评论 0 0