VerctorDrawable
Vector Drawable 是 Android 5.0+ 引入的矢量图形格式,相比位图具有无限缩放不失真和体积小的优势。
PathData 命令列表
| 命令 |
描述 |
大小写含义 |
| M |
移动到 (MoveTo) |
M 绝对位置,m 相对位置 |
| L |
画直线到 (LineTo) |
L 绝对位置,l 相对位置 |
| H |
水平直线 (Horizontal LineTo) |
H 绝对位置,h 相对位置 |
| V |
垂直直线 (Vertical LineTo) |
V 绝对位置,v 相对位置 |
| C |
三次贝塞尔曲线 (Cubic Bezier Curve) |
C 绝对位置,c 相对位置 |
| S |
平滑三次贝塞尔曲线 (Smooth Cubic Bezier) |
S 绝对位置,s 相对位置 |
| Q |
二次贝塞尔曲线 (Quadratic Bezier Curve) |
Q 绝对位置,q 相对位置 |
| T |
平滑二次贝塞尔曲线 (Smooth Quadratic Bezier) |
T 绝对位置,t 相对位置 |
| A |
椭圆弧线 (ArcTo) |
A 绝对位置,a 相对位置 |
| Z |
关闭路径 (Close Path) |
Z 或 z(没有相对位置) |
贝塞尔曲线的阶数由控制点的数量决定:
一阶贝塞尔曲线:2 个控制点,是一条直线。
二阶贝塞尔曲线:3 个控制点,是一条抛物线。
三阶贝塞尔曲线:4 个控制点,可以生成更复杂的曲线。
N 阶贝塞尔曲线:N+1 个控制点。
贝塞尔曲线第一个点为起点,最后一个点为终点,中间的点为曲线形状控制点。
各个命令的详细说明
命令的负数例如-1 -20 ,指的是反方向
M 100,200 移动到绝对位置 (100,200)
m 50,50 继续移动 50 个单位到相对位置 (150,250)
L 200,200 画线到绝对位置 (200,200)
l 50,0 画线到相对位置 (250,200)
H 300 从当前位置水平画线到 X 坐标 300
h -50 向左画 50 个单位
V 400 从当前位置垂直画线到 Y 坐标 400
v 30 向下画 30 个单位
使用控制点绘制三次贝塞尔曲线。
C 50,100 150,100 200,200
(50,100) 和 (150,100) 是控制点
(200,200) 是终点
使用平滑的三次贝塞尔曲线,前一个曲线的终点会自动成为新的控制点。
S 250,150 300,200
使用自动生成的控制点,形成流畅的曲线
语法:
s dx2,dy2 dx3,dy3
(x2,y2):第二个控制点(自动生成第一个控制点)。
(x3,y3):终点(End Point)
自动平滑连接:第一个控制点基于前一条 C/S 曲线的第二个控制点对称生成,确保曲线过渡平滑。
减少代码量:只需指定一个控制点和终点,适合连续平滑曲线(如波浪线、复杂路径)。
依赖前一条曲线:必须跟在 C 或 S 指令后,首条曲线需用 C。
一个例子:
<path
android:fillColor="@color/purple_200"
android:strokeWidth="0.1"
android:strokeColor="@color/white"
android:pathData="M10,0C5,5,20,8,10,10,S15,18,10,20z"/>
使用控制点绘制二次贝塞尔曲线。
Q 150,50 200,200
(150,50) 是控制点
(200,200) 是终点
使用平滑的二次贝塞尔曲线。
语法:T x y
(x, y) 是终点,控制点会自动从上一个 Q/T 的控制点对称推导出来 例如:
T 300,300
自动推算控制点,形成平滑的曲线。
如果前面有 Q 或 T 指令,它会用上一个控制点相对于当前点的对称点作为新的控制点。如果前面没有 Q 或 T,则控制点默认就是当前点(等于直线)
自动平滑连接:T 指令会以前一条曲线的控制点为基准,生成一个对称的新控制点,确保连接处平滑过渡。
仅需指定终点,适合连续绘制平滑曲线。
首条曲线必须用 Q:T 不能单独使用,必须跟在 Q 或另一个 T 之后。
一个例子:
<path
android:fillColor="@color/purple_200"
android:strokeWidth="0.1"
android:strokeColor="@color/white"
android:pathData="M10,5Q1,10,10,14T10,22z"/>
<path
android:fillColor="@color/purple_200"
android:strokeWidth="0.1"
android:strokeColor="@color/white"
android:pathData="M10,5Q1,5,10,10T10,15T10,18z"/>
绘制椭圆弧。
A 50,50 0 1,1 0.1,0
50,50 是椭圆的半径,分别是 x 轴和 y 轴的半径
0 是椭圆的旋转角度
1,1 分别表示大弧标志和顺时针方向,保持1,1即可 1:画大弧(>180°)1:顺时针方向
0.1,0 终点坐标,A就是决定坐标,a是相对坐标,终点相对于起点的偏移(微小偏移使路径闭合)
clip-path
clip-path 是 Android Vector Drawable 中用于裁剪图形区域的属性,它指定了一个路径,这条路径将用来限制你绘制的图形内容,使其只显示在该路径之内。
clip-path 是局部的,它只对包含它的 <group> 或 <path> 有效
一个例子:
<vector
android:width="200dp"
android:height="200dp"
android:viewportWidth="200"
android:viewportHeight="200">
<!-- 定义裁剪路径 -->
<clip-path
android:name="circleClip"
android:pathData="M100,100 m-50,0 a50,50 0 1,0 100,0 a50,50 0 1,0 -100,0"/>
<!-- 应用裁剪路径到 path -->
<group android:clipPath="circleClip">
<path
android:fillColor="#FF0000"
android:pathData="M0,0 h200 v200 h-200 z"/>
</group>
</vector>
关键参数
| 属性 |
说明 |
| width/height |
图像实际大小(dp) |
| viewportWidth/viewportHeight |
虚拟坐标系,pathData 基于此绘制 |
| pathData |
矢量路径,语法类似 SVG d 属性 |
| fillColor |
填充颜色 |
| strokeColor / strokeWidth |
描边颜色和宽度 |
| android:fillType |
nonZero 非零环绕规则(默认),路径方向有关(顺时针+1,逆时针-1)
🌀 nonZero(非零环绕规则)
如果某个点被路径包围多次,方向相加(顺时针为 +1,逆时针为 -1)。
只要最终结果 ≠ 0,就会被填充。
路径方向影响结果!
🔍 举个例子:
一个圈里套一个反方向画的小圈(比如 donut),中间是空的,因为正负方向抵消了。
如果两个圈方向一样,中间也会被填充。
evenOdd 奇偶规则,路径边界穿过奇数次算内部,偶数次算外部
不管方向,只数边界穿过的次数。
奇数次穿过,算里面,填充。偶数次,不填。
常用于绘制镂空图形、剪纸风格效果。
典型应用场景
五角星等星形图案:使用 evenOdd 可以正确填充星形内部而不填充中心区域
环形/甜甜圈形状:evenOdd 可以只填充环形部分而不填充中心孔洞
复杂交叉路径:当路径有自交叉时,evenOdd 通常能产生更直观的结果
|
| android:tint |
给整个矢量图应用一个颜色滤镜。无论你定义了什么 fillColor 或 strokeColor,都会被这个 tint 色覆盖 |
| android:trimPathStart/trimPathEnd/trimPathOffset |
android:trimPathStart 是 Android Vector Drawable 中 path 元素的一个属性,用于控制路径的可见起点位置,搭配 trimPathEnd 和 trimPathOffset 一起使用,可以实现路径绘制动画效果(比如画线、描边动画等)。属性允许你指定路径绘制的起始位置,即从路径的哪个百分比位置开始绘制。它的值范围是 0 到 1:
0 表示从路径的起点开始绘制(默认值)
1 表示从路径的终点开始绘制(即不绘制任何部分)
0.5 表示从路径的中间点开始绘制
trimPathStart 通常与以下属性一起使用:android:trimPathEnd - 控制路径绘制的结束点,android:trimPathOffset - 控制路径绘制的偏移量
|
| android:fillAlpha/strokeAlpha |
设置填充颜色的透明度,控制填充颜色的不透明度(0.0 到 1.0)0.0 表示完全透明,1.0 表示完全不透明如果不设置 fillColor,即使设置了 fillAlpha 也不会显示填充颜色。
可以和 strokeAlpha 搭配使用,分别控制填充和描边的透明度。也可以通过动画控制 fillAlpha 实现“淡入淡出”的效果。如果 fillColor 已经包含 alpha 通道(如 #80FF0000),则实际透明度是 fillAlpha 和 fillColor alpha 的乘积
优先使用 fillAlpha 属性来设置透明度,因为它更直观且易于动画处理 |
| android:strokeMiterLimit |
用于控制路径描边时尖角连接(miter join)的最大长度比例。
当两条线段以锐角相交并使用 android:strokeLineJoin="miter"(尖角连接)时,strokeMiterLimit 决定了尖角延伸长度的限制:
作用原理:尖角连接会延伸两条线段的外边缘直到它们相交,形成一个尖角
限制条件:当尖角长度超过 strokeWidth * strokeMiterLimit 时,会自动转换为斜角连接(bevel join)
默认值:4(Android默认值)
尖角长度(miter length)与 strokeWidth 的关系:miterLength = strokeWidth / sin(θ/2) 其中 θ 是两条线段的夹角
当 miterLength/strokeWidth > strokeMiterLimit 时,连接方式会从 miter 降级为 bevel
典型应用场景
锐角图形:需要保持尖角效果的图形(如星形、箭头)
避免过长尖刺:防止极锐角产生过长的尖刺
视觉一致性:确保不同角度的连接保持相似的视觉权重
注意事项
仅当 strokeLineJoin="miter" 时此属性才有效
值必须 ≥ 1,通常建议范围在 1-10 之间
过小的值会导致过多的斜角连接(bevel)
过大的值可能导致极锐角产生非常长的尖刺
从 API 级别 21 (Lollipop) 开始支持
|
一个例子:这样路径就会“从无到有”地被绘制出来,实现线条动画。
<path
android:fillColor="@color/purple_200"
android:strokeWidth="0.1"
android:strokeColor="@color/white"
android:trimPathStart="0"
android:trimPathEnd="0.5"
android:trimPathOffset="0.1"
android:pathData="M5,5l5,5" />
<objectAnimator
android:duration="1000"
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
一个例子:这样路径的填充颜色会从透明慢慢变为完全可见。
<path
android:fillColor="@color/purple_200"
android:strokeWidth="0.1"
android:strokeColor="@color/white"
android:fillAlpha="0.7"
android:strokeAlpha="0.5"
android:pathData="M5,5l5,5h-5z" />
<objectAnimator
android:duration="1000"
android:propertyName="fillAlpha"
android:valueFrom="0.0"
android:valueTo="1.0"
android:valueType="floatType" />
路径变换
在 group上可以设置以下变换属性:
| 属性 |
类型 |
作用 |
| android:rotation |
float |
顺时针旋转角度,单位是度(°) |
| android:pivotX |
float |
旋转中心的x坐标,如果不设置默认的旋转点是0.0
|
| android:pivotY |
float |
旋转中心的y坐标,如果不设置默认的旋转点是0.0
|
| android:scaleX |
float |
X 轴方向缩放系数 |
| android:scaleY |
float |
Y 轴方向缩放系数 |
| android:translateX |
float |
沿 X 轴的平移距离(单位:视图坐标单位 viewportWidth中定义的坐标数量) |
| android:translateY |
float |
沿 Y 轴的平移距离(单位:视图坐标单位 viewportHeight中定义的坐标数量) |
推荐使用 group 标签对多个 path统一应用变换
可以用多层group实现多个动画的组合,例如:
外层 group 控制整体缩放
内层 group 控制局部旋转
各自可以有不同动画效果
一个例子:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="108dp">
<group
android:scaleX="0.6"
android:scaleY="0.6"
android:translateX="4.8"
android:translateY="4.8">
<path android:fillColor="#0528D5"
android:pathData="M14,0
Q24,0,24,10,
L12,12,
L10,24
Q0,24,0,14
L12,12
Z"/>
<path android:fillColor="@color/teal_200"
android:pathData="M10,0
Q0,0,0,10
L12,12,
L14,24
Q24,24,24,14
L12,12
z"/>
</group>
</vector>
设置 pivot 让缩放后中心不变,这样缩放时是绕 (50, 50) 缩放的,图形会原地放大,中心不会偏移。
<group
android:scaleX="2.0"
android:scaleY="2.0"
android:pivotX="50"
android:pivotY="50">
gradient渐变色设置
在 Android Vector Drawable 中,渐变是在 元素的 fillColor 或 strokeColor 中,通过使用 标签嵌入来实现的。
| 属性 |
说明 |
| android:type |
linear 或 radial |
| android:startX, startY |
渐变起点坐标(相对于 path 的坐标系统,仅线性渐变) |
| android:endX, endY |
渐变终点坐标(仅线性渐变) |
| android:centerX, centerY |
渐变中心(仅径向渐变) |
| android:gradientRadius |
渐变半径,仅对径向渐变有效,相对于 path 的坐标系统 |
| android:tileMode |
渐变重复方式:clamp(默认)使用边缘颜色填充外部 / repeat 重复渐变/ mirror 镜像重复渐变 |
<item>:颜色节点
每个 <gradient> 必须包含一个或多个 <item> 来定义渐变的颜色和位置。
<item android:offset="0" android:color="#FF0000"/>
<item android:offset="0.5" android:color="#00FF00"/>
<item android:offset="1" android:color="#0000FF"/>
一个例子:定义线性渐变色
<path
android:pathData="M6,6v12L15,12Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="6"
android:startY="12"
android:endX="15"
android:endY="12"
android:type="linear">
<item android:offset="0" android:color="#FF0000"/>
<item android:offset="0.5" android:color="#00FF00"/>
<item android:offset="1" android:color="#0000FF"/>
</gradient>
</aapt:attr>
</path>
一个例子:径向渐变
<path
<android:pathData="M6,6v12L15,12Z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="6"
android:centerY="12"
android:gradientRadius="1"
android:tileMode="mirror"
android:type="radial">
<item android:offset="0" android:color="#FF0000"/>
<item android:offset="1" android:color="#0000FF"/>
</gradient>
</aapt:attr>
绘图建议
1、多个 path 分层绘制,使用 group 可以一起变换、旋转多个 path;好处:更易维护、动画支持更强。
<group>
<path ... />
<path ... />
</group>
2、控制 stroke 描边效果,
strokeLineCap: round / butt / square;
strokeLineJoin: round / bevel / miter;
strokeLineCap是控制线条“端点”的样式
控制线条的端点形状(仅对“开放路径”生效)
当一条线结束时,线的“头尾”应该长什么样?
| 属性值 |
说明 |
效果图(想象一条水平线) |
| butt |
默认,直接截断 |
—— |
| round |
端点为圆形 |
⚫——⚫ |
| square |
方形,但超出线末尾一点 |
▬▬(两头稍突出) |
strokeLineJoin 是控制两条线“拐角处”的连接方式
比如一个折线的两个线段拐角怎么拼接?
| 属性值 |
说明 |
效果 |
| miter |
尖角(默认) |
↖️🟩↗️ |
| round |
圆角连接 |
拐角是个小圆 |
| bevel |
斜角连接 |
拐角被“削平” |
<path
android:strokeColor="#000"
android:strokeWidth="1"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
3、结合 AnimatedVectorDrawable 做动画
创建 animated-vector.xml 文件,引用 VectorDrawable 并指定动画;
可做 pathMorph(变形)、颜色变化、旋转、缩放等;
需要 API 21+。
4、动态颜色控制,通过主题属性或 ColorStateList 实现换肤
<path
android:fillColor="?attr/colorPrimary"
android:strokeColor="@color/stateful_color"
android:strokeWidth="2dp"/>
5、路径动画(API 25+)
<animated-vector
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_base">
<target android:name="star" android:animation="@animator/path_morph"/>
</animated-vector>
配合 ObjectAnimator 实现路径变形:
<!-- res/animator/path_morph.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="pathData"
android:duration="300"
android:valueFrom="M起始路径..."
android:valueTo="M目标路径..."
android:valueType="pathType"/>
</set>
6、渐变填充(API 24+)
<path>
<aapt:attr name="android:fillColor">
<gradient
android:startColor="#FF00FF"
android:endColor="#00FFFF"
android:type="linear"
android:startX="0"
android:startY="0"
android:endX="100%"
android:endY="100%"/>
</aapt:attr>
</path>
7、性能优化技巧
1)简化路径数据
示例优化前: M10 10 L20 10 L20 20 L10 20 Z
优化后: M10 10H20V20H10Z
2)分层渲染复杂图形
<layer-list>
<item android:drawable="@drawable/vector_layer1"/>
<item android:drawable="@drawable/vector_layer2"/>
</layer-list>
3)启用硬件加速
imageView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
8、常见问题解决
1)锯齿问题
<vector android:antialias="true"/>
2)、点击区域不匹配
java
view.setTouchDelegate(new TouchDelegate(rect, vectorView));
3)、内存泄漏预防
java
// 在Activity销毁时
vectorDrawable.setCallback(null);
VerctorDrawableAnimator
android verctor结合animator可以做出好看的动画,这里展示一个例子
图像文件存放在drawable/new_icon_test9.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="108dp">
<path
android:name="test9"
android:strokeWidth="0.1"
android:strokeColor="@color/purple_700"
android:trimPathStart="0"
android:trimPathEnd="1"
android:pathData="M8,5v14l10,-7"/>
</vector>
动画文件存放在animator/test9_animator.xml,动画文件必须放在这个文件夹下面
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"/>
动画和图像连接文件,放在drawable/test9_animator_drawable.xml
<animated-vector
android:drawable="@drawable/new_icon_test9"
xmlns:android="http://schemas.android.com/apk/res/android">
<target
android:animation="@animator/test9_animator"
android:name="test9"/>
</animated-vector>
实现代码
@OptIn(ExperimentalAnimationGraphicsApi::class)
@Composable
fun test10Animator(){
var isPlaying by remember { mutableStateOf(false) }
val animatorDrawable = AnimatedImageVector.animatedVectorResource(R.drawable.test10_animator_drawable)
val painter = rememberAnimatedVectorPainter(animatorDrawable,isPlaying)
// Icon(painter,
// tint = Color.Unspecified, //不加这个属性,会导致图像颜色被默认的黑色覆盖,Icon(...) 组件会默认应用一个颜色 “tint” 到 painter 上,导致你矢量图的原始颜色被覆盖成了黑色。直接用Image就不会
// contentDescription = "",
// modifier = Modifier.size(200.dp).clickable{isPlaying = !isPlaying}
// )
Image(painter,
contentDescription = "",
modifier = Modifier.size(200.dp)
.clickable{
isPlaying = !isPlaying
})
}