图片压缩知识梳理(6) - VectorDrawable 及 AnimatedVectorDrawable 使用详解

一、Vector优点

VectorDrawable主要有两个优点:屏幕自适应体积小

  • 如果我们使用的是普通的png或者jpg图片,那么为了能让图片在不同分辨率的机型上能够很好地展现,通常会在各个drawable文件夹下放置不同分辨率大小的图片文件,而VectorDrawable则不需要,仅仅只使用一套资源,就能够适应任何分辨率的手机。
  • 对于某些图片,VectorDrawable素材的大小要比pngjpg小很多。

二、SVGVector的基本概念

下面,我们先介绍一下SVGVectorDrawable的基本概念:

  • SVG:它并不是Android平台特有的概念,它的全称为可缩放矢量图形,简单的来说,就是用于描述二维矢量图形的图形格式,更加详细的解释可以参考:SVG - 百度百科
  • VectorDrawable:它是Android当中的SVG实现,它并不支持SVG的全部语法,只是支持部分有必要的部分。

三、获取VectorDrawable

俗话说的好,巧妇难为无米炊,这一节我们就来介绍一下如何获得一个VectorDrawable,一般获取的方式有以下三种方式:

  • 先获取SVG素材,再通过工具转换成为VectorDrawable
  • 通过Android Studio中的素材库,直接获取VectorDrawable
  • 根据Vector的语法,自己描述

3.1 先获取SVG素材,再通过工具转换成为VectorDrawable

首先获取SVG素材,再将它转换成为VectorDrawable是最常用的方式,而SVG素材的获取又有以下几种途径:

  • 网站直接下载SVG图像
  • UI设计师使用专业的工具导出
  • 通过 VectorMagic 软件,将pngjpg素材转换为SVG图像

对于个人开发者而言,一般都会采用第一种方式,我们这里也只介绍第一种方式。很多文章都推荐过这个网站:iconFront - 阿里巴巴,它提供了SVGPNG素材的直接下载:


下载完毕之后,在Android StudioNew选项中,选择Vector Asset

打开之后,选择Local file,并打开我们下载后的图像,再选择next保存到指定的文件夹:

最后,我们会得到一个*.xml文件,这个就是VectorDrawable

<vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="24dp" 
    android:viewportHeight="1024.0"
    android:viewportWidth="1024.0" 
    android:width="24dp">
    <path android:fillColor="#FF000000" 
        android:pathData="M887.9,298.2c-12.9,-12.1 -33.2,-11.5 -45.2,1.4L415.9,754l-233.1,-229.7C170.2,511.9 150,512 137.5,524.6c-12.4,12.6 -12.3,32.9 0.4,45.2l256.5,252.7c0.1,0.1 0.2,0.1 0.3,0.2 0.1,0.1 0.1,0.2 0.2,0.3 2,1.9 4.4,3 6.8,4.3 1.2,0.7 2.1,1.7 3.4,2.1 3.8,1.5 7.8,2.2 11.7,2.2 4.2,0 8.4,-0.8 12.3,-2.5 1.3,-0.5 2.3,-1.7 3.6,-2.4 2.4,-1.4 4.9,-2.6 6.9,-4.7 0.1,-0.1 0.1,-0.3 0.2,-0.4 0.1,-0.1 0.2,-0.1 0.3,-0.2l449.2,-478.2C901.4,330.6 900.8,310.3 887.9,298.2z"/>
</vector>

3.2 通过Android Studio中的素材库,直接获取VectorDrawable

还是在上面的那个界面,我们勾选另外一个选项:


这里面有Material Design提供的素材库:

通过这种方式,同样可以获取到一个*.xmlVectorDrawable

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
</vector>

3.3 根据Vector的语法,自己描述

3.13.2当中,我们可以看到,最终VectorDrawable都是用一个*.xml来表示的,那么,我们当然可以根据SVG的语法,来编写一个xml文件,通过pathData属性进行描述,不过这种方式较为繁琐,一般不会采用。

四、Vector语法

虽然说大多数情况下,我们不会完全手动去编写Vectorxml文件,但是,对于Vector的基本语法还是有必要了解一些的,因为在我们后边谈到的动态Vector中需要了解对于每个标签有哪些属性可以设置,Vector包含下面三种标签:

<vector>
    <group>
        <path>
        <path>
    </group>
</vector>

4.1 <vector>标签

  • name:矢量图的名字
  • width, height:矢量图的固有宽高,通常使用dp
  • viewportWidth, viewportHeight:把矢量图的宽高分成多少个单元,这里的每个单元就对应pathData中的一个点坐标。

