SVG - 在Android中使用矢量图全攻略

概念

什么是矢量图,SVG

SVG全称:可伸缩矢量图形 (Scalable Vector Graphics)
SVG 用来定义用于网络的基于矢量的图形
SVG 使用 XML 格式定义图形
SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
SVG 是万维网联盟(W3C)的标准,与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体

与位图(Bitmap)相对,SVG不会像位图一样因为缩放而让图片质量下降。

优缺点

优点:
1.矢量图使用点和线来描述图形,所有文件会比较小,同时也能提供高清晰的画面。
2.矢量图缩放自由且不会失真,完全适配于任何分辨率的屏幕。
3.矢量图是以xml语言来描述的,所以它修改自如,节约空间,使用方便。
4.矢量图色彩分辨率非常高清,同时支持滤镜。
5.跨平台,因为矢量图是纯文本格式来描述的,所以不受平台的限制。

缺点:
SVG 是在要用图的时候再把图画出来,所以在图片显示的时候会花费更多的时间消耗更多的资源;此外,SVG 并不太适合层次过于复杂细节过于繁多的图片。

简单来说,SVG就是描述图形的xml文件,缩放不变形。
和安卓中的shape有点异曲同工之妙。

获取SVG图片

获取途径

SVG的获取途径有以下几种:

  1. Android自身提供
  2. 美工切图
  3. 自己从网站下载

android提供的矢量图多数不能满足我们的需求,美工切图的情况此处略去不表,来讲讲从网站下载的方法。

【iconfont】阿里妈妈矢量图标平台

在Android上如何使用,官网已经做了非常详细的介绍。
建议先戳 —> http://www.iconfont.cn/help/detail?helptype=code

使用流程
使用起来也很简单,总结就是:

  • 首先新建一个自己的项目;
  • 从iconfont平台选择要使用到的图标或者本地导入svg图片到项目中;
  • 下载代码,把iconfont.svg和iconfont.ttf文件导入到项目中的assets文件夹中;
  • 用TextView代替ImagerView,找到图标相对应的 HTML 实体字符码给textView设置;
  • textview设置大小跟颜色,图标的大小颜色也会改变(这里大小最好用dp为单位,这样不会随着手机字体大小的改变而改变图标的大小);
  • 为Textview设置指定的ttf文字;
 <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="&#xe6dd;"
    android:textSize="50dp"/>

//为TextView设置指定ttf文字

Typeface iconfont = Typeface.createFromAsset(getAssets(), "iconfont/iconfont.ttf");
TextView textview = (TextView)findViewById(R.id.text_icon);
textview.setTypeface(iconfont);

iconfont的封装

每次都给TextView设置指定文字也很繁琐,而且不断读取iconfont.ttf文字,也会浪费内存。我们可以做个优化,把这个抽离出来。
首先我们要读取到的是assets目录下的iconfont.ttf文件;这里我把它放到自定义的Application中,这样就只要读取一次,代码如下:

public class MyApplication extends Application {

public static Typeface iconfont;

...

public static Typeface getIconfont(Context context) {
    if (iconfont != null) {
        return iconfont;
    } else {
        iconfont = Typeface.createFromAsset(context.getAssets(), "iconfont/iconfont.ttf");
    }
    return iconfont;
}

这里就实例化了iconfont。然后给每TextView设置Typeface,这肯定不是我们想要的,所以我们可以通过自定义控件的方式,在初始化时执行setTypeface。代码如下:

public class TextViewIcon extends AppCompatTextView {
	public TextViewIcon(Context context) {
	    super(context);
	    init(context);
	}
	
	public TextViewIcon(Context context, AttributeSet attrs) {
	    super(context, attrs);
	    init(context);
	}
	
	public TextViewIcon(Context context, AttributeSet attrs, int defStyleAttr) {
	    super(context, attrs, defStyleAttr);
	    init(context);
	}
	
	private void init(Context context) {
	    setTypeface(VcApplication.getIconfont(context));
	}
}

如此即可在layout文件中直接使用了


<com.xxxxx.xxxxx.TextViewIcon
    android:id="@+id/text_icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="&#xe643;"
    android:textColor="#ff0000"
    android:textSize="50sp"/>

iconfont动态设置

动态设置通俗的说就是在java代码里动态的调整图标的大小颜色或改变图片,iconfont改变大小颜色这很简单直接调用TextView的setTextSize和setTextColor就可以了,动态设置图片是不是setText呢?

