Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android开发笔记(一百三十二)矢量图形与矢量动画

$
0
0

矢量图形VectorDrawable

与水波图形RippleDrawable一样,矢量图形VectorDrawable也是Android5.0之后新增的图形类。矢量图不同于一般的图形,它是由一系列几何曲线构成的图像,这些曲线以数学上定义的坐标点连接而成。具体到实现上,则需开发者提供一个xml格式的矢量图形定义,然后系统根据矢量定义自动计算该图形的绘制区域。因为绘图结果是动态计算得到,所以不管缩放到多少比例,矢量图形都会一样的清晰,不像位图那样拉大后会变模糊。

矢量图形的xml定义有点复杂,其结构可分为三个层次:根标签、组标签、路径标签。


根标签vector

首先是vector标签,它表示当前定义的是一个完整的矢量图形。该标签支持的主要属性说明如下:
android:name:指定矢量图形的名称。
android:width:指定矢量图形的默认宽度,一般使用dp数值。如果在layout布局文件中将ImageView的layout_width设置为wrap_content,同时src设置为该矢量图形,则ImageView控件的宽度就是此处的android:width。
android:height:指定矢量图形的默认高度,一般使用dp数值。
android:viewportWidth:指定视图空间的宽度,即虚拟坐标系的宽度,后续路径的坐标信息都位于该视图空间之内。
android:viewportHeight:指定视图空间的高度,即虚拟坐标系的高度。
android:alpha:指定矢量图形的的透明度,取值为0.0到1.0。

这里要注意width/height与viewportWidth/viewportHeight两组宽高的区别,前者指的是矢量图形被外部世界观察到的尺寸大小,故而采用了带dp单位的绝对数值;而后者指的是矢量图形为内部几何路径所参照的空间范围,故而采用了不带单位的相对数值,正因为矢量图形中的几何路径以相对坐标来标记,所以不管矢量图形缩放到多少比例,其内部的几何形状也会按同样比例缩放。


组标签group

然后是group标签,它定义了一组路径的共同行为(如一起旋转、一起缩放、一起平移等等)。该标签支持的主要属性说明如下:
android:name:指定分组对象的名称。
android:pivotX:指定旋转中心点的横轴坐标。
android:pivotY:指定旋转中心点的纵轴坐标。
android:rotation:指定分组对象的旋转角度。
android:scaleX:指定分组对象在横轴上的缩放比例。取值0.5表示缩小一半,取值2.0表示放大一倍。
android:scaleY:指定分组对象在纵轴上的缩放比例。
android:translateX:指定分组对象在横轴上的平移距离。
android:translateY:指定分组对象在纵轴上的平移距离。


路径标签path

最后是path标签,它定义了一个路径的几何描述,既可以表示一根曲线,也可以表示一块平面区域。该标签支持的主要属性说明如下:
android:name:指定几何路径的名称。
android:pathData:指定几何路径的数据定义。数据格式需符合SVG标准。
android:fillColor:指定平面区域的颜色。若不指定,则不绘制平面区域。
android:fillAlpha:指定平面区域的透明度。
android:strokeColor:指定曲线的颜色。若不指定,则不绘制曲线颜色。
android:strokeWidth:指定曲线的宽度。
android:strokeAlpha:指定曲线的透明度。
android:strokeLineCap:指定曲线的首尾外观。取值说明有三个:butt(默认值,直线边缘)、round(圆形边缘)、square(方形边缘)。
android:strokeLineJoin:指定两条曲线相交的边角外观。取值说明有三个:miter(默认值,锐角)、round(圆角)、bevel(钝角)。
android:trimPathStart:指定几何路径从哪里开始绘制。取值为0.0到1.0,比如取值0.4表示只绘制后面十分之六的内容,前面十分之四不予绘制。
android:trimPathEnd:指定几何路径到哪里结束绘制。取值为0.0到1.0,比如取值0.4表示只绘制前面十分之四的内容,后面十分之六不予绘制。
android:trimPathOffset:指定几何路径的绘制偏移。取值为0.0到1.0,表示线条从trimPathOffset+trimPathStart处一直绘制到trimPathOffset+trimPathEnd处。

