Android自定义动画三-SVG动画

Android自定义动画三-SVG动画

本篇文章主要是对SVG的一个介绍和使用,以及Android中对SVG的一个支持,从而可以帮助我们在android下很轻松的通过SVG实现一些非常酷炫的动画效果。

1.SVG介绍

SVG 是使用 XML 来描述二维图形和绘图程序的语言。

它具备以下的特点:

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

用户可以直接用代码来描绘图像,可以用任何文字处理工具打开SVG图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来观看。

2.SVG 的历史和优势

在 2003 年一月,SVG 1.1 被确立为 W3C 标准。
参与定义 SVG 的组织有:太阳微系统、Adobe、苹果公司、IBM 以及柯达。
与其他图像格式相比,使用 SVG 的优势在于:

  • SVG 可被非常多的工具读取和修改(比如记事本)
  • SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。
  • SVG 是可伸缩的
  • SVG 图像可在任何的分辨率下被高质量地打印
  • SVG 可在图像质量不下降的情况下被放大
  • SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图)
  • SVG 可以与 Java 技术一起运行
  • SVG 是开放的标准
  • SVG 文件是纯粹的 XML
  • SVG 的主要竞争者是 Flash。

与 Flash 相比,SVG 最大的优势是与其他标准(比如 XSL 和 DOM)相兼容。而 Flash 则是未开源的私有技术。

3.Android使用SVG动画

从上诉的描述中可以看到SVG是一个可伸缩的矢量图,而且相较于JPG和GIF图像尺寸都要更小,并且可以直接在xml中写入。正式由于这样的特点,那么 Android 从 5.0 提供了新的API VectorDrawable,通过该对象,我们可以使用矢量图SVG,在编写xml文件中,通过关键的几个标签节点vector,animated-vector,path完成对SVG的编写以及动画的实现。

VectorDrawable

可以看到 VectorDrawable 是继承与Drawable,并且官网说明了是用于创建一个drawable通过XML文件中申明根节点 vector 来实现一个android下的矢量图。

VectorDrawable 的出现也意味着以前我们放在mdpi, hdpi, xhdpi, xxhdpi中的部分图片资源(适合用矢量图描述的,比如图标)只用一个VectorDrawable 替代就可以了。

在vector节点下我们可以使用 “group、clip-path、path” 来描述一个drawable图形;“group”节点定义一组path或者子group,而path元素定义需要绘制的路径。clip-path节点定义当前绘制的剪切路径。注意,clip-path 只对当前的 group 和子 group 有效。

接下来大家可以通过以下的代码来学习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">
    <!--这里的viewportHeight:100的意思是将高度分为了100份
    这里的viewportWidth:100的意思是将宽度分为了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>

这段代码就是在Res下的drawable目录下建立了一个 xml 文件,然后指定根节点为 vector 写出的两根线段的效果。效果图如下:

上述代码中就是定义了一个 group 包含了两个path,而这两个 path 分别代表一根直线的绘制。这里要重点去理解的是 path 节点下的 pathData属性。

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():关闭路径

注释:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。

对于做 Android 开发的我们来说要掌握这么多命令,并且如果需要绘制一个比较复杂的图形那么可能 pathData中需要写很多的数据,这肯定不是我们想看到的结果,比如下面的这个效果:


对应的SVG代码为:

<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 490.358 490.358" style="enable-background:new 0 0 490.358 490.358;" xml:space="preserve">
<g>
    <g>
        <circle style="fill:#3C92CA;" cx="166.658" cy="164.129" r="137.1"/>
        <path d="M490.358,377.629c0-0.3,0-0.6-0.1-0.9c0-0.3-0.1-0.6-0.2-0.9s-0.1-0.6-0.2-0.8c-0.1-0.3-0.2-0.5-0.4-0.8
            c-0.1-0.3-0.2-0.5-0.4-0.8c-0.1-0.3-0.3-0.5-0.5-0.7s-0.3-0.5-0.5-0.7s-0.4-0.4-0.7-0.6c-0.2-0.2-0.4-0.4-0.6-0.6
            s-0.5-0.3-0.8-0.5c-0.2-0.1-0.4-0.3-0.7-0.4l-110.3-54.4c-0.3-0.2-0.6-0.3-1-0.4c-1.9-0.7-47.4-16.6-84.6,0.1l-82.1,29.7
            c-0.2,0.1-0.5,0.2-0.7,0.3c-11.8,5.4-18,17.5-15.5,30.2c2.5,12.6,12.7,21.5,25.5,22.1c0.1,0,0.1,0,0.2,0c0.8,0,4.4-0.2,41.8-2.6
            c16.3-1,33.1-2.1,34.6-2.2c4.7-0.2,8.6-4,8.7-8.8c0.1-5-3.8-9.2-8.9-9.3c-0.5,0-0.5,0-35.5,2.2c-17.5,1.1-37.1,2.4-40.4,2.6
            c-6.3-0.5-7.8-5.8-8.2-7.4c-0.7-3.4,0.3-7.9,5-10.2l82-29.7c0.2-0.1,0.4-0.2,0.7-0.3c28.8-13.1,66.4-1.3,70.9,0.1l49.1,24.2
            l-27,63.7c-13.3-8.2-29.5-10.4-44.5-5.9l-110.2,33.1c-13.1,4-27.6,2.5-39.7-4.1l-172.6-93.5c-6.2-3.4-4.2-9.3-3.7-10.5
            c0.6-1.6,3.2-6.6,9.5-4.9l134.1,44.7c4.7,1.6,9.9-1,11.5-5.7c1.6-4.8-1-9.9-5.7-11.5l-134.4-44.9c-0.2-0.1-0.3-0.1-0.5-0.1
            c-13.3-3.6-26.2,2.8-31.3,15.6c-5.2,12.9-0.3,26.5,11.9,33.2l172.7,93.8c16.3,8.9,35.8,10.9,53.5,5.5l110.2-33.1
            c10.1-3,21-1.6,29.9,4.1l56.2,35.4c0.3,0.2,0.5,0.3,0.8,0.4c0.1,0,0.1,0.1,0.2,0.1h0.1c0.1,0.1,0.2,0.1,0.4,0.1
            c0.2,0.1,0.5,0.2,0.7,0.3c0.1,0,0.3,0.1,0.4,0.1c0.2,0.1,0.5,0.1,0.7,0.2c0.1,0,0.3,0,0.4,0.1c0.4,0,0.7,0.1,1.1,0.1l0,0l0,0l0,0
            l0,0c0.4,0,0.8,0,1.2-0.1c0.1,0,0.3,0,0.4-0.1c0.3,0,0.5-0.1,0.8-0.2c0.1,0,0.3-0.1,0.4-0.1c0.2-0.1,0.5-0.2,0.7-0.3
            c0.1-0.1,0.3-0.1,0.4-0.2c0.2-0.1,0.4-0.2,0.7-0.4c0.1-0.1,0.3-0.2,0.4-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.1-0.1,0.3-0.2,0.4-0.3
            c0.2-0.2,0.3-0.3,0.5-0.5c0.1-0.1,0.2-0.2,0.4-0.4c0.2-0.2,0.3-0.4,0.4-0.6c0.1-0.1,0.2-0.3,0.3-0.4c0,0,0-0.1,0.1-0.1
            c0-0.1,0.1-0.2,0.1-0.2c0.1-0.2,0.3-0.5,0.4-0.8l40-85.2c0.1-0.3,0.2-0.5,0.3-0.8c0.1-0.3,0.2-0.6,0.3-0.8
            c0.1-0.3,0.1-0.6,0.1-0.9s0.1-0.6,0.1-0.9C490.358,378.229,490.358,377.929,490.358,377.629z M469.358,382.229l-31.9,67.9
            l-32.3-20.4l27.7-65.4L469.358,382.229z"/>
        <path d="M153.858,173.229h25.4c10.4,0,18.8,8.4,18.8,18.8c0,10.4-8.4,18.8-18.8,18.8h-45.7c-5,0-9.1,4.1-9.1,9.1s4.1,9.1,9.1,9.1
            h23.9v17.9c0,5,4.1,9.1,9.1,9.1s9.1-4.1,9.1-9.1v-17.9h3.7c20.4,0,37-16.6,37-37s-16.6-37-37-37h-25.4c-10.4,0-18.8-8.4-18.8-18.8
            s8.4-18.8,18.8-18.8h44.9c5,0,9.1-4.1,9.1-9.1s-4.1-9.1-9.1-9.1h-23.1v-17.5c0-5-4.1-9.1-9.1-9.1s-9.1,4.1-9.1,9.1v17.6h-3.7
            c-20.4,0-37,16.6-37,37C116.958,156.629,133.558,173.229,153.858,173.229z"/>
        <path d="M166.658,310.229c80.6,0,146.1-65.6,146.1-146.1s-65.6-146.2-146.1-146.2s-146.2,65.6-146.2,146.2
            S86.058,310.229,166.658,310.229z M166.658,36.129c70.6,0,128,57.4,128,128s-57.4,128-128,128s-128-57.4-128-128
            S96.058,36.129,166.658,36.129z"/>
    </g>
