Android 中 vector 的使用
一、兼容性、特点和性能。
1、兼容性
Vector图像刚发布的时候,兼容性是一个很大的问题,目前来说虽然问题不是很大,但仍需要注意。Vector Drawable相对于普通的Drawable来说,有以下几个好处:
- Vector图像可以自动进行适配,不需要通过分辨率来设置不同的图片
- Vector图像可以大幅减少图像的体积,同样一张图,用Vector来实现,可能只有PNG的几十分之一
- 使用简单,很多设计工具,都可以直接导出SVG图像,从而转换成Vector图像
- 功能强大,不用写很多代码就可以实现非常复杂的动画
- 成熟、稳定,前端已经非常广泛的进行使用了
2、性能
Bitmap 的绘制效率并不一定会比 Vector 高,它们有一定的平衡点,当 Vector 比较简单时,其效率是一定比 Bitmap 高的,所以,为了保证 Vector 的高效率,Vector 需要更加简单,PathData 更加标准、精简,当 Vector 图像变得非常复杂时,就需要使用 Bitmap 来代替了。
3、应用
Vector 适用于 ICON、Button、ImageView的图标等小的ICON,或者是需要的动画效果,由于 Bitmap 在 GPU 中有缓存功能,而 Vector 并没有,所以 Vector 图像不能做频繁的重绘。
Vector图像过于复杂时,不仅仅要注意绘制效率,初始化效率也是需要考虑的重要因素。
SVG加载速度会快于PNG,但渲染速度会慢于PNG,毕竟PNG有硬件加速,但平均下来,加载速度的提升弥补了绘制的速度缺陷。
二、Vector图像
1、概念
首先,需要讲解两个概念:SVG 和 Vector。
SVG,即Scalable Vector Graphics 矢量图,这种图像格式在前端中已经使用的非常广泛了。
Vector,在Android中指的是Vector Drawable,也就是Android中的矢量图。
因此,可以说 Vector 就是 Android 中的 SVG 实现,因为 Android 中的 Vector 并不是支持全部的 SVG 语法,也没有必要,因为完整的 SVG 语法是非常复杂的,但已经支持的SVG 语法已经够用了,特别是 Path 语法,几乎是 Android 中 Vector 的标配。
2.Vector语法简介
Android以一种简化的方式对SVG进行了兼容,这种方式就是通过使用它的Path标签,通过Path标签,几乎可以实现SVG中的其它所有标签,虽然可能会复杂一点,但这些东西都是可以通过工具来完成的,所以,不用担心写起来会很复杂。
Path指令解析如下所示:
[1]支持的指令
- 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():关闭路径
[2]使用原则
坐标轴为以(0,0)为中心,X轴水平向右,Y轴水平向下
所有指令大小写均可。大写绝对定位,参照全局坐标系;小写相对定位,参照父容器坐标系
指令和数据间的空格可以省略
同一指令出现多次可以只用一个
注意,'M'处理时,只是移动了画笔, 没有画任何东西。 它也可以在后面给出上同时绘制不连续线。
关于这些语法,开发者需要的并不是全部精通,而是能够看懂即可,其它的都可以交给工具来实现。
三、从 PNG 到 SVG
设计师
要从一般使用的PNG图像转换到SVG图像,对于设计师来说,并不是一件难事,因为大部分的设计工具(PS、Illustrator等等)都支持导出各种格式的图像,如PNG、JPG,当然,也包括SVG,因此,设计师可以完全按照原有的方式进行设计,只是最后导出的时候,选择SVG即可。
程序员
不要求开发者都去学习使用这些设计工具,开发者可以利用一些工具,自己转换一些比较基础的图像,就是这样一个非常牛逼的网站,可以在线将普通图像转换为Android Vector Drawable。或者,还可以使用SVG的编辑器来进行SVG图像的编写。
四、使用Android Studio
1.生成图片
利用Android Studio的Vector Asset,可以非常方便的创建Vector图像,甚至可以直接通过本地的SVG图像来生成Vector图像。
2.Vector图像
一个基本的Vector图像,实际上也是一个xml文件。如下:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportHeight="500"
android:viewportWidth="500">
<path
android:name="square"
android:fillColor="#000000"
android:pathData="M100,100 L400,100 L400,400 L100,400 z"/>
</vector>
这里需要解释下这里的几个标签:
- android:height:定义图片的宽高
- android:viewportWidth:定义图像被划分的比例大小,例如例子中的500,即把200dp大小的图像划分成500份,后面Path标签中的坐标,就全部使用的是这里划分后的坐标系统。这样做有一个非常好的作用,就是将图像大小与图像分离,后面可以随意修改图像大小,而不需要修改PathData中的坐标。
- android:fillColor:PathData中的这些属性就不详细讲了,与Canvas绘图的属性基本类似。
3.在控件中使用
[1]ImageView\ImageButton
可以发现,这里我们使用的都是普通的ImageView,好像并不是AppcomatImageView,这是因为使用了Appcomat后,系统会自动把ImageView转换为AppcomatImageView。
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/vector_image"/>
在代码中设置的话,代码如下所示:
ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setImageResource(R.drawable.vector_image);
setBackgroundResource也是可以设置Vector的API
[2]Button
Button并不能直接使用app:srcCompat来使用Vector图像,需要通过Selector来进行使用,首先,创建两个图像,用于Selector的两个状态,代码如下所示:
selector1.xml
<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
<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
<?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即可:
<Button
android:id="@+id/btn"
android:layout_width="70dp"
android:layout_height="70dp"
android:background="@drawable/selector"/>
它的一个典型,就是Selector(StateListDrawable也是)。这个开发者在文中提到的flag,就是下面的这段代码,放在Activity的前面就可以了:
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
开启这个flag后,你就可以正常使用Selector这样的DrawableContainers了。同时,你还开启了类似android:drawableLeft这样的compound drawable的使用权限,以及RadioButton的使用权限,以及ImageView’s src属性。
[3].RadioButton
RadioButton的Button同样可以定义,代码如下所示:
<RadioButton
android:layout_width="50dp"
android:layout_height="50dp"
android:button="@drawable/selector"/>
四、动态Vector基础
动态 Vector 才是Android Vector Drawable 的精髓所在,动态的Vector需要通过 animated-vector 标签来进行实现,它就像一个粘合剂,将控件与 Vector 图像粘合在了一起,一个基础的 animated-vector 代码如下所示:
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vector_img">
<target
android:name="left"
android:animation="@animator/animate"/>
</animated-vector>
实际上这里面只有两个重点是需要关注的,vector_img 和 animate。一个具体的示例如下所示
<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属性。
如果 animated-vector 标签在现在的 Android Studio 中报错,这个并不影响编译和运行,属于 Android Studio 的Bug。
1.目标图像
vector_img 是目标 Vector 图像,也就是静态的 Vector 图像,例如:
<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标签中.
2.动画效果
animate实际上就是模板要实现的动画,动画效果实际上就是基础的属性动画,例如:
anim_left.xml
<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
<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"/>
3.在代码中使用
ImageView imageView = (ImageView) findViewById(R.id.iv);
AnimatedVectorDrawableCompat animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(
this, R.drawable.square_anim
);
imageView.setImageDrawable(animatedVectorDrawableCompat);
((Animatable) imageView.getDrawable()).start();
4.动态Vector兼容性问题
[1].向下兼容问题
一说到兼容,就不得不提到坑,几乎所有的为了兼容而做的改动,都会留下一些不可填满的坑,动态Vector动画也不例外,虽然Google已经对Vector图像进行了Android 2.1以上的兼容,但对于动态Vector动画,还是有很多限制的,例如:
- Path Morphing,即路径变换动画,在Android pre-L版本下是无法使用的。
- Path Interpolation,即路径插值器,在Android pre-L版本只能使用系统的插值器,不能自定义。
- Path Animation,即路径动画,这个一般使用贝塞尔曲线来代替,所以没有太大影响。
[2].向上兼容问题
除了在低版本上的兼容性问题,在L版本以上,也存在兼容性问题,即继承了AppCompatActivity的界面,如果直接设置ImageView的srcCompat,那么Path Morphing动画是无法生效的,因为默认的AppCompatActivity已经默认使用ImageViewCompat给转换了,但是AnimatedVectorDrawableCompat是不支持Path Morphing动画的,所以,在AppCompatActivity界面里面就无效了。
解决办法很简单,即使用代码来给ImageView添加动画:
ImageView imageView = (ImageView) view;
AnimatedVectorDrawable morphing = (AnimatedVectorDrawable) getDrawable(morphing);
imageView.setImageDrawable(morphing);
if (morphing != null) {
morphing.start();
}
注意不要使用AnimatedVectorDrawableCompat即可。
[3].抽取string兼容问题
开发者有时候为了代码简洁可能会把Vector图像中的pathData放到string.xml中,然后在Vector图像中引用string。
但这种方式如果通过生成png来兼容5.0以下机型的话,会报pathData错误,编译器不会去读取string.xml,只能把pathData写到Vector图像中,动画文件中也是一样,这也是为了兼容做出的牺牲吗,不得而知。
[4].其它兼容问题
其它非常奇怪、诡异、不能理解的兼容性问题,只能通过版本文件夹的方式来进行兼容了,例如drawable-v21和drawable,分别创建两个文件名相同的资源在两个文件夹下,这样在21以上版本,会使用drawable-v21的资源,而其它会使用drawable下的资源。
五、动态 Vector 进阶
1.用好 ObjectAnimator
所谓 Vector 动画进阶,实际上就是在利用 ObjectAnimator 的一些属性,特别是 trimPathStart、trimPathEnd 这两个针对 Vector 的属性(要注意pathData属性不兼容pre-L)。
这两个属性的官方文档如下所示:
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开始算,大家参考几个例子就能理解了。
2.理解Path Morph
Path Morph动画是Vector动画的一个高级使用,说到底,也就是两个PathData的转换,但是这种转换并不是随心所欲的,对于两个PathData,它们能进行Path Morph的前提是,它们具有相同个数的关键点,即两个路径的变换,只是关键点的坐标变化,掌握了这一个基本原理,实现Path Morph就非常容易了。
六、总结
每个Vector动画,基本都包含四部分内容,即:
- Vector:图像资源
- Animated-vector:动画、图像粘合剂
- ObjectAnimator:动画资源
- 代码:启动动画
每个Vector动画通过这四个部分去进行分析,就非常清晰了。