学向勤中得,萤窗万卷书。这篇文章主要讲述Android Drawable Mipmap Vector使用及Vector兼容相关的知识,希望能为你提供帮助。
android Vector曲折的兼容之路 两年前写书的时候,
就在研究Android L提出的Vector,
可研究下来发现,
完全不具备兼容性,
相信这也是它没有被广泛使用的一个原因,
经过Google的不懈努力,
现在Vector终于迎来了它的春天。
文章图片
在文章后面, 会给出本文的Demo和效果图, 并开源在GithubVector Drawable Android 5.0发布的时候, Google提供了Vector的支持。Vector Drawable相对于普通的Drawable来说, 有以下几个好处:
- Vector图像可以自动进行适配, 不需要通过分辨率来设置不同的图片
- Vector图像可以大幅减少图像的体积, 同样一张图, 用Vector来实现, 可能只有PNG的几十分之一
- 使用简单, 很多设计工具, 都可以直接导出SVG图像, 从而转换成Vector图像
- 功能强大, 不用写很多代码就可以实现非常复杂的动画
- 成熟、稳定, 前端已经非常广泛的进行使用了
如何获得Vector图像 概念
首先, 需要讲解两个概念——SVG和Vector。
SVG, 即Scalable Vector Graphics 矢量图, 这种图像格式在前端中已经使用的非常广泛了, 详见WIKI: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
Vector, 在Android中指的是Vector Drawable, 也就是Android中的矢量图, 详见: https://developer.android.com/reference/android/graphics/drawable/VectorDrawable.html
因此, 可以说Vector就是Android中的SVG实现, 因为Android中的Vector并不是支持全部的SVG语法, 也没有必要, 因为完整的SVG语法是非常复杂的, 但已经支持的SVG语法已经够用了, 特别是Path语法, 几乎是Android中Vector的标配, 详细可以参考: http://www.w3.org/TR/SVG/paths.html
Vector语法简介
Android以一种简化的方式对SVG进行了兼容, 这种方式就是通过使用它的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 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轴水平向下
- 所有指令大小写均可。大写绝对定位, 参照全局坐标系; 小写相对定位, 参照父容器坐标系
- 指令和数据间的空格可以省略
- 同一指令出现多次可以只用一个
注意, ’M’处理时, 只是移动了画笔, 没有画任何东西。 它也可以在后面给出上同时绘制不连续线。关于这些语法, 开发者需要的并不是全部精通, 而是能够看懂即可, 其它的都可以交给工具来实现。
从PNG到SVG
- 设计师
- 程序员
文章图片
或者, 还可以使用SVG的编辑器来进行SVG图像的编写, 例如http://editor.method.ac/
文章图片
使用Android Studio
利用Android Studio的Vector Asset, 可以非常方便的创建Vector图像, 甚至可以直接通过本地的SVG图像来生成Vector图像, 如图所示:
文章图片
进去之后, 就可以生成Vector图像, 如图所示:
文章图片
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以上:
android {defaultConfig {
vectorDrawables.useSupportLibrary =
true
}
}
使用Gradle Plugin 2.0以下, Gradle Plugin 1.5以上:
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的支持:
compile '
com.android.support:appcompat-v7:23.4.0'
同时, 确保你使用的是AppCompatActivity而不是普通的Activity。
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:width \\ android:height:定义图片的宽高
- android:viewportHeight \\ android:viewportWidth:定义图像被划分的比例大小, 例如例子中的500, 即把200dp大小的图像划分成500份, 后面Path标签中的坐标, 就全部使用的是这里划分后的坐标系统。
- android:fillColor:PathData中的这些属性就不详细讲了, 与Canvas绘图的属性基本类似。
有了静态的Vector图像, 就可以在控件中使用了。
可以发现, 这里我们使用的都是普通的ImageView, 好像并不是AppcomatImageView, 这是因为使用了Appcomat后, 系统会自动把ImageView转换为AppcomatImageView。ImageView\\ImageButton 对于ImageView这样的控件, 要兼容Vector图像, 只需要将之前的android:src属性, 换成app:srcCompat即可, 示例代码如下所示:
<
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的APIButton 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"
/>
然后运行, 如果你认为可以运行, 那就是太天真了, 都说了是兼容, 怎么能没有坑呢, 这里就是一个坑……
这个坑实际上是有历史渊源的, 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的前面就可以了:
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
开启这个flag后, 你就可以正常使用Selector这样的DrawableContainers了。同时, 你还开启了类似android:drawableLeft这样的compound drawable的使用权限, 以及RadioButton的使用权限, 以及ImageView’s src属性。
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/XXXXX1"
>
<
target
android:name=
"
left"
android:animation=
"
@
animator/XXXXX2"
/>
<
/animated-vector>
实际上这里面只有两个重点是需要关注的, XXXXX1和XXXXX2。一个具体的示例如下所示:
<
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。目标图像
XXXXX1是目标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标签中.
XXXXX2实际上就是模板要实现的动画, 动画效果实际上就是基础的属性动画, 例如:
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"
/>
在代码中使用
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添加动画:
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) 。
这两个属性的官方文档如下所示:
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: 动画资源
- 代码: 启动动画
这里展示下Demo的效果图:
文章图片
Vector性能问题 有读者在文章后面留言, 询问VectorDrawable的性能问题, 这里解释一下。
- Bitmap的绘制效率并不一定会比Vector高, 它们有一定的平衡点, 当Vector比较简单时, 其效率是一定比Bitmap高的, 所以, 为了保证Vector的高效率, Vector需要更加简单, PathData更加标准、精简, 当Vector图像变得非常复杂时, 就需要使用Bitmap来代替了
- Vector适用于ICON、Button、ImageView的图标等小的ICON, 或者是需要的动画效果, 由于Bitmap在GPU中有缓存功能, 而Vector并没有, 所以Vector图像不能做频繁的重绘
- Vector图像过于复杂时, 不仅仅要注意绘制效率, 初始化效率也是需要考虑的重要因素
- SVG加载速度会快于PNG, 但渲染速度会慢于PNG, 毕竟PNG有硬件加速, 但平均下来, 加载速度的提升弥补了绘制的速度缺陷。
https://www.youtube.com/watch?v= wlFVIIstKmA& feature= youtu.be& t= 6m3s
参考 【Android Drawable Mipmap Vector使用及Vector兼容】https://medium.com/@ shemag8/animated-vector-drawable-e4d7743d372c#.3vkt12j20
https://github.com/jpuderer/AnimatedButton
推荐阅读
- Android Wi-Fi Peer-to-Peer(Android的Wi-Fi P2P对等网络)
- Android Design Support Library概览
- Python中如何使用切片range()函数(示例)
- JavaScript let关键字的介绍和用法指南
- 如何在C++中使用STL查找向量的最小和最大元素()
- PHP如何使用ftp_chdir()函数(代码示例)
- 使用Java中的HashMap检查两个字符串是否互为字母
- Scala中的链式包装子句用法指南
- jQuery如何使用toArray()方法()