</g>
</svg>

对于这么庞大的数据计算我们肯定是不可能去手动画的,而这张图片也是我从 FLATICON 网站上随便找的一张 SVG 图片,down下来就是现成的 svg 文件了,我们可以直接使用到我们android 中来,所以以后开发中可以让 UI 给我们这样的 SVG 文件就行了,当然也有 SVG编辑工具直接产出一些好看的 SVG图片,都可以在 http://www.flaticon.com中找到。

AnimatedVectorDrawable

AnimatedVectorDrawable 同样继承 Drawable,不过它实现了 Animatable 接口,专门用于让 VectorDrawable 动起来。

首先来看官方的说明:

This class animates properties of a VectorDrawable with animations defined using ObjectAnimator or AnimatorSet.

Starting from API 25, AnimatedVectorDrawable runs on RenderThread (as opposed to on UI thread for earlier APIs). This means animations in AnimatedVectorDrawable can remain smooth even when there is heavy workload on the UI thread. Note: If the UI thread is unresponsive, RenderThread may continue animating until the UI thread is capable of pushing another frame. Therefore, it is not possible to precisely coordinate a RenderThread-enabled AnimatedVectorDrawable with UI thread animations. Additionally, onAnimationEnd(Drawable) will be called the frame after the AnimatedVectorDrawable finishes on the RenderThread.

AnimatedVectorDrawable can be defined in either three separate XML files, or one XML.

AnimatedVectorDrawable 通过 ObjectAnimator 或 AnimatorSet 对 VectorDrawable 的某个属性作动画。

从API-25开始,AnimatedVectorDrawable 运行在 RenderThread (相反地,早期API是运行在UI线程)。这也就是说 AnimatedVectorDrawable 在UI线程繁忙时也能保持流畅运行。

如果UI线程没有反应,RenderThread 会持续动画计算,直到UI线程有能力推进下一帧。因此,没有办法准确地同步 RenderThread-enabled 的 AnimatedVectorDrawable 和 UI 线程中的 Animations。此外, Animatable2.AnimationCallback.onAnimationEnd(drawable) 肯定会在 RenderThread 渲染完 AnimatedVectorDrawable 最后一帧时被调用。

Animated vector drawable 可以让 group 和 path 元素的属性动态变化。group 定义一组 path 或者子 group,而 path 元素定义需要绘制的路径。当你想让 VectorDrawable 呈现动画效果,在定义 VectorDrawable 的时候需要为 group 和 path 的 android:name 属性设置一个唯一的名字,以便在Animated vector drawable中找到它们。比如在上面我们画的两根直线demo,我现在想让两根直线做出以下的动画效果:

在上面的两根直线 vector 代码中是由两段 path 定义出来的,也分别定义了 name 分别为 path1 path2,所以接下来只需要定义一个 AnimatedVectorDrawable 来分别执行这两段 path的动画。

代码如下:

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

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

</animated-vector>

注意上面:
一般我们使用 propertyName 节点都是传入基本动画(平移,缩放,旋转,透明度)
此处传入的为:pathData,效果为让动画沿着设定的 valueFrom 和 valueTo 的数据执行
最后要加入节点 android:valueType="pathType",表示数据类型为 path 类型,否则会报错

在 target 标签中需要分别为当前你要执行的 path 指定一个 属性动画,比如 anim_path1,代码如下:(必须是属性动画,所以在 animator 下创建)

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator  xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:propertyName="pathData"
    android:interpolator="@android:interpolator/bounce"
    android:valueFrom="
            M 20,80
            L 50,80 80,80"
    android:valueTo="
            M 20,80
            L 50,50 80,80"
    android:valueType="pathType"
    >
</objectAnimator >

anim_path2的代码也是类似:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator  xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:propertyName="pathData"
    android:interpolator="@android:interpolator/bounce"
    android:valueFrom="
            M 20,20
            L 50,20 80,20"
    android:valueTo="
            M 20,20
            L 50,50 80,20"
    android:valueType="pathType"
    >
</objectAnimator >

这里还分别给两个动画指定了一个动画插值器:bounce,使合并的时候具有碰撞效果。

输入框SVG背景动画

最后再来一个输入框的背景动画,效果如下:


结合svg动画和基本逻辑代码实现EditText的输入特效
在 activity 主界面中准备一个 ImageView 和两个 EditText ,第一个 EditText 没有背景(后续将为 ImageView 设置图片来使得 EditText 看起来像有一个背景一样),第二个 EditText 存在默认背景

这里直接贴出主要的 vector 和 animated_vector 代码,更详细的代码,请到GitHUb下载查看:https://github.com/zphuanlove/AnimationProject

首先是输入框背景,一根直线和一个对勾的代码:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="300dp"
        android:height="96dp"
        android:viewportHeight="48"
        android:viewportWidth="150">
    <!-- 绘制底部的直线-->
    <path
        android:name="bottom"
        android:pathData="
        M 0,23
        L 140,23"
        android:strokeAlpha="0.8"
        android:strokeColor="@color/colorAccent"
        android:strokeLineCap="square"
        android:strokeWidth="1"/>
    <!--对钩的处理
    大写的L:后面的数据就是直线的终点
    小写的l:后面的数据是增量-->
    <path
        android:name="right"
        android:pathData="
        M 128,13
        l 3,3
        l 5,-6"
        android:strokeWidth="2"
        android:strokeColor="@color/colorAccent"
        android:strokeLineCap="round"/>

</vector>

接着就是控制对勾和直线的动画效果,第一种动画:直线显示出来,对勾隐藏:

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/et_bg">
    <target
        android:name="bottom"
        android:animation="@animator/bottom_line_out" />
    <target
        android:name="right"
        android:animation="@animator/right_default_gone" />
</animated-vector>

bottom_line_out动画代码:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="300"
                android:propertyName="trimPathEnd"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType"
    >
</objectAnimator>

right_default_gone动画代码:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1"
    android:valueFrom="1"
    android:valueTo="1"
    android:propertyName="trimPathStart"
    android:valueType="floatType">
</objectAnimator>

第二种动画就是直线和对勾隐藏,第三种动画就是对勾显示,与第一种动画都是类似就不在此贴代码了。

总结

通过本篇文章可以对SVG有一个比较基础的了解,以及如何在 Android下实现通过 SVG 实现我们的自定义动画效果!

Thanks!

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

推荐阅读更多精彩内容