 textview.setText("&#xe643;");

运行发现并不如我们所愿,这里涉及到unicode 字符的问题,
把代码稍改一下,用 unicode 字符来表示,"&#x" 替换成 “\u”,改成下面这样,问题就可以解决

textview.settext("\ue643");// "&#x" 替换成 "\u",用 unicode 字符来表示

使用SVG

首先需要明确的是:Android不支持直接在项目中使用svg的图片。
但是Google在Android5.X中提供了两个新API来支持SVG:

  • VectorDrawable
  • AnimatedVectorDrawable

所以在Android中使用SVG的话,要区分5.0以上以及5.0以下的不同情况。

在Android Studio 使用SVG矢量图
在drawable文件夹上右键->new->Vector Asset

选择自带的图标或者从本地选取

android:viewportWidth与android:viewportHeight:是指当前Drawable对应的虚拟Canvas的大小, 之所以说是虚拟的是因为实际上并不存在这样一个Canvas, 又之所以需要这个值是因为在+ + 标签中的路径数据要基于具体的坐标系来绘制.

<vector> 是 VectorDrawable 对应的根标签
android:width与android:height:对应矢量图的实际大小

矢量图是可以无限大且不失真的, 但通常一个图片都会有一个原始大小,将在其父控件大小都设置为wrap_content时起作用)
基于加载性能考虑,Google推荐限定在 200x200dp 以内。

android:viewportWidth与android:viewportHeight是指当前Drawable对应的Canvas的大小,
一般与上面的android:width与android:height:成比例定义。

用于为<path/>标签中的路径数据提供绘制位置的参考坐标系及绘制范围
<path/>标签对应路径信息, 这里的path与我们自定义绘制图形时用的Path原理一样, 就是记录一些绘图操作, 具体对应其中的 pathData。

android:fillColor为默认黑色(#FF000000)时,在View容器内将使用tint颜色取代,否则将呈现为android:fillColor与tint的混合颜色。

<path/>标签:对应路径信息, 这里的path与我们自定义绘制图形时用的Path原理一样, 就是记录一些绘图操作, 具体对应其中的pathData.PathData中对应的路径描述符号不需要我们去记, 通常情况下由绘图软件生成svg图片再从svg文件中提取.
<path/>路径的路径描述指令含义表:

M = moveto 相当于 android Path 里的moveTo(),用于移动起始点
L = lineto 相当于 android Path 里的lineTo(),用于画线
H = horizontal lineto 用于画水平线
V = vertical lineto 用于画竖直线
C = curveto 相当于cubicTo(),三次贝塞尔曲线
S = smooth curveto 同样三次贝塞尔曲线,更平滑
Q = quadratic Belzier curve quadTo(),二次贝塞尔曲线
T = smooth quadratic Belzier curveto 同样二次贝塞尔曲线,更平滑
A = elliptical Arc 相当于arcTo(),用于画弧
Z = closepath 相当于closeTo(),关闭path

大写代表绝对位置, 小写代表相对位置
官方标准定义细则参考:SVG Paths Definition - W3C

矢量图创建完成后使用方法跟其他格式的图片用法一样。

我们这里提供三种解决方案:

  • 使用支持库PathView
    https://github.com/geftimov/android-pathview
    不仅支持直接使用svg,还可以自定义路径,使用是 svg动画。详情请看github介绍

  • 将SVG转换为VectorDrawable
    http://inloop.github.io/svg2android/
    该在线网站可以将svg转换为VectorDrawable

  • 将矢量图制作成字体图标
    step1:从iconfont平台选取图标下载到本地,复制字体文件到项目的assets目录。
    step2:打开下载下来的文件,并打开demo.html,找到图标对应的HTML实体字符码。
    step3:打开res/values/strings.xml,添加string值。

<resources>
	<string name="app_name">My Application</string>
	<string name="icon_cart"></string>
	<string name="icon_wl"></string>
</resources>

step4:为TextView设置string值。

step5:为TextView指定文字。

 @Override
 protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      mTv = (TextView) findViewById(R.id.tv_main);
      Typeface iconfont = Typeface.createFromAsset(getAssets(),    "iconfont.ttf");
      mTv.setTypeface(iconfont);
 }