路径信息有几个地方容易混淆,下面把相关细节详细说明一下:
1、关于butt和square的区别,乍看起来直线边缘与方形边缘没什么差别,但矢量图形的方形边缘其实是套上一个方形的帽子,既然是套上去,就会比没戴帽子的时候高一点,所以使用square的线条会比使用butt的线条要长一点。
2、关于butt和square的区别,miter保留了原样的尖角,而bevel会把尖角部分切掉一小块,看起来就变钝了。
3、trimPathOffset+trimPathEnd的和如果超过1,也会画出来。只是没有全部画出来,而是绘制从起点到trimPathOffset+trimPathEnd-1所处的位置。


可缩放矢量图形SVG标记

前面说到,path标签的android:pathData属性,取值需符合SVG标准。SVG全称为“Scalable Vector Graphics”,意即可缩放的矢量图形,它是一种图形格式,专门用于描述矢量图形的定义。

SVG标记比较抽象,下面先举个简单的例子,有了直观的概念更方便理解,如下所示:
        android:pathData="
            M 30,50
            L 75 35"
这个标记定义不难,首先“M 30,50”指的是把画笔移动到坐标点(30,50)的位置,后面的“L 75 35”指的是从当前位置画一根线段到坐标点(75,35)。说白了,就是在(30,50)和(75,35)两点之间画一根线段。


好了,每行定义一个动作,每行的第一个字符表示动作的类型,后面的数字表示动作经过的坐标点。这便是SVG标记的大概格式,万变不离其宗,掌握了规律学得更好更快。详细的SVG标记定义说明如下:
移动画笔 “M x0,y0” 把画笔移动到坐标点(x0,y0)。
画线段 “L x1 y1” 从当前位置(x0,y0)画一根线段到坐标点(x1,y1)。
画水平线段 “H x1” 从当前位置(x0,y0)画一根水平线到坐标点(x1,y0)。
画垂直线段 “V y1” 从当前位置(x0,y0)画一根垂直线到坐标点(x0,y1)。
画二次贝塞尔曲线 “Q xa ya x1 y1” 二次贝塞尔曲线的起点是当前位置,终点是(x1,y1),曲线中部向控制点(xa,ya)凸出。
画三次贝塞尔曲线 “C xa ya xb yb x1 y1” 三次贝塞尔曲线的起点是当前位置,终点是(x1,y1),曲线中部有两个控制点,分别向(xa,ya)和(xb,yb)两方向凸出。
画椭圆的圆弧 “A radius-x radius-y x-axis-rotation large-arc-flag sweep-flag x1 y1” 从当前位置拉出一段圆弧,圆弧的参数比较多,分别说明如下:
-- radius-x表示椭圆的横轴半径。
-- radius-y表示椭圆的纵轴半径。横轴半径等于纵轴半径时,表示这是个圆圈的圆弧。
-- x-axis-rotation表示圆弧的旋转角度。
-- large-arc-flag表示大弧标志,为0时表示取小弧度,1时取大弧度。
-- sweep-flag表示轨迹标志,为0表示逆时针方向,为1表示顺时针方向。
-- 圆弧经过某点,该点的横坐标为x1
-- 圆弧经过某点,该点的纵坐标为y1
闭合路径 “Z” 连接起点跟终点,即在起点(x0,y0)与终点之间画一根线段。

再来补充一下SVG标记的若干说明,如下所示:
1、每个命令都有大小写形式,大写代表后面的参数是绝对坐标,小写表示相对坐标。
2、参数之间用空格或逗号隔开,两种分隔符的效果是一样的。
3、关于圆弧的large-arc-flag和sweep-flag两个标志,光看文字说明其实不易理解,还是上个图观察观察:


下面使用SVG标记定义一个心形,先上个心形的效果图:


心形对应的矢量图形定义示例如下:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="256dp"
    android:height="256dp"
    android:viewportHeight="32"
    android:viewportWidth="32">

    <path
        android:fillColor= "#ffaaaa"
        android:pathData= "M20.5,9.5
                        c-1.955,0,-3.83,1.268,-4.5,3
                        c-0.67,-1.732,-2.547,-3,-4.5,-3
                        C8.957,9.5,7,11.432,7,14
                        c0,3.53,3.793,6.257,9,11.5
                        c5.207,-5.242,9,-7.97,9,-11.5
                        C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>


矢量动画AnimatedVectorDrawable

费了老大的劲搞清楚SVG标记,如果仅仅画个静态的矢量图形,未免大材小用了。其实矢量图形真正的意义在于矢量动画,通过动态计算几何路径的坐标,从而实现局部或整体的动画效果,这才是矢量图形的杀手锏呀。


Android提供了AnimatedVectorDrawable这么一个矢量动画类,但开发者还得通过属性动画及其xml标签方可实现动画定义。先看看AnimatedVectorDrawable的几个常用方法:
registerAnimationCallback : 注册动画监听器,需实现Animatable2.AnimationCallback接口的两个方法:onAnimationStart和onAnimationEnd。
start : 开始播放动画。
stop : 停止播放。
reverse : 倒过来播放。
再看看如何通过属性动画实现矢量动画效果。理论上,矢量图形的三个标签(vector、group、path)都有可以用来播放动画的属性;不过实际开发的时候,常用的只有三类属性可用作动画,说明如下:

变换类属性

这类属性包括vector标签的android:alpha,以及group标签的android:rotation、android:scaleX、android:scaleY、android:translateX、android:translateY等等,这几个属性分别对应于补间动画的灰度动画、旋转动画、缩放动画、平移动画。
因为该类属性实现的是大家熟悉的补间动画效果,所以这里就不再做演示了。


路径类属性

这类属性主要指path标签的android:pathData,通过设置几何路径的起始状态与终止状态,可实现两个几何形状之间的渐变效果,如一个圆圈从小变大,又如一条曲线变成直线等等。
下面是个从哭丧脸变为笑脸的动画截图:


下面是人脸的矢量图形定义文件vector_face_eye.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="200dp"
    android:width="200dp"
    android:viewportHeight="100"
    android:viewportWidth="100" >
  <path
      android:fillColor="@color/yellow"
      android:pathData="@string/path_circle"/>
  <path
      android:name="eye_left"
      android:strokeColor="@android:color/black"
      android:strokeWidth="4"
      android:strokeLineCap="round"
      android:pathData="@string/path_eye_left_sad"/>
  <path
      android:name="eye_right"
      android:strokeColor="@android:color/black"
      android:strokeWidth="4"
      android:strokeLineCap="round"
      android:pathData="@string/path_eye_right_sad"/>
  <path
      android:name="mouth"
      android:strokeColor="@android:color/black"
      android:strokeWidth="4"
      android:strokeLineCap="round"
      android:pathData="@string/path_face_mouth_sad"/>
</vector>


接着是脸部三处器官变化的属性动画定义文件。
下面是左眼的属性动画定义文件anim_smile_eye_left.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="3000"
  android:propertyName="pathData"
  android:valueFrom="@string/path_eye_left_sad"
  android:valueTo="@string/path_eye_left_happy"
  android:valueType="pathType"
  android:interpolator="@android:anim/accelerate_interpolator"/>
下面是右眼的属性动画定义文件anim_smile_eye_right.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="3000"
  android:propertyName="pathData"
  android:valueFrom="@string/path_eye_right_sad"
  android:valueTo="@string/path_eye_right_happy"
  android:valueType="pathType"
  android:interpolator="@android:anim/accelerate_interpolator"/>
