AndroidAndroid开发Android开发经验谈

Android矢量图(一)--VectorDrawable基础

2018-01-19  本文已影响347人  宛丘之上兮

背景

维基百科中的定义:

可缩放向量图形Scalable Vector GraphicsSVG)是一种基于可扩展标记语言(XML),用于描述二维向量图形的图形格式。SVG由W3C制定,是一个开放标准。

1,SVG何以可以任意缩放而不会失真,drawable-(m|h|xh|xxh|xxxh)dpi和mipmap-(m|h|xh|xxh|xxxh)dpi这俩货就可以省省了;2,SVG文件一般都比较小,省去很去资源达到apk缩包的目的;3,SVG占用内存非常小,性能高。但是SVG明显的缺点是没有位图表达的色彩丰富。

Android API 21(5.0)引入了一个Drawable的子类VectorDrawable目的就是用来渲染矢量图,AnimatedVectorDrawable用来播放矢量动画。之前老的小于21的API设备可以分别使用VectorDrawableCompatAnimatedVectorDrawableCompat这两个兼容包来同样达到渲染矢量图的目的。本文只讨论矢量图,不讨论矢量动画。

准备

使用矢量图要根据minSdkVersion来分3中不同的情况:

  1. minSdkVersion>=21:用xml文件或者代码定义VectorDrawable,和普通的Drawable用法一样,不再需要额外任何东西;如何编写矢量图,下文有介绍;
  2. minSdkVersion<21:如果想要渲染矢量图的话必须在app模块的build.gralde文件里添加一行代码:
defaultConfig {
    vectorDrawables.useSupportLibrary = true
}
  1. minSdkVersion<21以及更多:上面的第二种情况是使用兼容包,但是兼容包仅支持AppCompatImageView和AppCompatImageButton及其子类矢量图,而且矢量图的引用必须放在app:srcCompat属性中才会被识别并生效,代码必须这样写才行:
<android.support.v7.widget.AppCompatImageView
      app:layout_constraintBottom_toBottomOf="parent"
      android:layout_width="100dp"
      android:layout_height="100dp"
      app:srcCompat="@drawable/ic_oval"/>

ic_oval.xml是我们使用xml编写的矢量图,如果想要TextView的drawableTop或者其他额外方式使用矢量图渲染,那么必须在Activity中加入代码:

static {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

同时这个Activity必须继承AppCompatActivity这个compat兼容包属性才会生效。

minSdkVersion<21情况下在非app:srcCompat属性的地方使用矢量图时,需要将矢量图用drawable容器(如StateListDrawable, InsetDrawable, LayerDrawable, LevelListDrawable, 和RotateDrawable)包裹起来使用。否则会在低版本的情况下报错org.xmlpull.v1.XmlPullParserException: Binary XML file line #0: invalid drawable tag vector。minSdkVersion>=21则没有任何限制。

矢量图使用

准备工作做好之后,我们就需要自己动手编辑矢量图了。VectorDrawable类在xml中对应的是标签是vector。我目前所知道的是只有xml文件才能决定矢量图的样子(也就是编辑pathData、fillColor等属性),貌似无法使用代码来决定矢量图的绘制逻辑,而只能使用代码加载编辑好的xml文件,这个xml文件有两种方法来创建:

  1. 右击drawable-->Drawable resource file-->设置root element为vector,这样的矢量图绘制逻辑完全掌握在开发者手里;
  2. 右击drawable-->Vector Asset,选择SVG或者PSD文件直接生成根标签为vector的xml文件,可以百度或者Google怎样把png转换成SVG。

写了这么多字,一直在瞎扯淡而没谈重点,下面我们看下根标签为vector的xml文件的真面目,代码:


图1

上图中标签vector使用了四个属性:android:width="24dp"android:height="24dp"android:viewportHeight="300.0"android:viewportWidth="300.0"

  1. width和height:当使用这个矢量图的View的宽高是wrap_content 的时候这两个属性才生效;
  2. viewportWidth和viewportHeight:决定画布的宽高,是定义的一个虚拟空间,方便编辑pathData属性,如果pathData中的点超出了这个虚拟空间,超出的部分将不会展现给用户;虚拟空间的原点仍然还是在左上角(R点就是原点)。

path标签是vector标签的子标签,它使用了以下属性:

  1. android:name:类似View的id属性,方便path被引用,如上图的edge是虚拟空间四个边界的path,oval是一个椭圆的path;
  2. android:fillColor:填充path的颜色,如果没有定义则不填充path
  3. android:strokeColor:path边框颜色,如果没有定义则不显示边框
  4. android:strokeWidth:path边框的粗细尺寸
  5. android:pathData:path指令,决定path的移动和绘制逻辑,这个是最主要的属性,下面详细讨论。

更多path属性请参考链接

pathData的指令和Path类的API方法基本差不多,比如M指令对应moveTo方法,m指令对应rMoveTo方法,下面是一些基本的指令:

每个指令都有大小写形式,大写表示后面的参数是绝对坐标,小写表示相对于上一个点的相对坐标位置,参数可以用逗号或者空格分离。
只要掌握上面5个基本指令就能编辑pathData并且绘制一些酷炫的SVG。更详细全面的path指令请参阅链接

估计你已经发现了,圆弧曲线指令A竟然那么多参数,这直接吓跑了很多的程序员,其实也并不难,且慢慢道来。

先根据图1里的代码来分析pathData指令。如图一所示,edge这个path使用了四个相对指令,首先指令h300 0相对向右水平移动300到点S,然后指令v0 300相对向下垂直移动300到T,再次指令h-300 0相对向左水平移动300到U,最后指令v0 -300相对向上垂直移动300到起点R,这样就根据属性strokeColor和strokeWidth绘制了四条直线,最后一个指令可以使用z代替。这很简单吧?!

再来看oval这个path。它使用了三条指令。第一条指令移动到点M处,第二条指令a75,75 0 1,1 150,0绘制M-N-O的弧线,第三条指令a75,75 0 1,1 -150,0绘制O-P-M的弧线。a指令共有7个参数:rx和ry表示椭圆的两个半径,x-axis-rotation表示x轴的旋转角度,x和y表示绘制椭圆弧线的终点,这5个参数很简单很好理解,large-arc-flag和sweep-flag这两个参数有点唬人。
解释large-arc-flag和sweep-flag这两个参数之前先考虑下这个题目:已知椭圆的半径rx和ry,请绘制若干条从起始点A到终点B的椭圆弧线。题目中是若干条,那到底几条啊?一般情况下会有四条椭圆弧线(特殊情况是rx=线段AB的一半或者ry=线段AB的一半,这时候的椭圆弧线只有两条),而large-arc-flag和sweep-flag这两个参数就从这四个椭圆弧线中选取了最终的一条进行绘制。large-arc-flag决定是大弧线还是小弧线,1大0小,sweep-flag决定是顺时针弧线还是逆时针弧线,1顺0逆。

图2 。看图2希望你能明白这两个参数的意义。

有人可能会问,图1的oval path是个圆,竟然使用了两个a指令,使用一个a指令就能绘制圆的,只要终点回到起始点就能绘制圆的path了,刚开始我也是这样认为的,比如下面的代码:

<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="300.0"
    android:viewportWidth="300.0">
    <path
        android:name="circle"
        android:fillColor="@android:color/holo_green_light"
        android:pathData="
        M150,150
        a75,75 0 1,1 0,0"
        android:strokeColor="#00000000" />
</vector>

上面的代码的path从起始点又回到了起始点,不会绘制任何东西,终点x y需要和起始点错开几个像素比如android:pathData="M150,150 a75,75 0 1,1 0,1"就大约是一个圆path,为什么说是大约一个圆?因为起始点和终点不在一起,这只是一个圆的大弧线部分。推荐使用两条a指令绘制圆path,因为一条a指令绘制的不是真正的圆path。

group标签

path没有scalerotatetranslate这三种属性,因此也不能执行这三种属性动画,要达到这样的目的需要借助group这个标签。group标签也是vector的一个子标签,它可以作为path或者其他group的父标签使用,将path和group组合成一个组来附加一些变换操作,这些变换操作包括scalerotatetranslate共三种。这张图3是来自android官网的vector标签树型图:

图3 。<group>定义变换的细节,<clip-path>定义裁剪区域。根据这三个变换操作,group标签有以下属性:

这是group的全部属性了,属性都很简单,不需要解释。

clip-path标签

<clip-path>定义当前绘制的剪切路径,就是图像的一部分剪切下来。注意,clip-path只对当前的vector和group以及当前vector和group的孩子有效。这个标签仅有两个属性:

<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="300.0"
    android:viewportWidth="300.0">
    <clip-path android:name="clip_one" android:pathData="
    M0 20a20 20 0 0 1 20 -20
    l260 0a20 20 0 0 1 20 20
    l0 260a20 20 0 0 1 -20 20
    l-260 0a20 20 0 0 1 -20 -20
    l0 -260"/>
    <path
        android:name="edge"
        android:pathData="h300v300h-300v-300
        M150 0 v300
        M0 150 h300"
        android:fillColor="@android:color/holo_green_light"
        android:strokeColor="@android:color/holo_red_dark"
        android:strokeWidth="1" />
    <group>
        <clip-path android:name="clip_two" android:pathData="M0 150h300v150h-300v-150"/>
        <path
            android:name="oval"
            android:strokeLineCap="round"
            android:strokeLineJoin="round"
            android:pathData="M20 20 l260,260M280 20 l-260,260h100"
            android:strokeColor="#000000"
            android:strokeWidth="15"/>
    </group>
</vector>
上面代码定义了两个clip-path,其效果如图4所示。 图4.gif

build.gradle中vectorDrawables.useSupportLibrary属性

build.gradle中的vectorDrawables.useSupportLibrary默认是false,不设置为true的话会有什么问题吗?讨论这个问题也需要根据minSdkVersion具体分析:

  1. minSdkVersion>=21:这么高的API根本就不需要兼容包,仍然可以渲染矢量图;
  2. minSdkVersion<21:不再使用矢量图兼容包,不能渲染矢量图,但是有趣的是vector标签仍然可以使用,低版本的API完全把VectorDrawable当作Drawable使用了,VectorDrawable的特性完全失效。原理是vector xml文件会生成对应的png文件,使用png方式渲染图片,和矢量图没有任何关系。值得注意的是生成的png图片size很小而且会忽略vector标签的android:tint属性(貌似只忽略这个属性,我试过vector标签的android:alpha属性在生成的png图片中仍然有效,生成的png文件目录是app/build/generated/res/pngs/debug,minSdkVersion>=21或者vectorDrawables.useSupportLibrary=true的话不会生成这些png图片)。而且path标签的color相关的属性不能引用colors.xml的值,android:strokeColor="@android:color/holo_red_dark"这样写的话会编译失败,提示错误:Can't process attribute android:strokeColor="@android:color/holo_red_dark": references to other resources are not supported by build-time PNG generation,而只能写原生的16进制color值比如android:strokeColor="#234aac"

想看具体信息请查看这篇文章

SVG实战

我做的项目中一张扑克png资源大小2k左右,我试着用矢量图画这些扑克牌。代码如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:width="400dp"
    android:height="550dp"
    android:viewportHeight="550"
    android:viewportWidth="400.0">
    <group android:name="poker_diamond_a">
        <path
            android:name="border"
            android:strokeWidth="7"
            android:strokeColor="#96999c"
            android:fillColor="@android:color/white"
            android:pathData="M5 25a20 20 0 0 1 20 -20
            h350a20 20 0 0 1 20 20v500a20 20 0 0 1 -20 20h-350a20 20 0 0 1 -20 -20v-500"/>
        <path android:name="a"
            android:strokeWidth="8"
            android:strokeColor="#cc0000"
            android:strokeLineJoin="bevel"
            android:pathData="M40 120
            l40 -90
            l40 90
            l-16-35
            h-48"/>

        <path android:name="small_diamond" android:fillColor="#cc0000" android:pathData="M80 130l41 41l-41 41l-41 -41z"/>
        <path android:name="big_diamond" android:fillColor="#cc0000" android:pathData="M260 310l100 100l-100 100l-100 -100z"/>
    </group>
</vector>
图5

代码很简单,只有4条path。border路径顺序是1-2-3-4-5-6-7-8-1, a的路径是a-b-c-d-e,small_diamond的路径是e-f-g-h,big_diamond的路径是i-j-k-l。这个xml文件只有1k。


文章有错误的地方希望指正。
本文内容都是一些基础的东西,应该都能掌握,主要介绍了vector、group、path、clip-path这些标签常用的属性以及pathData属性对应的常用的指令,工作中掌握这些常用的知识就能比较熟练使用矢量图了。后续文章会剖析一些不常用的属性。

上一篇下一篇

猜你喜欢

热点阅读