5.封装使用
上面的使用的方式需要对每一个TextView指定字体,并且需要下载图标并获取对应的字符编码,使用起来非常的麻烦,github上提供了一个开源的库封装了操作,简化了使用:
https://github.com/mikepenz/Android-Iconics

<TextView
    android:text="{gmd-chart} Chart"
    android:textColor="@android:color/black"
    android:layout_width="wrap_content"
    android:layout_height="56dp"
    android:textSize="16sp"/>

6.SVG动画

6.1 使用AnimatedVectorDrawable来实现动画
SVG学习–AnimatedVectorDrawable的使用

6.2 使用三方库-PathView

可能遇见的坑

VectorDrawable

要想在Android使用svg,首先要介绍的肯定是VectorDrawable,VectorDrawable是Android 5.0系统中引入了 VectorDrawable 来支持矢量图(SVG),同时还引入了 AnimatedVectorDrawable 来支持矢量图动画。
官方文档:
VectorDrawable,AnimatedVectorDrawable
VectorDrawable转换Bitmap

 Bitmap bitmap;
    if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
        Drawable vectorDrawable = context.getDrawable(R.id.img_vector);
        bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        vectorDrawable.draw(canvas);
    }else {
        Drawable drawable = AppCompatDrawableManager.get().getDrawable(context, R.id.img_vector);
        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);
    }

5.0以上版本使用

Android studio在2.0的版本中可以直接使用svg
新建一个SVGDemo项目,

新建Vector Asset文件

File -> New -> Vector Asset

导入SVG

可以选择Google提供的Material Icon进行导入也可以选择Local File选择本地svg文件进行导入,一般选择后者。对文件命名后点击Next ->Finish在drawable目录下就添加了一个.xml的文件
点击可以进行预览,一看是不是很像selector、animation-list只是标签为vector标签 width、height为对应的宽高,可以进行设置,viewportWidth、viewportHeight这个我也不太了解大概就是视图的宽高吧,好像作用不大,没有设置过。path为html中的一个标签用来定义路径,我们只关心path标签中的android:fillColor=”#FBDC00”的属性,用来改变颜色。

使用SVG

在layout新建一个activity_svg.xml文件

<ImageView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:src="@drawable/ic_china"/>

就是这样简单,也可以当背景使用

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/ic_china">
</LinearLayout>

5.0以下版本使用SVG

上面说了在Android5.0以上使用svg图片是没有任何问题,但是怎么兼容5.0以下的机型,很多github第三方开源库可以解决,其实google已经给出了解决方案,我们主要了解原生的解决办法。

添加兼容性支持
首先,你需要在项目的build.gradle脚本中,增加对Vector兼容性的支持,代码如下所示:

android { 
	defaultConfig { 
		vectorDrawables.useSupportLibrary = true 
	} 
} 

在defaultConfig中添加了

vectorDrawables.useSupportLibrary = true

当然还需要添加appcompat的支持

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

在ImageView中使用
我们要引用support:appcompat-v7中的view首先我们需要在XML布局文件头部添加:

xmlns:app="http://schemas.android.com/apk/res-auto"

2、 用V7下的AppCompatImageView代替ImageView
3、将android:src属性,换成app:srcCompat即可
代码如下 :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">

<android.support.v7.widget.AppCompatImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:srcCompat="@drawable/ic_china"/>
 </LinearLayout>

根据上文是不是能推测出有app:backgroundCompat属性呢,然而并不如我们所愿,没有这样的属性。
所以我们只能用 android:background这个属性了,先不管这么多了直接4.4的机器上运行试试,果然崩了,在这里说明下在普通控件上使用Vector,就必须依附于StateListDrawable,InsetDrawable,LayerDrawable,LevelListDrawable,RotateDrawable我们选择selector代码如下:

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

这样是不是完了呢?在运行试试还是崩了,这里又是一个坑……
还需要在activity中添加如下代码:

