Android动画之VectorDrawable矢量图实战

简介: 效果图1. 矢量图SVG简介Android 5.0系统中引入了 VectorDrawable 来支持矢量图(SVG),同时还引入了 AnimatedVectorDrawable 来支持矢量图动画。
效果图

1. 矢量图SVG简介

Android 5.0系统中引入了 VectorDrawable 来支持矢量图(SVG),同时还引入了 AnimatedVectorDrawable 来支持矢量图动画。

所谓SVG(Scalable Vector Graphics),直译为可伸缩矢量图,具体内容可以参考矢量图百科。和一般的栅格图(比如PNG)相比,虽然其绘制速度较慢,却有以下的优点:

  • 保存最少的信息,文件大小比位图要小,并且文件大小与物体的大小无关;
  • 任意放大矢量图形,不会丢失细节或影响清晰度,因为矢量图形是与分辨率无关的。

从以上两个优点来看,在项目中使用矢量图至少可以缩小我们apk包的尺寸,而且可以在屏幕适配时提供很大的方便,因为矢量图是分辨率无关的。

2. Android中的Vector

SVG是一套标准,VectorDrawable是Android中的实现,但是VectorDrawable 并没有支持所有的 SVG 规范,目前只支持 PathData 和有限的 Group 功能。 所以对于使用 VectorDrawable 而言,我们只需要了解 SVG 的 PathData 规范即可。通过查看 PathData 文档,可以看到 path 数据包含了一些绘图命令,比如 :

  • M: move to 移动绘制点;
  • L:line to 直线;
  • Z:close 闭合;
  • C:cubic bezier 三次贝塞尔曲线;
  • Q:quatratic bezier 二次贝塞尔曲线;
  • A:ellipse 圆弧;

每个命令都有大小写形式,大写代表后面的参数是绝对坐标,小写表示相对坐标。参数之间用空格或逗号隔开
命令详解:

  • M (x y) 移动到x,y;
  • L (x y) 直线连到x,y,还有简化命令H(x) 水平连接、V(y)垂直连接;
  • Z,没有参数,连接起点和终点;
  • C(x1 y1 x2 y2 x y),控制点x1,y1 x2,y2,终点x,y;
  • Q(x1 y1 x y),控制点x1,y1,终点x,y;
  • A(rx ry x-axis-rotation large-arc-flag sweep-flag x y) :
    • rx ry 椭圆半径
    • x-axis-rotation x轴旋转角度
    • large-arc-flag 为0时表示取小弧度,1时取大弧度
    • sweep-flag 0取逆时针方向,1取顺时针方向

以下是绘制三角形的VectorDrawable实例:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" 
    android:height="64dp"
    android:width="64dp"
    android:viewportHeight="100"
    android:viewportWidth="100">
    
    <path
         android:fillColor="#000000"
         android:pathData="M25,0 l 50,50 -50,50Z"/>

</vector>

首先vector 标签是一个drawable对象,所以是放在res/drawable目录的。

vector 标签下有android:widthandroid:height属性,这两个属性是必填的,定义矢量图形的绝对大小,虽然说是矢量图形随意缩放,但是不能说这里不定义宽高直接到要设置到的目标控件上定义控件的宽高,这样是不允许的,一定要设置这个绝对宽高,要不然会报错。

然后还有个android:viewportHeightandroid:viewportWidth属性,这个是画布宽高,也是必填的,定义Path路径的时候就必须在这个画布大小里去绘制,超出画布就显示不出来了。

path标签android:fillColor属性定义绘制颜色,android:pathData定义绘制路径。

M25,0 l 50,50 -50,50Z这个路径表示:

  1. 在100*100的画布内,先把绘制点移动到绝对坐标(25,0)这个点,然后画直线到(50,50)这个点,l指令是相对坐标,大写的L表示绝对坐标,那么l 50,50就是在原点(25,0)的x轴往前移50,往下移50,绝对坐标就是(75,50),也就是三角形的右边那个点;
  2. 然后从(50,50)这个点绘制到三角形最下面那个点(-50,50),这也是相对右边那个点相对坐标,也就是把(75,50)这个绝对坐标当作是原点(0,0),参作这个原点往后移动50再往下移动50,在整个画布中的绝对坐标就是(25,100)。

效果如下:


绘制出的三角形

如果要绘制标准的SVG,可以是使用在线SVG Editor
另外,如果需要将SVG转化为VectorDrawable,还可以是使用在线工具Android SVG to VectorDrawable

更为重要的是,Android Studio自带Vector Assert工具,可以帮助我们生成矢量图;

  • Material icon:使用官方自带的各种矢量图,内容很丰富;
  • Local SVG file:使用第三方的SVG文件,比如从第三方图标网站:iconfont.cn中下载的图片都支持SVG;

3. Vector的配置和兼容性