4.2 <path>标签

  • name:路径的名称。
  • fillColor:图形的填充颜色。
  • pathData:定义控制点的位置,类似与Canvas当中的Path类。

4.3 <group>标签

<group>用来把多个<path>组合在一起,进行相同的处理。

更多的属性可以查阅下面这两篇文章:

VectorDrawable 详解
Android 中的 SVG 图像的各个属性意义

五、Vector的兼容性问题

为了让VectorDrawable能够在Android 5.0以下版本的手机上使用,我们需要引入support包,并修改gradle的配置。

5.1 引入support

VectorDrawable是在Android 5.0之后提出来的,因此,如果我们需要在低版本上使用,那么就要引入com.android.support:appcompat-v7包,要求版本号大于等于23.2.0,这里我们选用的是:

compile 'com.android.support:appcompat-v7:25.3.1'

Vector相关的两个包为:

5.2 修改gradle配置文件

如果gradle的版本小于2.0

android {
    defaultConfig {
        generatedDensities = []
    }

    aaptOptions {
        additionalParameters "--no-version-vectors"
    }
}

如果gradle的版本大于2.0,例如我们所用的版本:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
    }
}

那么需要这样配置:

android {
    defaultConfig {
         vectorDrawables.useSupportLibrary = true
    }
}

关于更多兼容性的问题,可以查看下面这篇文章

Android Vector 曲折的兼容之路

六、静态VectorDrawable

如果使用静态的方式展示VectorDrawable,那么很简单,只需要像使用普通的drawable一样,首先,我们按照第三节的方法,获取到一个vectorDrawable,并把它放在drawable文件夹下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
</vector>

我们可以应用于:

  • ImageViewsrc
  • Viewbackground
  • TextViewdrawable
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">
    <ImageView
        android:id="@+id/iv_static"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:clickable="true"
        android:src="@drawable/ic_account_circle_selector"/>
    <View
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:clickable="true"
        android:background="@drawable/ic_account_circle_selector"/>
    <TextView
        android:drawableStart="@drawable/ic_account_circle_black_24dp"
        android:text="UserName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

在上面的例子中,我们还使用了ic_account_circle_selector,其定义如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_account_circle_black_24dp" android:state_pressed="true"/>
    <item android:drawable="@drawable/ic_account_circle_black_normal_24dp"/>
</selector>

ic_account_circle_black_24dp就是之前获取到的素材,而ic_account_circle_black_normal_24dp就是改变了它的fillColor属性:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#80000000"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
</vector>

点击后的效果为:


七、动态VectorDrawable

动态的VectorDrawable,也就是AnimatedVectorDrawable,一般来说,它的整个结构如下图所示:


为了实现动态的VectorDrawable,一般需要提供三种类型的*.xml文件,这三个xml文件的根标签分别为:

  • vector:图像资源,也就是我们上面讨论的静态vector,定义于res/drawable文件夹下。
  • objectAnimator:定义图像资源中个元素的动画行为,定义于res/anim文件夹下。
  • animated - vector:对vector中的元素与objectAnimator进行组合,定义于res/drawable文件夹下。

7.1 对<group>标签进行动画

下面,我们先看一个简单的例子,上面我们所谈到的三个*.xml文件如下:

7.1.1 vector文件

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <group android:name="group_account" android:pivotX="12" android:pivotY="12">
        <path
            android:fillColor="#FF000000"
            android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
    </group>
</vector>

这里我们生成了一个头像的Vector素材,它的图像如下,注意到,我们用一个group标签把path标签包裹起来,并且给它定义了一个name属性为group_account,这个属性在后面会用到。

7.1.2 objectAnimator文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="500"
        android:propertyName="rotation"
        android:repeatCount="infinite"
        android:valueFrom="0"
        android:valueTo="360"/>
</set>

objectAnimator的定义和属性动画是相同的,我们需要指定需要变换的属性,也就是propertyName,并通过valueFrom/valueTo指定变化的起点值和终点值,这里我们选择了采用无限循环的方式来变换目标的rotation属性。

7.1.3 animated-vector文件

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/account_vector">
    <target
        android:animation="@anim/account_object_animator"
        android:name="group_account"/>
</animated-vector>

animated-vectorvector是一一对应的关系,因此需要在根标签中指定android:drawable,也就是在7.1.1中的vector文件。
而每一个target标签是内是成对的name - animation属性,name就是在7.1.1中声明的groupname,而animation则是在7.1.2中定义的动画文件。

如果objectAnimator所指定的属性在name所对应的标签中没有,那么不会发生任何变化。