下面是嘴巴的属性动画定义文件anim_smile_mouth.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="3000"
  android:propertyName="pathData"
  android:valueFrom="@string/path_face_mouth_sad"
  android:valueTo="@string/path_face_mouth_happy"
  android:valueType="pathType"
  android:interpolator="@android:anim/accelerate_interpolator"/>


最后是笑脸的矢量动画定义例子animated_vector_smile_eye.xml:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_face_eye" >

    <target
        android:name="mouth"
        android:animation="@anim/anim_smile_mouth" />

    <target
        android:name="eye_left"
        android:animation="@anim/anim_smile_eye_left" />
    
    <target
        android:name="eye_right"
        android:animation="@anim/anim_smile_eye_right" />
    
</animated-vector>


不要忘了在代码中进行矢量动画的播放操作:
	private void startVectorSmile() {
		iv_vector_smile.setImageResource(R.drawable.animated_vector_smile_eye);
		Drawable drawable = iv_vector_smile.getDrawable();
		if (drawable instanceof AnimatedVectorDrawable) {
			((AnimatedVectorDrawable) drawable).start();
		}
	}


修剪类属性

这类属性包括path标签的android:trimPathStart和android:trimPathEnd,可实现矢量图形逐步展开或者逐步消失的动画效果。
下面是个支付宝支付成功的动画截图:


支付成功动画包含两个形状,首先在外面画个圆圈,然后在圆圈里面画个打勾符号。因为圆圈和打勾并不相连,如果按照一般的处理,就会一边画圆圈一边画打勾,这不是我们所希望的画完圆圈再画打勾的效果。所以要想让圆圈动画和打勾动画按顺序播放,得分别定义圆圈的矢量图形和打勾的矢量图形,然后等圆圈动画播放完毕,再开始播放打勾动画。


下面是圆圈的矢量图形定义文件vector_pay_circle.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="100dp"
    android:viewportHeight="100"
    android:viewportWidth="100"
    android:width="100dp" >

    <path
        android:name="circle"
        android:pathData="
            M 10,50
            A 40 40 0 1 0 10 49"
        android:strokeAlpha="1"
        android:strokeColor="@color/blue_sky"
        android:strokeLineCap="round"
        android:strokeWidth="3" />

</vector>
下面是打勾的矢量图形(含圆圈图形)定义文件vector_pay_success.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="100dp"
    android:viewportHeight="100"
    android:viewportWidth="100"
    android:width="100dp" >

    <path
        android:name="circle"
        android:pathData="
            M 10,50
            A 40 40 0 1 0 10 49"
        android:strokeAlpha="1"
        android:strokeColor="@color/blue_sky"
        android:strokeLineCap="round"
        android:strokeWidth="3" />
    <path
        android:name="hook"
        android:pathData="
            M 30,50
            L 45 65
            L 75 35"
        android:strokeAlpha="1"
        android:strokeColor="@color/blue_sky"
        android:strokeLineCap="butt"
        android:strokeWidth="3" />

</vector>


接着是支付成功的属性动画的xml定义文件anim_pay.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:interpolator/linear"
    android:propertyName="trimPathEnd"
    android:valueFrom="0"
    android:valueTo="1"
    android:valueType="floatType" />


最后是矢量动画的定义文件,下面这个用来播放圆圈动画:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_pay_circle">

    <target
        android:name="circle"
        android:animation="@anim/anim_pay" />

</animated-vector>
下面这个用来播放圆圈动画后继的打勾动画:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_pay_success">

    <target
        android:name="hook"
        android:animation="@anim/anim_pay" />

</animated-vector>


圆圈动画播放完毕,接着播放打勾动画,这要在代码中控制,具体的是调用AnimatedVectorDrawable对象的registerAnimationCallback方法,一旦监听到原动画播放结束,然后开始播放新动画。





点此查看Android开发笔记的完整目录
作者:aqi00 发表于2017/2/9 11:02:25 原文链接
阅读:0 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles