Android vector clip-path XML实现矢量图裁剪遮罩

8次阅读

android vectordrawable 不支持 clip-path,需用 viewport 调整、shapeableimageview cutoutpath 或 canvas.clippath() 实现裁剪效果。

Android vector clip-path XML实现矢量图裁剪遮罩

clip-path 在 Android vector drawable 中根本不起作用

Android 原生 VectorDrawable 不支持 clip-path 属性——哪怕你照着 SVG 写了 android:clipPath 或在 <group></group> 里加 android:clipPath,它也不会生效,也不会报错,只是静默忽略。这是系统级限制,不是写法问题。

常见错误现象:
xml 里写了 android:clipPath,但预览和运行时图形完全没被裁剪
• 用 android studio 的 Vector Asset Studio 导入带 clipPath 的 SVG,生成的 XML 里该属性被自动删掉或留着但无效
• 尝试用 <path></path>android:pathData 模拟裁剪(比如画个圆再 intersect),结果只是多画了一条路径,不是遮罩

  • 真正起作用的是 <group></group>android:clipChildren="false" + 手动叠加 <path></path> 做「遮罩模拟」,但这不是矢量裁剪,是视觉欺骗
  • 如果目标是「用一个 path 当蒙版盖住另一个 path」,必须改用 LayerDrawable + 两张独立的 VectorDrawable,再靠 PorterDuffXfermode 合成(仅限代码层)
  • Android 12+ 的 DynamicColor 和 Material You 不改变这一限制,clip-path 依然不被解析

android:viewportWidth / android:viewportHeight 配合 android:width / android:height 实现“伪裁剪”

这不是真正的遮罩,但能解决 80% 的实际需求:让矢量图只显示视口范围内的部分,超出的直接截掉。关键在于 viewport 和 intrinsic size 的配合。

使用场景:
• 图标需要固定尺寸(比如 24dp × 24dp),但原始 path 数据画得很大或偏移了
• 要复用同一份 vector XML,在不同容器中显示局部区域(如头像框只取圆形中心)

  • android:viewportWidthandroid:viewportHeight 定义内部坐标系大小(比如 24.0),所有 pathData 坐标都基于它
  • android:widthandroid:height 是最终渲染尺寸(单位 dp),决定缩放比例
  • 真正实现“裁剪感”的是把 <group></group>android:translateX/android:translateYandroid:scaleX/android:scaleY 配合 viewport 移动/缩放内容,让目标区域刚好填满 viewport

示例:想只显示原图中间 16×16 区域,且居中:

<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:translateX="-4.0"         android:translateY="-4.0">         <path             android:fillColor="#FF0000"             android:pathData="M0,0h48v48h-48z"/>     </group> </vector>

这里 viewport 是 24×24,但 path 画了 48×48;通过平移 -4,把左上角从 (0,0) 拉到 (-4,-4),让中间区域进入 viewport 范围 —— 效果等同于裁剪。

真遮罩必须进代码层:用 Canvas.clipPath() + PictureRenderNode

XML 做不到的事,得靠 Canvas.clipPath()。但注意:不能直接在 onDraw() 里对 VectorDrawable 调用 draw() 前 clip,因为 VectorDrawable.draw() 内部会重置 canvas 状态。

可行路径只有两条:

  • 把 vector 转成 Picture,用 PictureDrawable 绘制,再在自定义 View 的 onDraw() 里先 canvas.clipPath(maskPath),再 pictureDrawable.draw(canvas)
  • Android 8.0+ 可用 RenderNode + RecordedCanvas 录制 vector 渲染过程,然后在 onDraw() 中回放并应用 clip(性能更好,但 API 较新)
  • 务必注意:clipPath 的坐标系和 vector 的 viewport 坐标系要对齐,通常需按 getIntrinsicWidth()/getIntrinsicHeight() 缩放 mask path

容易踩的坑:
clipPath() 在硬件加速开启时可能失效(尤其带抗锯齿的 path),需调用 setLayerType(LAYER_TYPE_SOFTWARE, NULL)
• mask path 必须是闭合路径(android:pathData 末尾是 Z),否则 clip 行为未定义
Picture 方式在 Android 10+ 上有兼容性风险,部分机型会跳过 clip

替代方案:用 ShapeableImageView + cutoutPath 做容器级遮罩

如果你的“裁剪”目标是让图标显示在特定形状(圆、圆角矩形、三角形)内,别硬啃 vector clip,直接换容器。

Material Components 提供的 ShapeableImageView 支持通过 app:shapeAppearanceOverlay 或代码设置 cutoutPath,它会在绘制子 view(包括 vector 图标)前,先对 canvas 做一次 clip。

  • 优势:声明式、可复用、适配深色模式和动态颜色
  • 限制:只能裁剪整个 ImageView 区域,不能对 vector 内部多个 <path></path> 单独遮罩
  • 性能影响:比纯 vector 稍重(多了 layer 和 clip 操作),但对图标类小图几乎无感
  • 配置要点:app:srcCompat="@drawable/ic_vector"app:shapeAppearanceOverlay="@style/Shape.Circle",样式里定义 cornerSize 或自定义 cutoutPath

这方法绕开了 vector XML 的所有限制,是绝大多数 ui 场景下最稳的选择。

真正难的从来不是怎么写 clip-path,而是判断该不该在 vector 层做这件事——90% 的所谓“矢量裁剪需求”,其实属于容器布局或绘制流程的问题。

text=ZqhQzanResources