裁剪画布对象

此 Codelab 是“使用 Kotlin 进行高级 Android 开发”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘课程的价值,但并不强制要求这样做。“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页列出了所有课程 Codelab。

简介

在本 Codelab 中,剪辑用于定义选择性地或不在屏幕上绘制的图像、画布或位图的区域。裁剪的一个目的是减少过度绘制。过度绘制是指在屏幕上多次绘制某个像素以显示最终图片。当您减少过度绘制时,可以最大限度地减少绘制显示屏或像素的区域的次数,以便最大限度地提升绘制性能。您还可以使用剪辑在界面设计和动画中制作有趣的效果。

例如,当您绘制一堆重叠的卡片(如下所示)时,相比从下至下完整地绘制每张卡片,仅绘制可见部分通常更高效。“通常”,由于裁剪操作也会产生费用,因此总体而言,Android 系统需要完成大量的绘制优化工作。

如需仅绘制卡片的可见部分,请为每个卡片指定裁剪区域。例如,在下图中,当图片被剪裁时,系统仅会展示该矩形内的部分。

裁剪区域通常是矩形,但也可以是任何形状或形状的组合,甚至是文本。您还可以指定是包含还是排除剪辑区域内的区域。例如,您可以创建一个圆形剪裁区域,并仅显示圆形以外的内容。

在此 Codelab 中,您将尝试各种剪辑方法。

您应当已掌握的内容

您应熟悉以下内容/操作:

  • 如何使用 Activity 创建应用并使用 Android Studio 运行该应用。
  • 如何在 Canvas 上创建和绘制。
  • 如何创建自定义 View,以及替换 onDraw()onSizeChanged()

学习内容

  • 如何裁剪对象以在 Canvas 上绘制。
  • 如何保存和恢复画布的绘图状态。
  • 如何将转换应用于画布和文本。

您将执行的操作

  • 创建一个应用,以在屏幕上绘制裁剪形状,演示不同的裁剪方式以及这些形状的可见性结果。
  • 您还将绘制一些翻译后歪斜的文字。

ClippingExample 应用演示了如何使用和组合形状来指定画布中的哪些部分显示在视图中。您的最终应用将如以下屏幕截图所示。

您将从头开始构建此应用,因此必须设置一个项目,定义维度和字符串,并声明一些变量。

第 1 步:创建 ClippingExample 项目

  1. 使用 Empty Activity 模板创建名为 ClippingExample 的 Kotlin 项目。使用 com.example.android 作为软件包名称前缀。
  2. 打开 MainActivity.kt
  3. onCreate() 方法中,替换默认内容视图并将内容视图设置为 ClippedView 的新实例。这是您自定义的剪辑示例,方便您接下来创建的剪辑示例。
setContentView(ClippedView(this))
  1. 在与 MainActivity.kt 相同的级别,为名为 ClippedView 的自定义视图创建新的 Kotlin 文件和类,以扩展 View。为其提供如下所示的签名。您的剩余工作将全部存储在此 ClippedView 中。@JvmOverloads 注解可指示 Kotlin 编译器为此函数生成可替换默认参数值的重载。
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

第 2 步:添加维度和字符串资源

  1. res/values/dimens.xml 的新资源文件中,指定您要对裁剪视图使用的尺寸。这些默认尺寸经过硬编码,其尺寸适合非常小的屏幕。
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">90dp</dimen>
   <dimen name="clipRectBottom">90dp</dimen>
   <dimen name="clipRectTop">0dp</dimen>
   <dimen name="clipRectLeft">0dp</dimen>

   <dimen name="rectInset">8dp</dimen>
   <dimen name="smallRectOffset">40dp</dimen>

   <dimen name="circleRadius">30dp</dimen>
   <dimen name="textOffset">20dp</dimen>
   <dimen name="strokeWidth">4dp</dimen>

   <dimen name="textSize">18sp</dimen>
</resources>