static {
	AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

完美解决问题。

VectorDrawable 与 SVG

Android 5.0(Lollipop, API 21)后,新增了<vector>标签,以VectorDrawable的形式支持SVG类型矢量图形(SVG本质为XML标记描述的图形)。

SVG文件(XML)对应的VectorDrawable资源封装格式为:

 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="64dp"
     android:width="64dp"
     android:viewportHeight="600"
     android:viewportWidth="600" >
     <group
         android:name="rotationGroup"
         android:pivotX="300.0"
         android:pivotY="300.0"
         android:rotation="45.0" >
         <path
             android:name="v"
             android:fillColor="#000000"
             android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
     </group>
 </vector>

※ 通常由SVG文件转换为VectorDrawable,而非手工录入

SVG编辑软件

Inkscape
跨平台开源矢量图形编辑软件,免费 + 三平台通用,支持直接编辑XML数据
类似的软件还有Illustrator、CorelDraw等

SVG 转 VectorDrawable

使用 Vector Asset Studio 转换,在res目录右键菜单上选择 New > Vector Asset
通过开源项目 svg2android 在线转换
※ 初始化VectorDrawable将消耗更多CPU资源,Google推荐将矢量图最大尺寸限定在 200x200dp 以内

VectorDrawable 向后兼容

对于Android 5.0之前的设备,有两种方式兼容VectorDrawable资源:

  1. 利用 Vector Asset Studio 工具动态生成位图
    Android Studio 内置一个名为 Vector Asset Studio 的工具,在低版本SDK上编译APK期间,针对VectorDrawable脚本自动生成一组PNG位图资源BitmapDrawable,取代矢量图形(在5.0及以后的手机上运行时会正常引用VectorDrawable)。

需要配置build.gradle:

defaultConfig {
    vectorDrawables.generatedDensities = ['hdpi','xxhdpi']
}

※ 自动生成PNG时,所支持的VectorDrawable元素子集

  1. 添加 Support Library 23.2+ 兼容包
    Support Library 23.2+ 包中包含VectorDrawableCompat和AnimatedVectorDrawableCompat兼容类,用于代码中桥接为BitmapDrawable及其动画支持,其中Drawable最低SDK为Android 2.1,动画最低SDK为Android 3.0

※ 兼容包要求使用Gradle 2.0或以上版本,并且不支持VectorDrawable嵌套引用,不支持直接解析为VectorDrawable类型,也不再编译期生成PNG位图

build.gradle配置如下:

android {
  defaultConfig {
    vectorDrawables.useSupportLibrary = true
  }
}

dependencies {
  compile 'com.android.support:appcompat-v7:23.2.0'
}

兼容包在矢量资源引用、代码调用时,存在一定限制
在 ImageView 等引用 VectorDrawable 资源时,需要使用app:srcCompat取代android:src
代码中使用setImageResource()指定资源 id 时,无需更改代码
将 VectorDrawable 用于 View 背景时,需要通过以下代码设定:

    Resources resources = context.getResources(Resources, int, Theme);
    Theme theme = context.getTheme();
    Drawable drawable = VectorDrawableCompat.create(resources, R.drawable.vector_drawable, theme);
    view.setBackground(drawable);

代码中需要进行Drawable的实现类型转换时,可使用以下代码段执行:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
   VectorDrawable vectorDrawable =  (VectorDrawable) drawable;
} else {
   BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
}

矢量动画

使用矢量图可进行特殊的动画绘制,比如形态变换、动态绘图等。

主要步骤
定义矢量资源及其关联动画 XML 脚本
创建矢量图形资源VectorDrawable,并在需要动画特效的子元素上使用属性android:name给定任意动画对象名
创建Animator属性动画脚本,可使用ObjectAnimator或AnimatorSet,对VectorDrawable的特定元素属性进行动画控制
创建AnimatedVectorDrawable,作用是连接VectorDrawable与Animator,组合为单一动画型Drawable对象
创建好的AnimatedVectorDrawable不能直接用于 ImageView,需要通过代码设定,并显式开始执行动画
官方文档
XML脚本具体定义方式可参考 开发手册 AnimatedVectorDrawable
开发手册 内联复合XML资源 提供了通过 AAPT 系统插件,实现单一XML脚本混合定义的方式,需要声明命名空间 xmlns:aapt="http://schemas.android.com/aapt"
在线实时输出 XML 脚本
Google工程师开发的开源项目 AndroidIconAnimator,可实现在线实时构造、编辑、预览 VectorDrawable 动画,并导出XML脚本,使用教程可参考 Qiita帖子

