Android动画机制与使用技巧——Android 5.X SVG 矢量动画机制

Posted 黄飞_hf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android动画机制与使用技巧——Android 5.X SVG 矢量动画机制相关的知识,希望对你有一定的参考价值。

Google在android 5.X 中增加了对SVG 矢量图形的支持,这对于创建新的高效率动画具有非常重大的意义。那首先了解SVG的含义。

  • 可伸缩矢量图形(Scalable Vector Graphics)
  • 定义用于网络的基于矢量的图形
  • 使用XML格式定义图形
  • 图像在放大或改变尺寸的情况下其图形质量不会有所损失
  • 万维网联盟的标准
  • 与诸如DOM和XSL之类的W3C标准是一个整体

SVG在Web上的应用非常广泛,在Android 5.X之前的Android版本上,可以通过一些第三方开源库来在Android中使用SVG。而在Android 5.X之后,Android中添加了对SVG的path标签的支持。从而让开发者可以使用SVG来创建更加丰富的动画效果。那么SVG对比传统的Bitmap,究竟有什么好处呢?Bitmap(位图)通过在每个像素点上存储色彩信息来表达图像,而SVG是一个绘图标准。与Bitmap相比,SVG最大的优点就是放大不会失真。而且Bitmap需要为不同分辨率设计多套图标,而矢量图则不需要。

path标签

使用path标签创建SVG,就像用指令的方式来控制一只画笔,例如移动画笔到某一坐标位置,画一条线,画一条曲线,结束。path标签所支持的指令有以下几种。

M = moveto(M X,Y):将画笔移动到指定的坐标位置,但未发生绘制

L = lineto(L X,Y):画直线到指定的坐标位置

H = horizontal lineto(H X):画水平线到指定的X轴坐标

V = vertical lineto(V Y):画垂直线到指定的Y轴坐标

C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝塞曲线

S = smooth curveto(S X2,Y2,ENDX,ENDY):三次贝塞曲线

Q = quadratic Belzier curveto(Q X,Y,ENDX,ENDY):二次贝塞曲线

T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射前面路径后的终点

A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线

Z = closepath():关闭路径

在使用上面的指令时,需注意以下几点:

  • 坐标轴以(0, 0)为中心,X轴水平向右,Y轴水平向下。
  • 所有指令大小写均可。大写绝对定位,参照全局坐标系;小写相对定位,参照父容器坐标系。
  • 指令和数据间的空格可以省略。
  • 同一指令出现多次可以只用一个。

SVG常用指令

  • L
    绘制直线的指令是“L”,代表从当前点绘制直线到给定点。“L”之后的参数是一个点坐标,如“L 200 400”绘制直线。同时,还可以使用“H”和“V”指令来绘制水平、竖直线,后面的参数是x坐标(H指令)或y坐标(V指令)。
  • M
    M指令类似Android绘图中path类的moveTo方法,即代表将画笔移动到某一点,但并不发生绘制动作。
  • A
    A指令用来绘制一段弧线,且允许弧线不闭合。可以把A指令绘制的弧线想象成是椭圆的某一段,A指令以下有七个参数。
    1)RX,RY指所在椭圆的半轴大小
    2)XROTATION指椭圆的X轴与水平方向顺时针方向夹角,可以想象成一个水平的椭圆绕中心点顺时针旋转XROTATION的角度。
    3)FLAG1只有两个值,1表示大角度弧线,0为小角度弧线。
    4)FLAG2只有两个值,确定从起点至终点的方向,1为顺时针,0为逆时针。
    5)X,Y为终点坐标

SVG编辑器

SVG参数的写法固定且复杂,因此完全可以使用程序来实现,所以一般通过SVG编辑器来编辑SVG图形。通过可视化编辑好图形后,点击View Source就可以转换为SVG代码。SVG在线编辑器网址

Android中使用SVG

Google在Android 5.X 中提供了下面两个新的API来帮助支持SVG。

  • VectorDrawable
  • AnimatedVectorDrawable

其中,VectorDrawable让你可以创建基于XML的SVG图形,并结合AnimatedVectorDrawable来实现动画效果。

VectorDrawable

在XML中常见一个静态的SVG图形,通常会形成下图所示的树形结构。

这里写图片描述

path是SVG树形结构中的最小单位,而通过Group可以将不同的path进行组合。

如何在drawable目录下创建SVG图形,首先需要在XML中通过vector标签来声明对SVG的使用,代码如下(vector.xml):

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="100"
    android:viewportHeight="100">

    <group android:name="test"
        android:rotation="0">
        <path android:fillColor="@android:color/holo_blue_light"
            android:pathData="M 25 50
                              a 25,25 0 1,0 50,0"/>
    </group>

</vector>

width与height属性表示该SVG图形的具体大小,而viewportWidth与viewportHeight属性表示SVG图形划分的比例。后面在绘制path时所示使用的参数,就是根据这两个值来进行转换的,比如上面的代码,将200dp划分为100份,如果在绘制图形时使用坐标(50, 50),则意味着该坐标位于该SVG图形正中间。因此,width、height的比例与viewportWidth、viewportHeight的比例,必须保持一致,不然图形就会发生压缩、变形。

