Android高级动画(2)

作者: 大公爵 | 来源:发表于2017-06-30 14:37 被阅读3173次

    目录

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

    下一篇

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

    相关文章

      网友评论

      • 修得养得梦得过得:小鸟 到 桃心 path的变化是咋算的,不会是自己手算的吧
        大公爵:@修得养得梦得过得 后文有说啊。用我自己开发的那个桌面工具可以导出来
      • 岱zy:我也准备写这个来着...查资料发现我们图都是一样的....同学千锋的吗
        岱zy:@大公爵 哈哈误会了~之前我在培训机构学习的时候,讲这个内容有些和文章中相同的svg素材,现在准备自己再记录一下,看完了大神的文章,收获颇多,比之前学习的详细多了。
        大公爵:@岱zy 千锋是什么?
      • 开发者头条_程序员必装的App:感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/jr22xi 欢迎点赞支持!
        欢迎订阅《Android技术杂货铺》https://toutiao.io/subjects/70141
      • bfb5f477373d:博主 求 animated-selector登场 里面的 <aapt:attr name="android:animation"> -->attr
        代码 感谢大神
        bfb5f477373d:@大公爵 大神!<aapt:attr name="android:animation"> 我这句报错 Attribute is missing the Android namespace prefix 是什么原因-.-
        bfb5f477373d:@大公爵 谢谢!!!
        大公爵:@Spec_d1bc 我把全部工程都分享出来了。你去github下载,链接在第三篇
      • 做梦枯岛醒:正需要,不知道能不能看懂,😂,过几天再看
      • 千里之行死于足下:没办法,博主的文章太硬,继续嚼……
      • 千里之行死于足下:主要是这个用来绘制矢量图的"pathData",看得一脸懵.博主要是能在出个入门教程就太好了:pray:
        大公爵:@千里之行死于足下 请看第三篇,有讲这个
        千里之行死于足下: @大公爵 刚了解完,想问下大神,你们做这些动画是不是用ps之类的画好矢量图再导出数据的?我感觉手动编写汇出矢量图那简直就是噩梦😰
        大公爵:@千里之行死于足下 pathData这块和svg是一样的,先去了解一下svg就好了
      • Yera丶:path计算什么的看的一脸懵逼。。。
        大公爵:@旧时光丶_9ad5 懵逼就对了,这块知识我文章里也没讲全,主要是太乱了,不能什么都讲,只能讲一下主要概念。
      • 依然范特稀西:补充一个:Android studio 1.4开始就支持了简单的图标和svg 直接生成vector,animated-vector要在gradle 1.5 + 上使用。
        大公爵:@依然范特稀西 复杂的请参考第三篇
        依然范特稀西:@大公爵 是的,支持5.0以上版本的内置的Material图标,复杂的估计就导入svg了
        大公爵:@依然范特稀西 AndroidStudio直接生成矢量图是基于系统固定的图标样本,不能把任意的图片转成矢量图。
      • yask:收获了

      本文标题:Android高级动画(2)

      本文链接:https://www.haomeiwen.com/subject/rwxmcxtx.html