filltype="evenodd" 和 filltype="nonzero" 的填充区域取决于路径走向与射线交点的有向代数和(nonzero)或奇偶计数(evenodd),而非视觉形状;内环需反向绘制才能在 evenodd 下正确镂空。

fillType="evenOdd" 和 fillType="nonZero" 到底填哪块?
关键看 path 自身的走向和交叉逻辑,不是看形状“看起来”有没有洞——很多开发者画了个带内环的图标,设了 fillType="evenOdd" 却发现内环没被挖空,其实是路径方向写反了。
Android 的 fillType 控制的是「描边闭合后,像素点是否被判定为内部」的填充规则,底层用的是 Skia 的 winding rule。它不解析几何拓扑,只数路径段穿过某点的「有向次数」。
-
nonZero:从点引一条射线,统计所有与之相交的路径段的「方向代数和」;只要和 ≠ 0,就填充 -
evenOdd:同样引射线,只看交点总数是奇数还是偶数;奇数才填充
所以哪怕你画了两个完全重叠的圆形,方向相反,nonZero 下它们会互相抵消(净绕数为 0),结果不填;而 evenOdd 只数 2 个交点 → 偶数 → 也不填。但若只是嵌套、方向一致,nonZero 会填满整个外轮廓,evenOdd 才能挖空内环。
vector drawable 中 pathData 的方向怎么影响 fillType?
Android 不强制要求顺时针/逆时针,但 fillType 的行为高度依赖你手写的 pathData 走向。尤其是用工具导出 SVG 转 vector 时,常见问题就是内环被反向生成,导致 evenOdd 失效。
典型错误现象:android:pathData="M100,100 L200,100 L200,200 L100,200 Z M150,150 L180,150 L180,180 L150,180 Z" —— 两个矩形都是顺时针闭合,evenOdd 下内矩形不会被挖掉(因为交点数是偶数,但实际渲染仍实心);必须把内环改成逆时针,比如 M150,150 L150,180 L180,180 L180,150 Z,才能正确镂空。
- 用 Android Studio 的 Vector Asset Studio 导入 SVG 时,默认启用「Optimize for vector drawables」,它会自动翻转内环方向适配
evenOdd - 手动写
pathData时,建议用nonZero+ 统一顺时针方向,最稳;需要镂空再切到evenOdd并检查内环走向 - Chrome DevTools 或 Inkscape 的「Path > Reverse Direction」可快速验证/修正方向
为什么有时 evenOdd 在低版本 Android 上表现异常?
Android 5.0(API 21)才正式支持 fillType 属性;5.0 之前该字段被直接忽略,默认按 nonZero 行为处理。更隐蔽的问题在 API 21–22:某些复杂自相交路径下,evenOdd 渲染结果与高版本不一致,比如一个「8」字形路径,部分设备上中间交叠区被误填。
- 目标最低 SDK ≥ 23 时,
evenOdd行为基本可靠;低于 23 必须做兼容降级(例如拆成多个独立group或改用 bitmap fallback) - 避免在
evenOdd路径中使用c(三次贝塞尔)或s(平滑贝塞尔)产生隐式自交,优先用直线段 + 圆弧组合 - 真机测试比模拟器更准——部分模拟器对 Skia winding 实现有简化
fillType 对性能和内存有影响吗?
几乎没有。无论是 evenOdd 还是 nonZero,Skia 在光栅化阶段才做 winding 判定,且只对每个像素做一次射线投射(优化后实际是扫描线算法),开销远小于 path 解析或 shader 计算。
真正影响性能的是 pathData 复杂度:1000 个点的路径,无论什么 fillType,解析和缓存成本都一样高;而一个带 10 层嵌套环的简单路径,evenOdd 也比 10 条独立路径快得多。
- 别为了“省性能”硬改
fillType;该镂空就用evenOdd,该保实心就用nonZero - 过度拆分
path(比如把一个图标拆成 20 个path元素)反而增加 XML 解析和绘制状态切换开销 - VectorDrawable 的内存占用主要取决于
pathData字符串长度和节点数,跟fillType无关
绕数规则本身很简单,难的是你画的路径到底怎么走——盯着 pathData 最后那个 Z 前的点序,比查文档管用。








