Canvas API详解(Part 2)剪切方法合集
Canvas API 详解 (Part 2):剪切方法合集
在 Android 的 Canvas
API 中,剪切(Clipping)是一种限制绘制区域的技术,用于控制绘图操作仅在指定区域内生效。剪切操作会修改画布的裁剪区域(Clip Bounds),后续的绘制(如形状、文本、位图)只在该区域内可见,常用于实现复杂形状的遮罩、动画裁剪或性能优化。本部分(Part 2)聚焦于 Canvas
的剪切方法合集,作为整体 API 详解的延续。剪切通常与状态管理(save/restore)结合使用,以避免永久修改画布状态。后续 Part 3 将深入矩阵变换(translate/scale/rotate 等)和层级管理(saveLayer)。
剪切的作用与原理
- 作用:剪切定义了一个“可见窗口”,所有绘制操作都会被限制在该窗口内。默认情况下,画布的剪切区域为其整个大小(由 Bitmap 或 View 决定)。剪切可以多次叠加,形成交集(默认使用
Op.INTERSECT
),或通过Op
参数指定其他布尔操作(如差集、并集)。 - 原理:剪切操作修改画布的内部 ClipRegion,后续绘制会检查像素是否在 Clip 内。剪切是不可逆的,除非使用
save()
和restore()
恢复状态。 - 应用场景:
- 创建不规则形状的遮罩(如圆形头像裁剪)。
- 动画效果(如渐变揭示内容)。
- 优化绘制(减少不必要的渲染区域)。
- 结合路径实现复杂图形(如波浪边缘)。
剪切方法不支持硬件加速下的某些复杂操作(如非矩形剪切),可能需切换到软件渲染(View.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
)。
剪切方法合集
Canvas
提供了多种剪切方法,主要基于矩形(Rect)和路径(Path)。以下是所有剪切方法的表格总结,包括参数、API 级别和用法:
方法名称 | 描述 | 主要参数 | API 级别 | 用法示例 |
---|---|---|---|---|
clipRect(Rect rect, Region.Op op) | 以矩形区域剪切画布,支持布尔操作(Op)。默认 Op 为 INTERSECT(交集)。 | rect (Rect,剪切矩形);op (Region.Op,可选,默认为 INTERSECT) | API 1+ | canvas.clipRect(new Rect(50, 50, 150, 150), Region.Op.INTERSECT); |
clipRect(float left, float top, float right, float bottom) | 以浮点坐标定义矩形剪切,默认 INTERSECT 操作。 | left, top, right, bottom (float,矩形边界) | API 1+ | canvas.clipRect(50f, 50f, 150f, 150f); |
clipPath(Path path, Region.Op op) | 以自定义路径剪切画布,支持布尔操作。路径可为任意形状。 | path (Path,非空);op (Region.Op,可选,默认为 INTERSECT) | API 1+ | Path path = new Path(); path.addCircle(100, 100, 50, Path.Direction.CW); canvas.clipPath(path); |
clipOutRect(Rect rect) | 排除指定矩形区域(差集操作),即在当前剪切外移除该矩形。 | rect (Rect,要排除的矩形) | API 26+ | canvas.clipOutRect(new Rect(50, 50, 150, 150)); |
clipOutPath(Path path) | 排除指定路径区域(差集操作)。 | path (Path,要排除的路径) | API 26+ | canvas.clipOutPath(path); |
clipRegion(Region region, Region.Op op) | 以 Region(区域对象)剪切,支持布尔操作。但该方法已弃用(deprecated)。 | region (Region,非空);op (Region.Op,可选) | API 1+ (deprecated) | 不推荐使用;用 clipRect 或 clipPath 替代。 |
Region.Op 枚举说明(用于 clipRect 和 clipPath):
INTERSECT
:交集(默认,保留共同区域)。DIFFERENCE
:差集(从当前剪切中移除新区域)。UNION
:并集(合并区域)。XOR
:异或(对称差集)。REVERSE_DIFFERENCE
:反差集(从新区域中移除当前剪切)。REPLACE
:替换(用新区域完全替换当前剪切)。
注意:多次剪切会累积效果(交集),使用 save()
和 restore()
可隔离剪切作用域。clipOut
方法相当于 clipRect
的 DIFFERENCE
操作,但更高效(API 26+)。
代码示例
以下是一个自定义 View 示例,演示各种剪切方法的结合使用。复制到 Android 项目中运行,可在画布上看到剪切效果。
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;
public class CanvasClipView extends View {
private Paint paint;
public CanvasClipView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 准备一个全屏矩形作为测试绘制
Rect fullRect = new Rect(0, 0, getWidth(), getHeight());
// 1. 简单矩形剪切(INTERSECT)
canvas.save(); // 保存状态
canvas.clipRect(50, 50, 150, 150); // 剪切到小矩形
canvas.drawColor(Color.BLUE); // 只在剪切内填充蓝色
canvas.restore(); // 恢复状态
// 2. 路径剪切(圆形遮罩)
canvas.save();
Path path = new Path();
path.addCircle(100, 250, 50, Path.Direction.CW);
canvas.clipPath(path); // 剪切到圆形
canvas.drawRect(fullRect, paint); // 只在圆内绘制红色矩形
canvas.restore();
// 3. 布尔操作:UNION(并集)
canvas.save();
canvas.clipRect(50, 350, 150, 450, Region.Op.REPLACE); // 先替换为一个矩形
canvas.clipRect(100, 400, 200, 500, Region.Op.UNION); // 并集另一个矩形
canvas.drawColor(Color.GREEN);
canvas.restore();
// 4. clipOutRect(排除矩形,API 26+)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
canvas.save();
canvas.clipRect(50, 550, 150, 650); // 先剪切到一个矩形
canvas.clipOutRect(75, 575, 125, 625); // 排除内部小矩形,形成“孔”
canvas.drawColor(Color.YELLOW);
canvas.restore();
}
// 5. 结合路径和排除(clipOutPath,API 26+)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
canvas.save();
Path outerPath = new Path();
outerPath.addRect(50f, 700f, 150f, 800f, Path.Direction.CW);
canvas.clipPath(outerPath);
Path holePath = new Path();
holePath.addCircle(100, 750, 25, Path.Direction.CW);
canvas.clipOutPath(holePath); // 排除圆形孔
canvas.drawColor(Color.MAGENTA);
canvas.restore();
}
}
}
- 运行效果:从上到下依次显示小蓝色矩形、红色圆形、绿色并集矩形、黄色带孔矩形(API 26+)、紫色带圆孔矩形(API 26+)。
- 状态管理扩展:在示例中,使用
canvas.save()
和canvas.restore()
隔离每个剪切操作,避免相互干扰。save()
返回一个整数层级 ID,可用于restoreToCount(int saveCount)
恢复到指定层级。
注意事项
- 性能:复杂剪切(如非矩形路径)会增加渲染开销,尤其在硬件加速下。矩形剪切(clipRect)最高效。
- 兼容性:基本剪切自 API 1 起支持,
clipOut
需要 API 26+。clipRegion
已弃用,避免使用。 - 查询剪切状态:使用
canvas.getClipBounds(Rect bounds)
获取当前剪切边界矩形;canvas.quickReject(Rect rect)
检查矩形是否完全在剪切外(优化绘制)。 - 与绘制结合:剪切后绘制会自动裁剪,但位图绘制可能需额外处理(如
drawBitmap
的矩阵)。 - 错误处理:如果剪切区域为空,后续绘制无效。确保路径或矩形有效。
- 调试:使用
canvas.getClipBounds()
在 Log 中输出边界,验证剪切效果。
扩展:Jetpack Compose 中的等效实现
在 Jetpack Compose 中,剪切功能通过 Modifier.clip
或 drawWithContent
实现,更具声明式:
- 矩形剪切:
Modifier.clip(RectangleShape)
或自定义形状。 - 路径剪切:使用
clipToBounds
或Path
在Canvas
内。 - 示例(圆形剪切):
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.unit.dp
@Composable
fun ClipExample() {
Canvas(modifier = Modifier.size(200.dp)) {
clipPath(Path().apply { addCircle(Offset(100f, 100f), 50f, androidx.compose.ui.graphics.Path.Direction.Clockwise) }, clipOp = ClipOp.Intersect) {
drawRect(color = Color.Red) // 只在圆内绘制
}
}
}
- 说明:Compose 支持
ClipOp.Intersect
和ClipOp.Difference
(类似 clipOut)。对于复杂布尔操作,可嵌套drawWithContent
。
本部分聚焦剪切方法合集,作为 Canvas API 的 Part 2。如果需要 Part 3(矩阵变换详解)、特定方法的更多示例、动画集成,或 Jetpack Compose 的高级剪切用法,请告诉我!