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 方法相当于 clipRectDIFFERENCE 操作,但更高效(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.clipdrawWithContent 实现,更具声明式:

  • 矩形剪切Modifier.clip(RectangleShape) 或自定义形状。
  • 路径剪切:使用 clipToBoundsPathCanvas 内。
  • 示例(圆形剪切):
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.IntersectClipOp.Difference(类似 clipOut)。对于复杂布尔操作,可嵌套 drawWithContent

本部分聚焦剪切方法合集,作为 Canvas API 的 Part 2。如果需要 Part 3(矩阵变换详解)、特定方法的更多示例、动画集成,或 Jetpack Compose 的高级剪切用法,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注