为了让应用在较大的屏幕上正常显示(以及更轻松地查看详细信息),您可以创建一个 dimens 值更高的文件,仅适用于大屏幕。

  1. 在 Android Studio 中,右键点击 values 文件夹,然后选择 New > Values resource file
  2. New Resource File 对话框中,调用文件 dimens。在 Available qualifiers 中,选择 最小屏幕宽度,然后点击 >> 按钮以将其添加到选择限定符。在最小屏幕宽度框中输入 480,然后点击确定

  1. 该文件应显示在值文件夹中,如下所示。

  1. 如果您看不到该文件,请切换到应用的项目文件视图。新文件的完整路径如下所示:ClippingExample/app/src/main/res/values-sw480dp/dimens.xml

  1. values-sw480dp/dimens.xml 文件的默认内容替换为以下尺寸。
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">120dp</dimen>
   <dimen name="clipRectBottom">120dp</dimen>

   <dimen name="rectInset">10dp</dimen>
   <dimen name="smallRectOffset">50dp</dimen>

   <dimen name="circleRadius">40dp</dimen>
   <dimen name="textOffset">25dp</dimen>
   <dimen name="strokeWidth">6dp</dimen>
</resources>
  1. strings.xml 中,添加以下字符串。它们用于在画布上显示文本。
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

第 3 步:创建并初始化绘制和路径对象

  1. 切换回项目的 Android 视图。
  2. ClippedView 中,定义用于绘制的 Paint 变量。启用抗锯齿功能,然后使用维度中定义的描边宽度和文字大小,如下所示。
private val paint = Paint().apply {
   // Smooth out edges of what is drawn without affecting shape.
   isAntiAlias = true
   strokeWidth = resources.getDimension(R.dimen.strokeWidth)
   textSize = resources.getDimension(R.dimen.textSize)
}
  1. ClippedView 中,创建并初始化 Path,以在本地存储绘制路径。导入 android.graphics.Path
private val path = Path()

第 4 步:设置形状

在此应用中,您将展示几行和两列以不同方式裁剪的形状。

它们都有一个共同的特点:

  • 一个充当容器的大型矩形(方形)
  • 横跨大矩形的对角线
  • 一个圆圈
  • 一小段文本

在此步骤中,您将从资源中为这些形状设置尺寸,以便在以后使用它们时只需获取一次尺寸。

  1. ClippedView 中的 path 下方,为整组形状周围的裁剪矩形添加尺寸变量。
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
  1. 为矩形的边衬区和小矩形的偏移量添加变量。
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. 添加圆半径的变量。这是矩形内绘制的圆的半径。
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. 为矩形内绘制的文本添加偏移值和文本大小。
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

第 4 步:设置行和列的位置

此应用的形状会以两列四行显示,具体取决于上面设置的维度值。此 Codelab 并未介绍相关数学知识,不过在您将其复制到此步骤中提供的代码时,不妨了解一下。

  1. 设置两列的坐标。
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. 为每行添加坐标,包括转换后的文本的最后一行。
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
  1. 运行您的应用。此时,应用应该会以空白屏幕显示,名称下方会显示空白。

onDraw() 中,您可以调用方法来绘制七个不同的裁剪矩形,如下面的应用屏幕截图所示。所有矩形都以相同的方式绘制;唯一的区别是它们定义的裁剪区域和在屏幕上的位置。

用于绘制矩形的算法如下图所示,相关说明见下文。总之,您可以通过移动 Canvas 的原点绘制一系列矩形。从概念上来讲,这包括以下步骤:

(1) 首先,将 Canvas 转换为您希望绘制矩形的位置。也就是说,您移动 Canvas 原点(即其坐标系),而不是计算下一个矩形和所有其他形状的绘制位置。

(2) 然后,在画布的新位置绘制矩形。也就是说,在平移的坐标系中的相同位置绘制形状。这要简单得多,而且效率更高一些。

(3) 最后,将 Canvas 恢复为原始 Origin

以下是您实现它的算法:

  1. onDraw() 中,调用函数以灰色背景填充 Canvas 并绘制原始形状。
  2. 为每个裁剪矩形以及要绘制的文本调用一个函数。

