浅谈SVG及矢量图在Android的应用

1. 图像基础

图像分为矢量图和栅格图两种,这两张格式最直观的区别是矢量图可以无限放大而不失真,而矢量图放大或缩小就会因为失真而变得模糊,可以参加如下图片。


对比图

1.1栅格图

栅格图也称位图,它由像素点组成,每个像素点分配特定的色值和位置。我们平时生活和工作中遇到的图像大部分都是栅格图,它对图片在空间和亮度上都做了离散化。我们先拿最简单的一张黑白位图举例:


黑白位图

假如这个图片是300x300的,即它由300x300个像素点组成,每个像素点要么是黑色的,要么是白色的。

如果像让图片表达更丰富点,可以使用灰度图,参见下面这张图像处理课中的经典图片。一般灰度图每个像素点用0~255表示灰度等级,0表示白色,255表示黑色,这样的话一张300x300的图片就可以用 unsigned int8[300][300]来表示这张图片。

灰度图

那支持彩色图片,也同样原理了,每个像素点支持RGB三原色,三原色可以产生其他颜色。正常情况下RGB每种颜色也是支持0~255个色度。为了支持图片透明,有ARGB格式,前面的A表示透明度,这时一个像素点大小为int32。写程序时有时图片占用内存太大,会把加载时的RGB888改成RGB555,它的原理即让支持256个色度的颜色改为了128个色度,节省了空间牺牲了颜色的饱和度。

上面讲到的图片放大或者缩小,可以看数字图像处理教程中的公式,放大或者缩小只是对原图像素位置做矩阵运算得到新的位置,对于放大新产生的像素点或者缩小时不是整数位的像素点,需要做插值处理。由此可以知道失真是必然的。

缩放

1.2 矢量图

矢量的定义为既有大小又有方向的几何对象,那矢量图顾名思义为由矢量组成的图像。矢量图由矢量定义的直线和曲线组成,可以放在特定位置使用颜色填充,它基于数学公式计算获得,所以无论放大还是缩小都不会失真。
可以举个抽象的栗子,图像是个红色的圆形,或者是一个绿色的等边三角形。这样的图像无论你放大多少倍都不会失真的。如下图为字体库导出的矢量图。


字体库

根据两种图像的实现原理,不难得出他们的优缺点:矢量图只需要存储函数和一些关键点和方向信息,所以存储空间非常小,对它进行放大缩小旋转等操作也不会有任何失真。由于图像是临时计算出来的,对于运算资源需求大,同时对于颜色描述能力不够。

2. SVG在android中的使用

2.1 SVG简介

SVG全称为Scalable Vector Graphics,是由(W3C)联盟指定的基于XML的矢量图形格式,是一个开放的标准。
它以<svg>标签开始,以</svg>标签结束。标签可以添加一些属性,也可以嵌套一些其他标签。下面我们可以看几个例子

<svg width="100%" height="100%" version="1.1">
    <circle cx="100" cy="50" r="40" stroke="black"stroke-width="2" fill="red"/>
</svg>
圆形
<svg width="100%" height="100%" version="1.1">
    <polygon points="220,100 300,210 170,250 123,234" style="fill:#cccccc;stroke:#000000;stroke-width:1"/>
</svg>
多边形
<svg width="100%" height="100%" version="1.1">
<path d="M153 334
C153 334 151 334 151 334
C151 339 153 344 156 344
C164 344 171 339 171 334
C171 322 164 314 156 314
C142 314 131 322 131 334
C131 350 142 364 156 364
C175 364 191 350 191 334
C191 311 175 294 156 294
C131 294 111 311 111 334
C111 361 131 384 156 384
C186 384 211 361 211 334
C211 300 186 274 156 274"
style="fill:white;stroke:red;stroke-width:2"/>
</svg>
路径

通过上面的几个例子,基本可以窥视SVG用法,通过一些标签和属性,完成各种图形。查看文档知道它有圆形、矩形、多边形支持一些常规需求,比较复杂的图像可以使用路径(path)来实现。当然复杂的图形,可以使用其他工具去完成然后生成路径。

2.2 SVG图创建和获取

1)官方自带Meterial Icon,官方自带了一套图片库,可以满足常用场景需求。系统鼠标选中drawable文件夹, New -> Vector Asset,然后出现

选择

上面默认勾选"Material Icon",然后点击Android图标,进入官方自带的SVG图标库:
图片库

我们可以选一个图标看一下(点击右边的Preview可以预览图片效果):
SVG图片