7.1.4 启动动画

首先,在布局的xml文件中,把imageViewsrc指定为7.1.3中的animate-vector

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">
    <ImageView
        android:id="@+id/iv_dynamic"
        android:src="@drawable/account_animated_vector"
        android:text="UserName"
        android:layout_width="50dp"
        android:layout_height="50dp"/>
</LinearLayout>

在代码中,需要手动获得这个vectorDrawable,然后启动动画:

public class VectorDrawableActivity extends AppCompatActivity {

    private ImageView mAnimateView;
    private AnimatedVectorDrawable mAnimatedVectorDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_vector_drawable);
        mAnimateView = (ImageView) findViewById(R.id.iv_dynamic);
        mAnimatedVectorDrawable = (AnimatedVectorDrawable) mAnimateView.getDrawable();
        mAnimateView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mAnimatedVectorDrawable.start();
            }
        });
    }
}

效果如下:


7.2 对<path>标签中的属性进行动画

7.1中,演示了如何对group标签的属性进行变换,下面,我们再演示一下如何对path标签中的属性进行变换。和前面类似,我们依然需要三种类性的*.xml文件

7.2.1 vector文件

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:name="path_check"
        android:fillColor="#FF000000"
        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

这里,同样需要给path指定一个名字,用于后面和objectAnimator进行关联,它的素材为:

7.2.2 objectAnimator文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="500"
        android:propertyName="trimPathEnd"
        android:valueFrom="0"
        android:valueTo="1"
        android:valueType="floatType"/>
</set>

这里,我们选择的是path标签下的trimPathEnd属性,它表示从Path终点的位置去除Path,与之对应的还有trimPathStart属性,它表示从Path起点的位置去除Path,而trimPathOffset则可以改变Path的起点,这三个值的取值范围都是0~1

7.2.3 animated-vector文件

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/check_vector">
    <target
        android:animation="@anim/check_object_animator"
        android:name="path_check"/>
</animated-vector>

效果如下:


7.3 <path>之间切换

除了上面普通的属性之外,还支持对<path>标签下的<pathData>进行改变,系统会比较两个pathData之间的差别,并自动产生合适的动画。需要注意,它要求这两个pathData的点坐标的个数是相同的

7.3.1 Vector文件

这里,我们需要生成两个vectorDrawable,首先是起始的VectorDrawable

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:name="path_arrow_down"
        android:fillColor="#FF000000"
        android:pathData="M7,10l5,5 5,-5z"/>
</vector>

它的素材为:


接着是终点的VectorDrawable

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M7,14l5,-5 5,5z"/>
</vector>

它对应的素材为:


7.3.2 objectAnimator文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="500"
        android:propertyName="pathData"
        android:valueFrom="M7,10l5,5 5,-5z"
        android:valueTo="M7,14l5,-5 5,5z"
        android:valueType="pathType"/>
</set>

这里的propertyNamevalueType需要分别定义为pathDatapathType,而起点和终点的值就是我们在7.3.1生成的两个VectorDrawable所对应的pathData属性的值。

7.3.3 animated-vector文件

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/arrow_down_vector">
    <target
        android:animation="@anim/arrow_down_object_animator"
        android:name="path_arrow_down"/>
</animated-vector>

最终的效果为:


八、VectorDrawable的性能

关于VectorDrawable的性能问题,Android Vector 曲折的兼容之路 这篇文章说的很好,因此直接引用过来了:

  • Bitmap的绘制效率并不一定会比Vector高,它们有一定的平衡点,当Vector比较简单时,其效率是一定比Bitmap高的,所以,为了保证Vector的高效率,Vector需要更加简单,PathData更加标准、精简,当Vector图像变得非常复杂时,就需要使用Bitmap来代替了。
  • Vector适用于ICONButtonImageView的图标等小的ICON,或者是需要的动画效果,由于BitmapGPU中有缓存功能,而Vector并没有,所以Vector图像不能做频繁的重绘
  • Vector图像过于复杂时,不仅仅要注意绘制效率,初始化效率也是需要考虑的重要因素
  • SVG加载速度会快于PNG,但渲染速度会慢于PNG,毕竟PNG有硬件加速,但平均下来,加载速度的提升弥补了绘制的速度缺陷。

九、参考文献

1. Android Vector 曲折的兼容之路
2. VectorDrawable 怎么玩
3. Android 使用矢量图(SVG, VectorDrawable)实践篇
4. SVG - 百度百科
5. Android 中的 SVG 图像的各个属性意义

推荐阅读更多精彩内容