正如上文所说,Android L开始提供了新的API VectorDrawable
可以使用SVG类型的资源,最初只能在21以上版本中使用。

为了让低版本也可以支持矢量图,Gradle Plugin 1.5加入了如下功能:

  • buildVersion>=21时,Vector矢量图功能不变;
  • buildVersion<21时,编译时,自动转换把Vector矢量图转化为PNG;

再后来Google升级了support library,官方向后兼容了矢量图的使用。矢量图兼容到API7,矢量图动画兼容到API11。

3.1 appcompat-v:23.2.0

因此要在低版本上兼容矢量图,就需要在项目中引入新的兼容库support-vector-drawable,并且appcompat-v7库的版本要在23.2.0+

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

3.2 gradle

而且你还要修改下gradle的相关配置,不要让gradle在构建的时候为你在低版本(API21以下)的情况下生成针对于不同密度的png文件,因为android studio1.4的时候支持了矢量图。

如果你的gradle插件的版本为2.0以下,你应该这么修改:

android {
  defaultConfig {
    // 不让gradle自动生成不同屏幕分辨率的png图
    generatedDensities = []
  }
  aaptOptions {
    additionalParameters "--no-version-vectors"
  }
}

现在大部分人的gradle插件版本是2.0+,只要这样修改就好:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

经过上面这几步的修改,就可以在项目中使用矢量图了。下面我们就正式来说说怎么使用。

4. Vector和属性动画结合使用

先看下效果图,这是一个代表文件下载的图片,是通过矢量图绘制的。我们通过将其和属性动画结合,实现了下载过程中的动画效果:箭头跳跃+横线反复出现


FileDownloading.gif

4.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="arrow_location">
        <path
            android:name="arrow_pic"
            android:fillColor="#FF000000"
            android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7z"/>
        </group>

    <path
        android:name="bar"
        android:fillColor="#FF000000"
        android:pathData="M5,18v2h14v-2H5z"/>

</vector>

矢量图中有两个Path标签,第一个来绘制箭头,第二个来绘制底下的横线。细心的读者已经发现,第一个Path标签外被Group标签包裹,这是因为path标签中没有坐标变化的属性,这些属性在group标签中,要使用这些标签就必须在group中绘制。

4.2 ObejctAnimator

Android属性动画Animator实现卫星Button中已经介绍了关于如何动态使用属性动画。本文的属性动画将以静态XML文件的出现,但是其内容是一样的。

如有要实现箭头跳跃的动画,就要让箭头的"translateY"属性向上移动,然后反复执行。为了让移动更有跳跃的感觉,interpolator被设为overshoot
“arrow_jump.xml”文件如下:

<set xmlns:androd="http://schemas.android.com/apk/res/android">
    <objectAnimator
        androd:duration="500"
        androd:interpolator="@android:interpolator/overshoot"
        androd:propertyName="translateY"
        androd:repeatCount="infinite"
        androd:repeatMode="reverse"
        androd:valueFrom="0"
        androd:valueTo="-3"
        androd:valueType="floatType"
        >
    </objectAnimator>
</set>

"bar_drawing.xml"实现动画下方横线反复出现的过程。

<set xmlns:androd="http://schemas.android.com/apk/res/android">
    <objectAnimator
        androd:duration="500"
        androd:propertyName="trimPathEnd"
        androd:repeatCount="infinite"
        androd:repeatMode="reverse"
        androd:valueFrom="0"
        androd:valueTo="1"
        androd:valueType="floatType">
    </objectAnimator>
</set>

这里要特别说明下属性* trimPathEnd,另外还有个属性 trimStartEnd*,其中trim是截取的意思,PathStart代表开始时的图像,PathEnd代表结束时的图像,变化的初始值和终值代表绘制的比率,从0变化到1,也就代表的是从完全没有到完全绘制出来,即从无到有。

持外两个属性属性动画最外层都是set标签,用户可以根据自己设计加入更多的属性动画标签ObjectAnimator

4.3 animated-vector 粘合剂

如何将矢量图和属性动画结合起来呢,这就需要们的粘合剂*animated-vector *文件,在其中建立矢量图和动画的对应关系,如下:
“file_download.xml"

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_file_download_black_24dp">
    <target
        android:animation="@animator/arrow_jump"
        android:name="arrow_location"/>

    <target
        android:animation="@animator/bar_drawing"
        android:name="bar"/>
</animated-vector>

其中animated-vectorandroid:drawable属性代表要控制的对象是哪一张矢量图。targe标签代表的动画和对象的一个对应关系,animation代表要加载的动画文件,name代表矢量图文件中的哪一个部分。这里两个target分别给。