通过添加group标签和path标签来绘制一个SVG图形,其中pathData就是绘制SVG图形所用到的指令。在上面的代码中,先使用“M”指令,将画笔移动到(25, 50)这个坐标,再通过A指令来绘制一个圆弧并填充,效果图如下。

这里写图片描述

由于这里使用了android:fillColor属性来绘制图形,因此绘制出来的是一个填充的图形,如果要绘制一个非填充的图形,可以使用以下属性。

            android:strokeColor="@android:color/holo_blue_light"
            android:strokeWidth="2"

绘制效果如下所示:

这里写图片描述

AnimatedVectorDrawable

AnimatedVectorDrawable的作用就是给VectorDrawable提供动画效果。Google工程师将AnimatedVectorDrawable比喻为一个胶水,通过AnimatedVectorDrawable来连接静态的VectorDrawable和动态的objectAnimator。

如何使用AnimatedVectorDrawable实现SVG图形的动画效果。首先,在XML文件中通过animated-vector标签来声明对AnimatedVectorDrawable的使用,并制定其作用的path或group,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector">

    <target
        android:animation="@anim/anim_path1"
        android:name="test"/>

</animated-vector>

对应的vector即为静态的VectorDrawable。

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportHeight="100"
    android:viewportWidth="100">

    <group
        android:name="test"
        android:rotation="0">

        <path
            android:pathData="M 25 50
                              a 25,25 0 1,0 50,0"
            android:strokeColor="@android:color/holo_blue_light"
            android:strokeWidth="2"/>
    </group>

</vector>

AnimatedVectorDrawable中指定的target的name属性必须与VectorDrawable中需要作用的name属性保持一致,这样系统才能找到要实现动画的元素。通过AnimatedVectorDrawable中target的animation属性,将一个动画作用到了对应name的元素上,objectAnimator代码如下:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="4000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360" />

可以看到,对动画效果的实现,还是通过属性动画来实现的,只是属性稍有不同。

在group标签和path标签中添加了rotation、strokeColor、pathData等属性,那么在objectAnimator中,就可以通过指定android:propertyName=”XXX”属性来选择控制哪一种属性,通过android:valueFrom=”XXX”和android:valueTo=”XXX”属性,可以控制动画的起始值,唯一需要注意的是,如果指定属性为pathData,那么需要添加一个属性——android:valueType=”pathType”来告诉系统进行pathData变换。类似的情况,可以使用rotation进行旋转动画、使用strokeColor实现颜色变化,使用pathData进行形状、位置变化。

当所有的XML文件准备好以后,就可以在代码中控制SVG动画,可以非常方便地将一个AnimatedVectorDrawable XML文件设置给一个ImageView作为背景显示。

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@drawable/anim_vector" />

在程序中,只需要使用以下代码,即可开始SVG动画。

        Drawable drawable = imageView.getDrawable();
        if(drawable instanceof Animatable){
            ((Animatable) drawable).start();
        }

SVG动画实例

线图动画

在Android 5.X 中Google大量引入了线图动画。当页面发生改变时,页面上的icon不再是生硬地切换,而是通过非常生动的动画效果,转换为另一种形态。如下图所示,点击图像时,开始SVG动画,上下两根线会从中间折断并向中间折起,最终形成一个“X”。

这里写图片描述

要实现这样一个效果,首先创建一个静态的SVG图形,即静态的VectorDrawable,并绘制为如图所示的初始状态。

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="200dp"
        android:height="200dp"
        android:viewportHeight="100"
        android:viewportWidth="100">

    <group>
        <path
            android:name="path1"
            android:pathData="
            M 20,80
            L 50,80 80,80"
            android:strokeColor="@android:color/holo_green_dark"
            android:strokeLineCap="round"
            android:strokeWidth="5"/>

        <path
            android:name="path2"
            android:pathData="
            M 20,20
            L 50,20 80,20"
            android:strokeColor="@android:color/holo_green_dark"
            android:strokeLineCap="round"
            android:strokeWidth="5"/>
    </group>

</vector>

path1和path2分别绘制了两条直线,每条直线由三个点控制,即起始点、中间点和终点,形成初始状态。接下来实现变换的objectAnimator动画。

<?xml version="1.0" encoding="utf-8"?>
<!--anim_path1.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="500"
                android:interpolator="@android:anim/bounce_interpolator"
                android:propertyName="pathData"
                android:valueFrom="
            M 20,80
            L 50,80 80,80"
                android:valueTo="
            M 20,80
            L 50,50 80,80"
                android:valueType="pathType"/>
<!--代码中定义了一个pathType的属性动画,并指定了变化的起始值。
在SVG的路径变化属性动画中,变化前后的节点数必须相同,这也是为什么前面需要使用三个点来绘制一条直线的原因,
因为后面需要中间点进行动画交换。-->
<?xml version="1.0" encoding="utf-8"?>
<!--anim_path2.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="500"
                android:interpolator="@android:anim/bounce_interpolator"
                android:propertyName="pathData"
                android:valueFrom="
            M 20,20
            L 50,20 80,20"
                android:valueTo="
            M 20,20
            L 50,50 80,20"
                android:valueType="pathType"/>