2)本地导入。上面的提到的勾选框,选择Local file可以导入本地已有的SVG图片或PSD。可以找专业设计师设计图标,也可以从一些图片库上面去下载需要的图标,例如阿里的iconfont(http://iconfont.cn/),还有一些第三方工具例如Vector Magic,将png、jpg等图片格式导出SVG,也有在线导出svg的工具如http://editor.method.ac/

2.3 在Android中应用

正常来说,矢量图在Android应用很简单,我们把它当做普通图片来使用就可以了。现在的主要问题是,Android是从5.0版本才开始支持矢量图的,为了适配低版本手机,需要额外做一些工作:
1)ImageView中矢量图的使用。一般使用兼容包里的AppCompatImageView替换ImageView,在布局中使用“app:srcCompat”代替“app:src”。如果当前的Activity继承了AppCompatActivity,可以直接使用ImageView,编译时会自动替换,不过属性还需要使用“”app:srcCompat。

<ImageView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:scaleType="fitXY"
        app:srcCompat="@drawable/ic_clear_black_24dp"
        />
  1. background中使用矢量图。低版本普通控件background是不支持直接使用矢量图的,对矢量图的支持依赖于StateListDrawable,InsetDrawable,LayerDrawable,LevelListDrawable,RotateDrawable。所以可以通过使用selector来支持矢量图。
<Button
    android:layout_width="200dp"
    android:layout_height="50dp"
    android:background="@drawable/selector"
    />

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_clear_black_24dp" android:state_pressed="true"/>
    <item android:drawable="@drawable/ic_clear_black_24dp"/>
</selector>

这样还不够,还需要在Activity前面添加如下代码

static {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

跟进源码可以看到只是修改一个静态变量的值,所以图省事可以在application里面添加,这样所有的Activity控件都支持vector了,但是看到它的注释里提到,开启这个flag会增加内存开销,并且影响更新Configuration实例。具体的影响目前还没有测试。
开启这个这个flag后,android:drawableLeft、RadioButton已经ImageView的src属性都可以正常使用矢量图了。

3. 矢量动画

3.1官方矢量动画

Android提供了AnimatedVectorDrawable来实现矢量动画。它有几个重要方法需要了解:
start:开始播放动画
stop:停止动画
registerAnimationCallback: 注册一个监听者,可以监听到动画开始和结束事件。矢量图形的很多属性都支持动画,最常用的主要是下面三类:

  • 1)属性变换类。主要属性包括alpha、rotation、scaleX、scaleY、translateX、translateY等,这个跟常见的补间动画一样。
  • 2)路径绘制类,这个主要使用path标签的trimPathStart和trimPathEnd属性。它可以按照path的路径逐步画出或者擦除图片。我们可以看下图(盗图:),由path绘制的一个圆形和一个对号,可以使用下面代码实现路径绘制:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:interpolator/linear"
    android:propertyName="trimPathEnd"
    android:valueFrom="0"
    android:valueTo="1"
    android:valueType="floatType" />
路径绘制
    1. 路径变换类,这个主要是指定两个path,系统自动生成动画,从第一个path图形变换为第二个path图形。但是并不是所有的path间都支持这种变换,如果两个path间不支持时系统会抛出异常。这里有一个开源项目可以帮你优化path使其支持路径变换动画。动画代码如下,valueFrom指向动画开始的path,valueTo指向动画变换后的path
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="3000"
  android:propertyName="pathData"
  android:valueFrom="@string/path_begin"
  android:valueTo="@string/path_end"
  android:valueType="pathType"
  android:interpolator="@android:anim/accelerate_interpolator"/>
路径变换
路径变换

3.2 Lottie,让矢量动画更简单

上面讲到了官方矢量动画的实现,客观讲使用起来还是有点复杂的,首先要准备相关的矢量图资源,然后再写动画文件,并且根据上面讲到的知识,一些复杂动画实现起来还是很麻烦的。另外一个动画好不容易在Android平台上实现了,iOS上面可能还需要做同样的实现工作,介于以上原因,airbnb实现了一套通用矢量动画方案lottie,可以支持android、iOS、RN和Web。它的思路就是设计同学使用相关工具(例如After Effects)做出矢量动画,导出为一个json文件,然后这个json文件就可以拿来在不同的系统上使用。我们看下Android中的使用:

  • 1)引入库
compile 'com.airbnb.android:lottie:2.1.0'
    1. 使用动画。lottie最低支持到api 16,支持从assets中和res/raw中加载动画资源,官方建议使用raw,因为可以使用R文件静态引用到资源文件,下面一个例子:
<com.airbnb.lottie.LottieAnimationView
        android:id="@+id/animation_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        app:lottie_rawRes="@raw/hello_world"
        // 或者
        app:lottie_fileName="hello_world.json"

        app:lottie_loop="true"
        app:lottie_autoPlay="true" />

就这么简单就完成了矢量动画的显示,当然还有一些其他设置例如停止动画、监听动画等可以参考官方文档
另外lottie还支持从网络下载矢量动画资源:

LottieAnimationView animationView = ...
// This allows lottie to use the streaming deserializer mentioned above.
JsonReader jsonReader = new JsonReader(new StringReader(json.toString()))
animationView.setAnimation(jsonReader)
animationView.playAnimation

使用起来非常简洁,这里有一个网站提供大量动画素材,大家可以参考使用。

4. 参考

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

推荐阅读更多精彩内容