更需要注意的是,位置移动所操控的属性并不是矢量图中path标签的,而是group标签的内容,所以箭头的name标准建立在group之中,arrow_jump动画绑定的对象也是group。同时,* trimPathEnd属性是Path的属性,所以动画绑定对象应该是Path*。
4.4 布局和模拟
Activity中的布局如下:

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:orientation="vertical"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/image"
            android:layout_marginTop="5dp"
            android:onClick="anim"
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:srcCompat="@drawable/file_download"/>
    </LinearLayout>
</ScrollView>

需要注意的是,ImageViewapp:srcCompat被设为粘合剂文件” file_download“。另外因为使用了app:srcCompat这个自定义的属性,需要加入命名空间xmlns:app="http://schemas.android.com/apk/res-auto"

Activity中对应的源码也很简单,点击图片模拟下载过程并开始动画,5秒之后下载结束,停止动画。

import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import static java.lang.Thread.sleep;

public class MainActivity extends AppCompatActivity {
    ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImageView = (ImageView) findViewById(R.id.image);
    }

        
    public void anim(View view) {//图片点击事件
        ImageView imageView = (ImageView) view;
        Drawable drawable = imageView.getDrawable();//获得图片的Drawable属性
        if(drawable instanceof Animatable) {//如果是属性动画
            ((Animatable) drawable).start(); //开始动画
            Toast.makeText(view.getContext(), "下载开始!",Toast.LENGTH_SHORT).show();
        }
        new Thread(new Runnable() {//开启计时线程
            @Override
            public void run() {
                try {
                    sleep(5000); //睡眠5s,代表下载过程
                    runOnUiThread(new Runnable() {//在UI线程中停止动画
                        @Override
                        public void run() {
                            ((Animatable) mImageView.getDrawable()).stop();
                            Toast.makeText(getApplicationContext(),"下载完毕",Toast.LENGTH_SHORT).show();
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

5. VectorDrawable的一些兼容问题:

如果使用appcompat-v:23.2.0+,兼容的API版本有比Android 5小的情况,还要注意有一些兼容新的问题:

  1. 在5.0以下的版本中,还无法使用Path Morphing(路径变化)属性;
  2. 在5.0以下的版本中,只能使用系统插值器;
  3. 在5.0以上版本中,需要把Drawable对象转化成AnimatedVectorDrawable之后才能使用
    public void animL(View view) {
        ImageView imageView = (ImageView) view;
        AnimatedVectorDrawable drawable = (AnimatedVectorDrawable) getDrawable(R.drawable.fivestar_anim);
        imageView.setImageDrawable(drawable);
        if (drawable != null) {
            drawable.start();
        }
    }

当然,如果最小版本都是5以上的,就没有这些问题了。

6. VectorDrawable的使用场景

最后借用前辈"徐医生"总结的VectorDrawable和Bitmap对比,为介大家绍适合VectorDrawable使用的场景:


VectorDrawable和Bitmap对比

7. 参考文献与阅读扩展

  1. Android VectorDrawable与SVG
  2. VectorDrawable怎么玩
  3. Androidの矢量图形之VectorDrawable研究
  4. Android 5.0学习之AnimatedVectorDrawable
  5. VectorDrawable详解
  6. Android属性动画Animator实现卫星Button
相关文章
|
2月前
|
存储 Shell Android开发
基于Android P,自定义Android开机动画的方法
本文详细介绍了基于Android P系统自定义开机动画的步骤,包括动画文件结构、脚本编写、ZIP打包方法以及如何将自定义动画集成到AOSP源码中。
49 2
基于Android P,自定义Android开机动画的方法
|
1月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
74 8
|
2月前
|
设计模式 Android开发 Kotlin
Android经典实战之Kotlin委托模式和by关键字
本文介绍了Kotlin中`by`关键字在类及属性委托中的运用,通过实例展示了如何利用类委托简化接口实现,以及如何借助标准与自定义属性委托管理属性的读写操作。通过`by`关键字的支持,Kotlin使得委托模式的实现更为直观且高效。
50 4
|
2月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
34 2
|
17天前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
35 9
|
15天前
|
开发工具 Android开发 git
Android实战之组件化中如何进行版本控制和依赖管理
本文介绍了 Git Submodules 的功能及其在组件化开发中的应用。Submodules 允许将一个 Git 仓库作为另一个仓库的子目录,有助于保持模块独立、代码重用和版本控制。虽然存在一些缺点,如增加复杂性和初始化时间,但通过最佳实践可以有效利用其优势。
19 3
|
18天前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
43 5
|
18天前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
58 1
|
29天前
|
编解码 前端开发 Android开发
Android经典实战之TextureView原理和高级用法
本文介绍了 `TextureView` 的原理和特点,包括其硬件加速渲染的优势及与其他视图叠加使用的灵活性,并提供了视频播放和自定义绘制的示例代码。通过合理管理生命周期和资源,`TextureView` 可实现高效流畅的图形和视频渲染。
79 12
|
1月前
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
29 7