代码设置
创建好的 VectorDrawable 动画资源,需要通过代码方式加载到 View 容器内,并指定执行动画

使用原生支持的代码设定(5.0 LOLLIPOP, API 21)

ImageView imageView = (ImageView) findViewById(R.id.imageView);
AnimatedVectorDrawable vectorDrawable = (AnimatedVectorDrawable) getResources().getDrawable(AnimatedVectorDrawableRes, Theme);
imageView.setImageDrawable(vectorDrawable);
vectorDrawable.start();

使用 Support Library 时的动画设置
矢量动画要求最低SDK为Android 3.0,并且不支持<path>路径类型的变换

ImageView imageView = (ImageView) findViewById(R.id.imageView);
AnimatedVectorDrawableCompat drawableCompat = AnimatedVectorDrawableCompat.create(context, AnimatedVectorDrawableRes);
imageView.setImageDrawable(drawableCompat);
drawableCompat.start();

可执行的特有动画效果,可参考 VectorDrawable 元素属性列表
所有属性中文解释可参考 Android矢量图形与矢量动画
其中一种动效实例,可参考博文 Android使用SVG矢量动画
特别介绍几个特殊属性:
<group>元素:
rotation:旋转角度,取值为360角度,valueType=floatType
pivotX / pivotY:旋转中心坐标,以viewport为参照基准
<path>元素:
pathData:矢量图形的绘制路径数据位点,可动画形变为另一种图案,要求动画参数值必须拥有相同长度参数及一致的路径描述指令,并且 ObjectAnimator 中android:valueType="pathType"(Support 包不支持该数据进行变换动画)
trimPathStart:从路径起始点开始向后裁剪(擦除)的相对距离,取值 [0, 1],,0 表示路径起始点,1 表示路径结束点,可实现逐步消退、绘制动效,android:valueType="floatType"
trimPathEnd:从路径结束点开始向前裁剪的相对距离,取值 [0, 1],0 表示路径起始点,1 表示路径结束点
trimPathOffset:从路径起始、结束点开始裁剪的相对距离,取值 [0, 1],0 表示不裁剪,1 表示该路径完全不绘制
<clip-path>元素:遮罩路径、反蒙版路径,该路径范围内的元素才会真正进行绘制,作用于当前所在的<group>内定义在其后面的所有<path>元素
pathData:定义遮罩路径,动画操作方式与<path>相同
pathData 形变动画参考
理解Android VectorDrawable中的pathData命令
PathMorphing with AnimatedVectorDrawables in Android
【VectAlign】pathData自动算法变换对齐工具

已知bug:
<animated-vector>标签在使用时会发生错误警告requires API level 21,不影响兼容包的编译运行
放置于animator目录内的Animator脚本,需要在<animated-vector><target>中手工引用@animator/资源id(自动提示补全菜单失效)
使用动画时,ImageView 不能使用android:tint属性,否则会导致Mutate() is not supported for older platform异常崩溃
通过 AAPT 标签使用单一脚本混合定义时,Android Studio 会出现识别错误提示
矢量动画通过关联 Property Animation 打包为 Drawable 方式实现,对属性的动态修改将影响所有同一 Drawable 资源产生的对象(Drawable 状态共享机制),并且兼容包不支持 mutate() 调用(状态分离,兼容包内要求API 23以上),实际上代码暂时无法访问矢量元素内部属性(如 pathData 等)

文章参考:
作者:慕涵盛华 原文:https://blog.csdn.net/guofudong2/article/details/81299137
作者:Dick_zeng 原文:https://blog.csdn.net/dick_zeng/article/details/72473591
作者:陈立扬 原文:https://www.cnblogs.com/chenliyang/p/6542896.html
作者:woxingxiao 原文:https://www.jianshu.com/p/0555b8c1d26a
作者:慕涵盛华 原文:https://blog.csdn.net/guofudong2/article/details/81299137
http://www.w3school.com.cn/svg/index.asp

发布了70 篇原创文章 · 获赞 176 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/zheng_weichao/article/details/94453946