对于每个矩形或文本:

  1. 保存 Canvas 的当前状态,以便您重置到该初始状态。
  2. 将画布的 Origin 转换为您要绘制的位置。
  3. 应用裁剪形状和路径。
  4. 绘制矩形或文本。
  5. 恢复 Canvas 的状态。

步骤:替换 onDraw()

  1. 替换 onDraw(),如以下代码所示。您需要为绘制的每个形状调用一个函数,稍后将实现该函数。
 override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawBackAndUnclippedRectangle(canvas)
        drawDifferenceClippingExample(canvas)
        drawCircularClippingExample(canvas)
        drawIntersectionClippingExample(canvas)
        drawCombinedClippingExample(canvas)
        drawRoundedRectangleClippingExample(canvas)
        drawOutsideClippingExample(canvas)
        drawSkewedTextExample(canvas)
        drawTranslatedTextExample(canvas)
        // drawQuickRejectExample(canvas)
    }
  1. 为每个绘图函数创建存根,以便继续编译代码。您可以复制以下代码。
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
}
private fun drawDifferenceClippingExample(canvas: Canvas){
}
private fun drawCircularClippingExample(canvas: Canvas){
}
private fun drawIntersectionClippingExample(canvas: Canvas){
}
private fun drawCombinedClippingExample(canvas: Canvas){
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas){
}
private fun drawOutsideClippingExample(canvas: Canvas){
}
private fun drawTranslatedTextExample(canvas: Canvas){
}
private fun drawSkewedTextExample(canvas: Canvas){
}
private fun drawQuickRejectExample(canvas: Canvas){
}

该应用绘制同一矩形和形状七次,首先不进行裁剪,然后六次应用各种裁剪路径。drawClippedRectangle() 方法分解用于绘制一个矩形的代码,如下所示。

第 1 步:创建 drawClippedRectangle() 方法

  1. 创建一个 drawClippedRectangle() 方法,该方法接受 Canvas 类型的参数 canvas
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. drawClippedRectangle() 方法内,为整个形状设置裁剪矩形的边界。应用限制矩形,使其仅绘制正方形。
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

Canvas.clipRect(...) 方法可减少未来绘制操作可写入的屏幕区域。它会将裁剪边界设置为当前裁剪矩形与传入 clipRect() 的矩形的空间交叉。clipRect() 方法的许多变体都可以接受不同的区域形式,并允许对裁剪矩形执行不同的操作。

  1. 使用白色填充 canvas。会!整个画布,因为您不是绘制矩形,所以你们在进行裁剪!使用裁剪矩形时,系统只会填充剪裁矩形所定义的区域,从而创建白色矩形。表面的其余部分仍呈灰色。
canvas.drawColor(Color.WHITE)
  1. 将颜色更改为红色,然后在裁剪矩形内绘制一条对角线。
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. 将颜色设为绿色,然后在裁剪矩形内画一个圆圈。
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. 将颜色设为蓝色,然后绘制与裁剪矩形右边缘对齐的文字。使用 canvas.drawText() 绘制文本。
paint.color = Color.BLUE
// Align the RIGHT side of the text with the origin.
paint.textSize = textSize
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(
   context.getString(R.string.clipping),
   clipRectRight,textOffset,paint
)

第 2 步:实现 drawBackAndUnclippedRectangle() 方法

  1. 如需查看 drawClippedRectangle() 方法的实际应用,可通过实现 drawBackAndUnclippedRectangle() 方法绘制第一个未裁剪的矩形,如下所示。保存 canvas,将其转换为第一行和列位置,通过调用 drawClippedRectangle() 绘制绘制,然后将 canvas 恢复到之前的状态。
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. 运行应用。您应该会看到第一个白色矩形及其圆形、红色线条和灰色背景文本。

在以下剪切示例方法中,您将应用各种剪裁区域组合以获得图形效果,并学习如何组合剪裁区域以创建所需的任何形状。

其中每个方法都遵循相同的模式。

  1. 保存画布的当前状态:canvas.save()

activity 上下文会保持一个绘图状态堆栈。绘制状态由当前转换矩阵和当前剪裁区域组成。您可以保存当前状态,执行更改绘图状态的操作(例如,平移或旋转画布),然后恢复已保存的绘图状态。(注意:这类似于 git 中的“stash”命令)。

如果您的绘图包含转换,那么通过逆转来转换和撤消转换就容易出错。例如,如果翻译、拉伸,然后旋转,会很快变得复杂。正确的做法是保存画布状态,应用转换,绘制,然后恢复之前的状态。

例如,您可以定义一个裁剪区域,然后保存该状态。然后翻译画布,添加剪裁区域和旋转。进行一些绘制后,您可以恢复原始剪裁状态,然后可以继续进行不同的转换和倾斜转换,如图所示。

  1. 将画布的原点转换为行/列坐标:canvas.translate()

移动画布原点并在新坐标系中绘制相同对象要比移动所有要绘制的元素要简单得多。(提示:您也可以使用相同的方法来旋转元素。)

  1. 将转换应用于 path(如果有)。
  2. 应用剪辑:canvas.clipPath(path)
  3. 绘制形状:drawClippedRectangle() or drawText()
  4. 恢复之前的画布状态:canvas.restore()

第 1 步:实现 drawDifferenceClippingExample(canvas)

添加代码以绘制第二个矩形,该矩形根据两个裁剪矩形之间的差异来形成相框效果。

使用以下代码,执行以下操作:

  1. 保存画布。
  2. 将画布的原点转换为空白区域到第一个矩形的第一行、第二列。
  3. 应用两个裁剪矩形。DIFFERENCE 运算符从第一个矩形减去第二个矩形。
  1. 调用 drawClippedRectangle() 方法以绘制修改后的画布。
  2. 恢复画布状态。
private fun drawDifferenceClippingExample(canvas: Canvas) {
   canvas.save()
   // Move the origin to the right for the next rectangle.
   canvas.translate(columnTwo,rowOne)
   // Use the subtraction of two clipping rectangles to create a frame.
   canvas.clipRect(
       2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .DIFFERENCE) was deprecated in API level 26. The recommended
   // alternative method is clipOutRect(float, float, float, float),
   // which is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
       canvas.clipRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset,
            Region.Op.DIFFERENCE
       )
   } else {
       canvas.clipOutRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. 运行应用,它应如下所示。

第 2 步:实现 drawCircularClippingExample(canvas)

接下来,添加代码以绘制一个使用从圆形路径创建的圆形裁剪区域的矩形,这基本上会移除(而不是绘制)圆形,因此会改为显示灰色背景。

private fun drawCircularClippingExample(canvas: Canvas) {

   canvas.save()
   canvas.translate(columnOne, rowTwo)
   // Clears any lines and curves from the path but unlike reset(),
   // keeps the internal data structure for faster reuse.
   path.rewind()
   path.addCircle(
       circleRadius,clipRectBottom - circleRadius,
       circleRadius,Path.Direction.CCW
   )
   // The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
   // API level 26. The recommended alternative method is
   // clipOutPath(Path), which is currently available in
   // API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipPath(path, Region.Op.DIFFERENCE)
   } else {
       canvas.clipOutPath(path)
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

第 3 步:实现 drawIntersectionClippingExample(canvas)

接下来,添加代码以绘制第二行和第二列中两个裁剪矩形的交集。

请注意,此区域的外观会因屏幕分辨率而异。尝试使用 smallRectOffset 维度更改可见区域的尺寸。较小的 smallRectOffset 会使屏幕上的区域变大。

private fun drawIntersectionClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowTwo)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight - smallRectOffset,
       clipRectBottom - smallRectOffset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .INTERSECT) was deprecated in API level 26. The recommended
   // alternative method is clipRect(float, float, float, float), which
   // is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom,
           Region.Op.INTERSECT
       )
   } else {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

第 4 步:实现 drawMergedClippingExample(画布)

接下来,结合使用形状、圆形和矩形,并绘制任意路径来定义剪裁区域。

private fun drawCombinedClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne, rowThree)
   path.rewind()
   path.addCircle(
       clipRectLeft + rectInset + circleRadius,
       clipRectTop + circleRadius + rectInset,
       circleRadius,Path.Direction.CCW
   )
   path.addRect(
       clipRectRight / 2 - circleRadius,
       clipRectTop + circleRadius + rectInset,
       clipRectRight / 2 + circleRadius,
       clipRectBottom - rectInset,Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

第 5 步:实现 drawRoundedRectangleClippingExample(画布)

接下来,添加一个圆角矩形,这是常用的裁剪形状。

  1. 在顶层,创建并初始化一个矩形变量。RectF 是一个用于存储矩形坐标的浮点数的类。
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. 实现 drawRoundedRectangleClippingExample() 函数。addRoundRect() 函数接受一个矩形、角半径的 x 值和 y 值,以及用于环绕圆角矩形的方向。Path.Direction 指定封闭形状(如矩形、椭圆形)添加到路径时所采用的方向。CCW 表示逆时针。
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowThree)
   path.rewind()
   path.addRoundRect(
       rectF,clipRectRight / 4,
       clipRectRight / 4, Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

第 6 步:实现 drawOutsideClippingExample(canvas)

将裁剪矩形的边衬区加倍,使矩形的外部裁剪。

private fun drawOutsideClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne,rowFour)
   canvas.clipRect(2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset)
   drawClippedRectangle(canvas)
   canvas.restore()
}