最后使用AnimatedVectorDrawable来将VectorDrawable与objectAnimator黏合在一起。

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
                 android:drawable="@drawable/trick">

    <target android:name="path1" android:animation="@anim/anim_path1"/>
    <target android:name="path2" android:animation="@anim/anim_path2"/>

</animated-vector>

模拟三球仪

三球仪是天文学中一个星象仪器,用来模拟地、月、日三个星体的绕行轨迹,即地球绕太阳旋转,月球绕地球旋转的同时,绕太阳旋转,如下图所示:

这里写图片描述

首先,同样是需要绘制一个静态的地、月、日三星系统。

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="200dp"
        android:height="200dp"
        android:viewportHeight="100"
        android:viewportWidth="100">

    <group
        android:name="sun"
        android:pivotX="60"
        android:pivotY="50"
        android:rotation="0">

        <path
            android:name="path_sun"
            android:fillColor="@android:color/holo_blue_light"
            android:pathData="
            M 50,50
            a 10,10 0 1,0 20,0
            a 10,10 0 1,0 -20,0"/>

        <group
            android:name="earth"
            android:pivotX="75"
            android:pivotY="50"
            android:rotation="0">

            <path
                android:name="path_earth"
                android:fillColor="@android:color/holo_orange_dark"
                android:pathData="
                M 70,50
                a 5,5 0 1,0 10,0
                a 5,5 0 1,0 -10,0"/>

            <group>

                <path
                    android:fillColor="@android:color/holo_green_dark"
                    android:pathData="
                    M 90,50
                    m -5,0
                    a 4,4 0 1,0 8,0
                    a 4,4 0 1,0 -8,0"/>

            </group>
        </group>
    </group>
</vector>

上述代码中,在“sun”这个group中,有一个“earth”group,同时使用android:pivotX和android:pivotY属性来设置其旋转中心。这个VectorDrawable分别代表了太阳系系统和地月系系统。下面对这两个group分别进行SVG动画:

<?xml version="1.0" encoding="utf-8"?>
<!--sun动画-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="4000"
                android:propertyName="rotation"
                android:valueFrom="0"
                android:valueTo="360"/>
<?xml version="1.0" encoding="utf-8"?>
<!--earth动画-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="4000"
                android:propertyName="rotation"
                android:valueFrom="0"
                android:valueTo="360"/>

这里为了简化示例,让两个动画效果的实现完全相同。最后,最后使用AnimatedVectorDrawable黏合SVG静态图形和动画。

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
                 android:drawable="@drawable/earth_moon_system">

    <target android:name="sun" android:animation="@anim/anim_sun"/>
    <target android:name="earth" android:animation="@anim/anim_earth"/>

</animated-vector>

轨迹动画

Android对SVG的支持给我们带来了很多好玩的特效,例如可以将propertyName指定为trimPathStart,这个属性用来控制一个SVG Path的显示比例,例如一个圆形的SVG,使用trimPathStart动画,可以像画出一个圆一样来绘制一个圆,从而形成一个轨迹动画,下面这个实例就展示了绘制轨迹的动画效果,用来绘制搜索框中的一个放大镜,效果如下所示:

这里写图片描述

代码如下:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="160dp"
        android:height="30dp"
        android:viewportHeight="30"
        android:viewportWidth="160">

        <path
            android:name="search"
            android:pathData="
            M 141,17
            A 9 9 0 1 1 142 16
            L 149 23"
            android:strokeColor="#ff3570be"
            android:strokeLineCap="round"
            android:strokeWidth="2"
            android:strokeAlpha="0.8"/>

        <path
            android:name="bar"
            android:pathData="
            M 0,23
            L 149 23"
            android:strokeColor="#ff3570be"
            android:strokeLineCap="square"
            android:strokeWidth="2"
            android:strokeAlpha="0.8"/>

</vector>
<?xml version="1.0" encoding="utf-8"?>

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:interpolator/accelerate_decelerate"
    android:propertyName="trimPathStart"
    android:valueFrom="0"
    android:valueTo="1"
    android:valueType="floatType" />
<!--trimPathStart就是利用0到1的百分比来按照轨迹绘制SVG图像。类似的,还有trimPathEnd这个属性。-->
<?xml version="1.0" encoding="utf-8"?>

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/searchbar">

    <target
        android:name="search"
        android:animation="@anim/anim_searchbar" />

</animated-vector>

代码地址

以上是关于Android动画机制与使用技巧——Android 5.X SVG 矢量动画机制的主要内容,如果未能解决你的问题,请参考以下文章

Android事件分发机制

Android官方文档之Animation

Android高级开发知识总结

窗口动画与 Android 5.0 上的导航栏重叠

Android--多线程之Handler

Android--多线程之Handler