Android中的SVG

SVG是什么

Android 5.0(Lollipop, API 21)后,新增了 <vector> 标签,以 VectorDrawable 的形式支持 SVG 类型矢量图形(SVG本质为XML标记描述的图形)。

Vector图像刚发布的时候,是只支持Android 5.0+的,对于Android pre-L的系统来说,并不能使用,所以,可以说那时候的Vector并没有什么卵用。不过自从AppCompat 23.2之后,Google对p-View的Android系统也进行了兼容,也就是说,Vector可以使用于Android 2.1以上的所有系统,只需要引用com.android.support:appcompat-v7:23.2.0以上的版本就可以了,这时候,Vector应该算是迎来了它的春天。

SVG:可缩放矢量图形是基于可扩展标记语言(标准通用标记语言的子集),用于描述二维矢量图形的一种图形格式。它由万维网联盟制定,是一个开放标准。SVG 使用 XML 格式定义图形

与其他图像格式相比,使用 SVG 的优势在于:

  • 可被非常多的工具读取和修改(比如记事本)
  • 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。
  • 是可伸缩的
  • 图像可在任何的分辨率下被高质量地打印
  • 可在图像质量不下降的情况下被放大
  • 图像中的文本是可选的,同时也是可搜索的(很适合制作地图)
  • 可以与 Java 技术一起运行
  • 是开放的标准
  • 文件是纯粹的 XML
  • 成熟、稳定,前端已经非常广泛的进行使用了

Vector: 在Android中指的是Vector Drawable,也就是Android中的矢量图。

因此,可以说Vector就是Android中的SVG实现,因为Android中的Vector并不是支持全部的SVG语法,也没有必要,因为完整的SVG语法是非常复杂的,但已经支持的SVG语法已经够用了,特别是Path语法,几乎是Android中Vector的标配,详细可以参考:http://www.w3.org/TR/SVG/paths.html

Android中SVG创建

Android 2.3可以直接新建一个vector的xml文件,如图

Android中创建vector文件

创建过程中我们可以对该vector文件做一些基本设置

vector文件配置

相关设置说明:

设置说明
Local file可以选择本地的SVG文件
Material Icon可以选择自带的SVG
Name当然就是xml文件的名字
Size图片大小
Override勾选后替代默认的大小
Opactity透明度
Enable atuo mirroring for RTL layout用于镜像显示

最后生成一个xml文件如下:

1
2
3
4
5
6
7
8
9
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="40dp"
android:viewportHeight="40.0"
android:viewportWidth="40.0"
android:width="40dp">
<path
android:fillColor="#FF000000"
android:pathData="...省略"/>
</vector>

Android以一种简化的方式对SVG进行了兼容,这种方式就是通过使用它的Path标签,通过Path标签,几乎可以实现SVG中的其它所有标签,虽然可能会复杂一点,但这些东西都是可以通过工具来完成的,所以,不用担心写起来会很复杂。

解释一下xml中的几个标签:

  • android:widthandroid:height:定义图片的宽高
  • android:viewportHeightandroid:viewportWidth:定义图像被划分的比例大小,例如例子中的40,即把40dp大小的图像划分成40份,后面Path标签中的坐标,就全部使用的是这里划分后的坐标系统。

Path

Path指令(支持的)解析如下所示:

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 curve(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轴水平向下
  • 所有指令大小写均可。大写绝对定位,参照全局坐标系;小写相对定位,参照父容器坐标系
  • 指令和数据间的空格可以省略
  • 同一指令出现多次可以只用一个

关于这些语法,开发者需要的并不是全部精通,而是能够看懂即可,其它的都可以交给工具来实现。

从PNG到SVG

设计师

要从一般使用的PNG图像转换到SVG图像,对于设计师来说,并不是一件难事,因为大部分的设计工具(PS、Illustrator等等)都支持导出各种格式的图像,如PNG、JPG,当然,也包括SVG,因此,设计师可以完全按照原有的方式进行设计,只是最后导出的时候,选择SVG即可。

程序员

不要求开发者都去学习使用这些设计工具,开发者可以利用一些工具,自己转换一些比较基础的图像,http://inloop.github.io/svg2android/ 就是这样一个非常牛逼的网站,可以在线将普通图像转换为Android Vector Drawable。或者,还可以使用SVG的编辑器来进行SVG图像的编写,例如http://editor.method.ac/

Google的兼容之路

只兼容L+

Vector是在Android L中提出来的新概念,所以在刚开始的时候是只兼容L+的。

Gradle Plugin 1.5的兼容

从Gradle Plugin 1.5开始,Google支持了一种兼容方式,即在Android L之上,使用Vector,而在L之下,则使用Gradle将Vector生成PNG图像。

Android gradle plugin 1.5发布以后,加入了一个跟VectorDrawable有关的新功能。Android build tools 提供了另外一种解决兼容性的方案,如果编译的版本是5.0之前的版本,那么build tools 会把VectorDrawable生成对应的png图片,这样在5.0以下的版本则使用的是生成的png图,而在5.0以上的版本中则使用VectorDrawable.在build.gradle添加generatedDensities配置,可以配置生成的png图片的密度。

AppCompat23.2的兼容

从AppCompat23.2开始,Google开始支持在低版本上使用Vector。

静态Vector图像

我们有很多方法能够得到这些Vector,那么如何使用它们呢,Android 5.0以上的使用就不讲了,不太具有普遍代表性,我们从pre-L版本的兼容开始做起。

pre-L版本兼容

VectorDrawableCompat依赖于AAPT的一些功能,它能保持最近矢量图使用的添加的属性ID,以便他们可以被pre-L版本之前的引用。

在Android 5.0之前使用Vector,需要aapt来对资源进行一些处理,这一过程可以在aapt的配置中进行设置,如果没有启用这样一个flag,那么在5.0以下的设备上运行就会发生android.content.res.Resources$NotFoundException。

首先,你需要在项目的build.gradle脚本中,增加对Vector兼容性的支持,代码如下所示:

使用Gradle Plugin 2.0以上:

1
2
3
4
5
6
android {

defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}

使用Gradle Plugin 2.0以下,Gradle Plugin 1.5以上:

1
2
3
4
5
6
7
8
9
10
android {
defaultConfig {
// Stops the Gradle plugin’s automatic rasterization of vectors
generatedDensities = []
}
// Flag to tell aapt to keep the attribute ids around
aaptOptions {
additionalParameters "--no-version-vectors"
}
}

像前面提到的,这种兼容方式实际上是先关闭AAPT对pre-L版本使用Vector的妥协,即在L版本以上,使用Vector,而在pre-L版本上,使用Gradle生成相应的PNG图片,generatedDensities这个数组,实际上就是要生成PNG的图片分辨率的数组,使用appcompat后就不需要这样了。

当然,最重要的还是添加appcompat的支持:

1
compile 'com.android.support:appcompat-v7:23.4.0'

同时,确保你使用的是AppCompatActivity而不是普通的Activity。

Vector图像的使用

有了静态的Vector图像,就可以在控件中使用了。可以发现,这里我们使用的都是普通的ImageView,好像并不是AppcomatImageView,这是因为使用了Appcomat后,系统会自动把ImageView转换为AppcomatImageView。

ImageView和ImageButton

对于ImageView这样的控件,要兼容Vector图像,只需要将之前的android:src属性,换成app:srcCompat即可,示例代码如下所示:

1
2
3
4
5
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/vector_image"/>

在代码中设置的话,代码如下所示:

1
2
ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setImageResource(R.drawable.vector_image);

另外,setBackgroundResource也是可以设置Vector的API

Button

Button并不能直接使用app:srcCompat来使用Vector图像,需要通过Selector来进行使用,首先,创建两个图像,用于Selector的两个状态,代码如下所示:

selector1.xml

1
2
3
4
5
6
7
8
9
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M14.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

selector2.xml

1
2
3
4
5
6
7
8
9
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

selector.xml

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/selector1" android:state_pressed="true"/>
<item android:drawable="@drawable/selector2"/>
</selector>

非常简单,只是把普通的Selector中的图像换成了Vector图像而已,接下来,在Button中使用这个Selector即可:

1
2
3
4
5
<Button
android:id="@+id/btn"
android:layout_width="70dp"
android:layout_height="70dp"
android:background="@drawable/selector"/>

然后运行,如果你认为可以运行,那就是太天真了,都说了是兼容,怎么能没有坑呢,这里就是一个坑……

这个坑实际上是有历史渊源的,Google的一位开发者在博客中写到:

First up, this functionality was originally released in 23.2.0, but then we found some memory usage and Configuration updating issues so we it removed in 23.3.0. In 23.4.0 (technically a fix release) we’ve re-added the same functionality but behind a flag which you need to manually enable.

实际上,他们的这个改动,就影响了类似DrawableContainers(DrawableContainers which reference other drawables resources which contain only a vector resource)这样的类,它的一个典型,就是Selector(StateListDrawable也是)。这个开发者在文中提到的flag,就是下面的这段代码,放在Activity的前面就可以了:

1
2
3
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

开启这个flag后,你就可以正常使用Selector这样的DrawableContainers了。同时,你还开启了类似android:drawableLeft这样的compound drawable的使用权限,以及RadioButton的使用权限,以及ImageView’s src属性。

RadioButton

RadioButton的Button同样可以定义,代码如下所示:

1
2
3
4
<RadioButton
android:layout_width="50dp"
android:layout_height="50dp"
android:button="@drawable/selector"/>

创建动态的Vector

动态Vector才是Android Vector Drawable的精髓所在,动态的Vector需要通过animated-vector标签来进行实现,它就像一个粘合剂,将控件与Vector图像粘合在了一起,一个基础的animated-vector代码如下所示:

1
2
3
4
5
6
7
8
9
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/XXXXX1">

<target
android:name="left"
android:animation="@animator/XXXXX2"/>

</animated-vector>

实际上这里面只有两个重点是需要关注的,XXXXX1和XXXXX2。一个具体的示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_arrow">

<target
android:name="left"
android:animation="@animator/anim_left"/>

<target
android:name="right"
android:animation="@animator/anim_right"/>

</animated-vector>

这里表示目标图像是drawable/ic_arrow,对left、right分别使用了anim_left、anim_right动画。这里的name属性,就是在静态Vector图像中group或者path标签的name属性。

目标图像

XXXXX1是目标Vector图像,也就是静态的Vector图像,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">

<group android:name="left">
<path
android:fillColor="#FF000000"
android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3"/>
</group>

<group android:name="right">
<path
android:fillColor="#FF000000"
android:pathData="M14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4"/>
</group>

</vector

可以发现,这里的Vector图像比之前我们看见的要多了一个group标签。group标签的作用有两个:

  • 对Path进行分组,由于我们后面需要针对Path进行动画,所以可以让具有同样动画效果的Path在同一个Group中
  • 拓展动画效果,单个的path标签是没有translateX和translateY属性的,因此无法使用属性动画来控制path translateY,而group标签是有的,所以我们需要先将相关的path标签元素包裹在一个个的group标签中.

动态效果

XXXXX2实际上就是模板要实现的动画,动画效果实际上就是基础的属性动画,例如:

anim_left.xml

1
2
3
4
5
6
7
8
9
10
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:interpolator/anticipate_overshoot"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="-10"
android:valueType="floatType"/>

anim_right.xml

1
2
3
4
5
6
7
8
9
10
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:interpolator/anticipate_overshoot"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="10"
android:valueType="floatType"/>

在代码中使用

1
2
3
4
5
6
ImageView imageView = (ImageView) findViewById(R.id.iv);
AnimatedVectorDrawableCompat animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(
this, R.drawable.square_anim
);
imageView.setImageDrawable(animatedVectorDrawableCompat);
((Animatable) imageView.getDrawable()).start();

动态Vector兼容性问题

向下兼容问题

一说到兼容,就不得不提到坑,几乎所有的为了兼容而做的改动,都会留下一些不可填满的坑,动态Vector动画也不例外,虽然Google已经对Vector图像进行了Android 2.1以上的兼容,但对于动态Vector动画,还是有很多限制的,例如:

  • Path Morphing,即路径变换动画,在Android pre-L版本下是无法使用的。
  • Path Interpolation,即路径插值器,在Android pre-L版本只能使用系统的插值器,不能自定义。
  • Path Animation,即路径动画,这个一般使用贝塞尔曲线来代替,所以没有太大影响。

向上兼容问题

除了在低版本上的兼容性问题,在L版本以上,也存在兼容性问题,即继承了AppCompatActivity的界面,如果直接设置ImageView的srcCompat,那么Path Morphing动画是无法生效的,因为默认的AppCompatActivity已经默认使用ImageViewCompat给转换了,但是AnimatedVectorDrawableCompat是不支持Path Morphing动画的,所以,在AppCompatActivity界面里面就无效了。

解决办法很简单,即使用代码来给ImageView添加动画:

1
2
3
4
5
6
ImageView imageView = (ImageView) view;
AnimatedVectorDrawable morphing = (AnimatedVectorDrawable) getDrawable(morphing);
imageView.setImageDrawable(morphing);
if (morphing != null) {
morphing.start();
}

注意不要使用AnimatedVectorDrawableCompat即可。

抽取string兼容问题

开发者有时候为了代码简洁可能会把Vector图像中的pathData放到string.xml中,然后在Vector图像中引用string。

但这种方式如果通过生成png来兼容5.0以下机型的话,会报pathData错误,编译器不会去读取string.xml,只能把pathData写到Vector图像中,动画文件中也是一样,这也是为了兼容做出的牺牲吗,不得而知。

其它兼容问题

其它非常奇怪、诡异、不能理解的兼容性问题,只能通过版本文件夹的方式来进行兼容了,例如drawable-v21和drawable,分别创建两个文件名相同的资源在两个文件夹下,这样在21以上版本,会使用drawable-v21的资源,而其它会使用drawable下的资源。

动态Vector进阶

用好ObjectAnimator

所谓Vector动画进阶,实际上就是在利用ObjectAnimator的一些属性,特别是trimPathStart、trimPathEnd这两个针对Vector的属性(要注意pathData属性不兼容pre-L)。

这两个属性的官方文档如下所示:

1
2
3
4
5
6
android:trimPathStart
The fraction of the path to trim from the start, in the range from 0 to 1.
android:trimPathEnd
The fraction of the path to trim from the end, in the range from 0 to 1.
android:trimPathOffset
Shift trim region (allows showed region to include the start and end), in the range from 0 to 1.

其实很简单,就是一个图像的截取,设置一个比例即可,即当前绘制多少比例的图像,其余部分不绘制,Start和End分别就是从PathData的Start和End开始算,大家参考几个例子就能理解了。

理解Path Morph

Path Morph动画是Vector动画的一个高级使用,说到底,也就是两个PathData的转换,但是这种转换并不是随心所欲的,对于两个PathData,它们能进行Path Morph的前提是,它们具有相同个数的关键点,即两个路径的变换,只是关键点的坐标变化,掌握了这一个基本原理,实现Path Morph就非常容易了。

学习Vector

在Github上有一个开源的Vector的动画Demo库,地址如下所示:

https://github.com/xuyisheng/VectorDemo

这个Demo分为两部分,一部分是可以兼容Android pre-L版本和L+版本的Vector动画,另一部分(通过Actionbar的按钮切换)是只能兼容L+的Vector动画。

每个Vector动画,基本都包含四部分内容,即:

  • Vector:图像资源
  • Animated-vector:动画、图像粘合剂
  • ObjectAnimator:动画资源
  • 代码:启动动画

每个Vector动画通过这四个部分去进行分析,就非常清晰了。

Vector性能问题

  1. Bitmap的绘制效率并不一定会比Vector高,它们有一定的平衡点,当Vector比较简单时,其效率是一定比Bitmap高的,所以,为了保证Vector的高效率,Vector需要更加简单,PathData更加标准、精简,当Vector图像变得非常复杂时,就需要使用Bitmap来代替了

  2. Vector适用于ICON、Button、ImageView的图标等小的ICON,或者是需要的动画效果,由于Bitmap在GPU中有缓存功能,而Vector并没有,所以Vector图像不能做频繁的重绘

  3. Vector图像过于复杂时,不仅仅要注意绘制效率,初始化效率也是需要考虑的重要因素

  4. SVG加载速度会快于PNG,但渲染速度会慢于PNG,毕竟PNG有硬件加速,但平均下来,加载速度的提升弥补了绘制的速度缺陷。

Google的这个视频中,已经对Vector的效率问题做了解释,可以参考下:

https://www.youtube.com/watch?v=wlFVIIstKmA&feature=youtu.be&t=6m3s

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Babel Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome Class ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage ES2015 ESLint Element Error Exception Extensions File FileProvider Flow Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Style Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code VUE ValueAnimator ViewPropertyAnimator Vue Vue.js Web Web前端 Workbench api apk bookmark by关键字 cli compileOnly computed css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge methods mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode watch webpack 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指令 指针 插件 插值 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 渲染 源码 源码分析 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 绑定 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 脚手架 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×