第 7 步:实现 draw TranslatedTextExample(画布)

绘制文本与任何其他形状没有什么不同,您可以对文本应用转换。例如,您可以通过翻译画布并绘制文本来翻译文本。

  1. 实现下面的函数。
private fun drawTranslatedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.GREEN
   // Align the RIGHT side of the text with the origin.
   paint.textAlign = Paint.Align.LEFT
   // Apply transformation to canvas.
   canvas.translate(columnTwo,textRow)
   // Draw text.
   canvas.drawText(context.getString(R.string.translated),
       clipRectLeft,clipRectTop,paint)
   canvas.restore()
}
  1. 运行您的应用以查看译文。

第 8 步:实现 drawSkewedTextExample(画布)

您还可以倾斜文本。也就是说,可通过各种方式对其进行扭曲。

  1. ClippedView 中创建下面的函数。
private fun drawSkewedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.YELLOW
   paint.textAlign = Paint.Align.RIGHT
   // Position text.
   canvas.translate(columnTwo, textRow)
   // Apply skew transformation.
   canvas.skew(0.2f, 0.3f)
   canvas.drawText(context.getString(R.string.skewed),
       clipRectLeft, clipRectTop, paint)
   canvas.restore()
}
  1. 运行您的应用,查看译文之前绘制的倾斜文本。

借助 quickReject() Canvas 方法,您可以在应用所有转换后,检查指定的矩形或路径是否完全位于当前可见区域之外。

当您构造更复杂的绘制并且需要尽快完成时,quickReject() 方法非常有用。借助 quickReject(),您可以高效地决定根本无需绘制哪些对象,而无需编写自己的相交逻辑。

  • 如果矩形或路径在屏幕上完全不可见,quickReject() 方法会返回 true。对于部分雷同,您仍需自行检查。
  • EdgeType 可以是 AA抗锯齿:通过四舍五入来对边缘进行圆角处理,因为它们可能会被去混淆)或 BW(仅通过四舍五入至最接近的像素边界来处理边缘),以便四舍五入至最接近的像素。

quickReject() 有多个版本,您还可以在文档中找到它们。

boolean

quickRejected(float left, float top, float right, float bottom, 画布边缘类型 type)

boolean

quickRejected(RectF rect, Canvas.EdgeType type)

boolean

quickRejected(路径 path, Canvas.EdgeType type)

