本文是以读者对SVG有一定了解为前提的,否则请先百(谷)度(歌)了解下。
实践都是从坑里爬出来的,因此本文的子题目也可叫做Android使用矢量图填坑记。
文章开始前,先墙裂安利一个网站,阿里的iconfont,海量在线矢量图,早收藏早致富。本文主要涉及到的矢量图资源均来自该网站。放图镇楼:
可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。SVG由W3C制定,是一个开放标准。——摘自维基百科
.svg格式相对于.jpg、.png甚至.webp具有较多优势,我认为核心有两点:
VectorDrawable
是Google从Android 5.0开始引入的一个新的Drawable
子类,能够加载矢量图。到现在通过support-library
已经至少能适配到Android 4.0了(通过AppBrain统计的Android版本分布来看,Android 4.1以下(api<15)几乎可以不考虑了)。Android中的VectorDrawable
只支持SVG的部分属性,相当于阉割版。
它虽然是个类,但是一般通过配置xml再设置到要使用的控件上。在Android工程中,在资源文件夹res/drawable/
的目录下(没有则需新建),通过
标签描述,例如svg_ic_arrow_right.xml
:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="8dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
vector>
基本属性说明:
width
,height
:图片的宽高。可手动修改到需要尺寸;viewportHeight
,viewportWidth
:对应将上面height
width
等分的份数。以svg_ic_arrow_right.xml
举例,可以想象将长宽都为8dp的正方形均分为24x24的网格,在这个网格中就可以很方便地描述点的坐标,图像就是这些点连接起来构成的。fillColor
:填充颜色。最好直接在这里写明色值#xxxxxxxx
,而不要用@color/some_color
的形式,避免某些5.0以下机型可能会报错。pathData
:在2中描述的网格中作画的路径。具体语法不是本文的重点,故不展开。
这段代码描述出来的是一个白色箭头,可以从Android Studio的preview功能栏里预览到它的样子:
前言部分已经墙裂推荐过,感觉我已经离不开它了= ̄ω ̄=
第一步,搜索你要的资源名字,中英文一般都会有结果。比如“arrow”,结果:
第二步,鼠标移动到某一图标上点击,比如上面第一排第二个,出现:
三个选项,第一相当于购物车,可不用登录,第二是收藏,第三是下载,均需要登录。如果未登录,点击后出现:
选择GitHub或微博都行。
第三步,登录成功,点击下载,弹出:
可以对图标属性进行编辑,如色值和大小(单位dp),然后点按钮“SVG下载”。下载成功后在下载目录找到一个.svg格式的文件,我的是:
这个文件可以用浏览器打开->查看网页源码,或者用NotePad++等编辑器打开看到里面的内容,格式化后是这样:
xml version="1.0" standalone="no"?>
<svg t="1490517024583" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1010" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
<defs>
<style type="text/css">style>
defs>
<path d="M288.86749 12.482601C272.260723-4.160867 245.369563-4.160867 228.720647 12.482601 212.15603 29.126068 212.15603 56.438425 228.720647 73.081892L704.289552 511.786622 228.720647 950.918109C212.15603 967.561574 212.15603 994.447175 228.720647 1011.517401 245.369563 1028.160866 272.260723 1028.160866 288.86749 1011.517401L794.952385 544.646802C803.803707 535.684935 807.597131 523.735776 807.007043 511.786622 807.597131 500.264224 803.803707 488.315065 794.952385 479.353198L288.86749 12.482601Z" p-id="1011">path>
svg>
问题是,文件里好多标签Android是不认识的。不过没关系,有三种解决办法:
点击图标弹出对话框:
勾选Batch选项,将对被选中文件夹中的.svg文件进行批量转换。nodpi会自动添加到没有后缀的drawable文件夹中。
网上下载的svg资源往往一步到位,有个这个插件将会事半功倍。导入第一个svg文件时就命名成我们想要的名字,如果不满意再导入时无需再关注命名,将后面导入的
pathData
覆盖第一个观察效果,直到满意后删除不需要的文件。
标签的xml文件,通过观察文件内容,很容易获取到关键信息。width
height
自然对应
中宽高,viewBox
后两位数字是分别对应
中的viewportWidth
和viewportHeight
,往下
中的d
的数据的对应
中
中的pathData
。fillColor
自己手动设置。
先选本地文件(还能支持PSD,强吧),再到磁盘中找到之前下载的.svg矢量图。导入后可以为文件重命名(建议用svg_
或者有区别于其它格式的前缀),默认导入宽高均为24dp,选中Override框则读取文件本来宽高,其它配置视需求而定。点击Next到下一页最后点Finish就导入了。自动导入需要格式化一下就是前面svg_ic_arrow_right.xml
的样子了。
海搜比较耗时间,线条粗细啦,位置没居中啦,大小不搭配啦,关键是这些问题都是导入项目或者运行到手机后才能发现(非强迫症当我没说)。
iconfont还有诸多成套的图标库,优点是风格大小一致,或者多彩图标。
鼠标选中drawable文件夹,右键, New, Vector Asset,然后出现:
点击机器人进入搜索筛选:
左侧的搜索和分类可以快速索引。这里应该都是由谷歌官方制作的MD标准图标,建议先到这里搜索,如果没有再到网上搜索。
对本人来说,方式一基本可以搞定一个App了。但如果以上两种方式均不能满足你的需求,下面祭出求矢量图三式:
前提:
项目的build.gradle
配置有:
android{ ... defaultConfig { ... vectorDrawables.useSupportLibrary = true } ... } dependencies { ... compile "com.android.support:appcompat-v7:21+" // 至少Api21 ... }
项目的
Activity
中都包含(通用做法是在BaseActivity中加):
static { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); }
这是继承自ImageView
用于5.0以下加载矢量图的控件,只需要替换src
为srcCompat
属性,其它没什么不同。例如:
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/svg_ic_arrow_right"/>
- 如果你的
Activity
直接或间接继承自AppCompatActivity
,当前视图中的ImageView
在编译过程中会被自动转为AppCompatImageView
(support包中所有含有AppCompat前缀的控件均受相同处理),因而在Activity
中通过findViewById()
的实例用ImageView
或AppCompatActivity
接收是没有区别的。- 用以上条件的
Activity
中装载的Fragment
,或者通过动态注入(如Dialog
的contentView
)的ImageView
,均将被自动转为AppCompatActivity
。- 从xml文件中初始化
ImageView
并加载矢量图,必须使用AppCompatImageView
的srcCompat
属性。ImageView
的染色属性tint
同样适合矢量图。
在我的经验中,TextView
可以用到矢量图的场景是最多的,主要是设置CompoundDrawable
。例如:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableRight="@drawable/svg_ic_arrow_right"
android:drawablePadding="4dp"
android:text="drawable right"/>
这样设置后,没有任何不适,编译器也不报错,可能你自己运行也没问题。但是!这才是深坑啊。5.0以下某些机型可能会崩溃的。
AppCompatTextView
是没有对CompoundDrawable
进行适配的,所以需要自己动手才能丰衣足食。简单原理是,判断系统版本如果小于5.0,就用ContextCompat.getDrawable
获取到Drawable
实例,再setCompoundDrawablesWithIntrinsicBounds
。
这个部分本人已经做好并开源了,地址:VectorCompatTextView,轻松compile到项目中使用。还特意添加了一个实用功能——tint
染色——可以选择是否让图标与文字颜色一样,这样就不必关心xml里的fillColor
属性了。用例:
<com.xw.repo.VectorCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_gray_light"
android:gravity="center_vertical"
android:padding="16dp"
android:text="Next"
android:textSize="16sp"
app:drawableRightCompat="@drawable/svg_ic_arrow_right"
app:tintDrawableInTextColor="true"/>
效果:
Tips:
存在这样一种情形,连续两个控件(编号1和2)都来加载同一张svg图,svg的本来颜色是A,控件1将颜色改为了B,控件2也加载这张svg,颜色却变为了B,这应该是Android系统对矢量图的缓存造成的。解决办法是把svg的本来颜色改为B,控件2再改为颜色A。(好绕,不过一般不会遇到)
MenuItem
就是在res/menu/
目录下通过xml配置的菜单,适用于NavigationView
的menu
属性和Activity
中onCreateOptionsMenu()
注入的选项菜单。
前一阵做了一个小应用叫“简影讯”,发现MenuItem
是可以完美支持矢量图的,而且也可以自动跟随文字颜色改变颜色。且看证明:
简影讯(开源地址:GracefulMovies),是一枚基于Retrofit+RxJava+MVP+Colorful多彩主题框架开发的高颜值影讯app。简约,优雅,精彩,即看即走。欢迎在应用宝或360手机助手下载围观。
该项目除ic_launcher
外,所有的图标都是矢量图。
“是时候全面使用矢量图了。”
自定义View中也可以自由使用矢量图。
首先需要将VectorDrawable 转为 Bitmap,看码:
public Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
执行以上方法获得一个Bitmap的实例(设为mVectorBitmap),然后再在ondraw()
里根据你的需求画出bitmap:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
///
canvas.drawBitmap(mVectorBitmap, left, top, paint);
///
}
矢量图的优点一大把,但也不是万能的。矢量图特别适合icon图标的应用场景,但是不能用于比如加载相册时,设置的placeholder或者error这类需要频繁切换回收的应用场景,否则会造成非常明显的卡顿,因为矢量图是不被硬件加速支持的。
Android 5.0推出已经有些年份了,也不知道Android开发圈对矢量图的使用情况,但知道比如微信这些大厂早已全面推广使用。然而在本人周边似乎自己算先驱了,所以才有了把过程中的一些经验总结分享出来的想法。
毕竟本人才疏学浅,难免有纰漏之处,请大神轻拍砖,并不吝赐教。若对后来学习者有帮助,那花这一天码的字自然也超值了,希望共勉。