在本练习中,您将像之前一样,在新文本、文本下方和 clipRect 内绘制。

  • 您首先使用与 clipRect 重叠的矩形 inClipRectangle 调用 quickReject()。因此,quickReject() 返回 false,clipRect 填充了 BLACK,并绘制了 inClipRectangle 矩形。

  • 然后,更改代码并使用 notInClipRectangle 调用 quickReject()quickReject() 现在返回 true,clipRect 填充了 WHITE,未绘制 notInClipRectangle

如果您的绘图比较复杂,就可以快速了解哪些形状完全不在剪切区域范围内,以及是否需要进行额外的计算和绘制,因为它们部分或完全位于剪切区域内。

步骤:使用 quickRejected() 进行实验

  1. 在顶层为其他行的 y 坐标创建变量。
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. 将以下 drawQuickRejectExample() 函数添加到 ClippedView。请阅读代码,其中包含使用 quickReject() 需知的所有事项。
private fun drawQuickRejectExample(canvas: Canvas) {
   val inClipRectangle = RectF(clipRectRight / 2,
       clipRectBottom / 2,
       clipRectRight * 2,
       clipRectBottom * 2)

   val notInClipRectangle = RectF(RectF(clipRectRight+1,
       clipRectBottom+1,
       clipRectRight * 2,
       clipRectBottom * 2))

   canvas.save()
   canvas.translate(columnOne, rejectRow)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
   )
   if (canvas.quickReject(
           inClipRectangle, Canvas.EdgeType.AA)) {
       canvas.drawColor(Color.WHITE)
   }
   else {
       canvas.drawColor(Color.BLACK)
       canvas.drawRect(inClipRectangle, paint
       )
   }
       canvas.restore()
}
  1. onDraw() 中,取消对 drawQuickRejectExample() 的调用的注释。
  2. 运行应用,您将看到一个黑色矩形(填满的裁剪区域)以及 inClipRectangle 的一部分,这是因为这两个矩形重叠,因此 quickReject() 会返回 false 并绘制 inClipRectangle

  1. drawQuickRejectExample() 中,将代码更改为针对 notInClipRectangle. 运行 quickReject()。现在,quickReject() 会返回 true 且裁剪区域填充了白色。

下载已完成的 Codelab 的代码。

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping


另一种方法是,以 Zip 文件的形式下载代码库,将其解压缩,然后在 Android Studio 中打开它。

下载 Zip 文件

  • activity 的 Context 会维护一个状态,用于保留 Canvas 的转换和裁剪区域。
  • 使用 canvas.save()canvas.restore() 绘制并返回画布的原始状态。
  • 如需在画布上绘制多个形状,您可以计算它们的位置,也可以移动(转换)绘图表面的原点。后者可让您更轻松地为重复绘制序列创建实用程序方法。
  • 裁剪区域可以是任何形状、形状或路径的组合。
  • 您可以添加、减去和相交裁剪区域,以获得您所需的区域。
  • 您可以通过转换画布来对文本进行转换。
  • 通过 quickReject() Canvas 方法,您可以检查指定的矩形或路径是否完全位于当前可见的区域之外。

Udacity 课程:

Android 开发者文档:

另请参阅图形架构系列文章,深入了解 Android 框架如何绘制到屏幕上。

此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:

  • 根据需要布置作业。
  • 告知学生如何提交家庭作业。
  • 给家庭作业评分。

讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。

如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。

回答以下问题

问题 1

为了有效排除形状,您会调用什么方法?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

问题 2

Canvas.save()Canvas.restore()会保存和恢复哪些信息?

▢ 颜色、线宽等

▢ 仅当前的转换

▢ 当前的转换和剪裁区域

▢ 仅当前剪裁区域

问题 3

Paint.Align 指定以下内容:

▢ 如何对齐以下绘图形状

文字是从源站的哪一侧出发的

▢ 在剪辑区域中对齐的

▢ 与原图的对齐方式

如需查看本课程中其他 Codelab 的链接,请参阅“使用 Kotlin 进行高级 Android 开发”的 Codelab 着陆页。