裁剪 Canvas 物件

這個程式碼研究室是「Android Kotlin 進階功能」課程的一部分。如果您按部就班完成每一堂程式碼研究室課程,就能充分體驗到本課程的價值,但這不是強制要求。如要查看所有課程程式碼研究室,請前往 Android Kotlin 進階功能程式碼研究室登陸頁面

簡介

在本程式碼研究室中,裁剪是指定義圖片、畫布或點陣圖的區域,並選擇性地繪製或不繪製到畫面上。剪裁的用途之一是減少過度繪製。過度繪製是指為了顯示最終圖像,螢幕上的像素繪製超過一次。減少過度繪製可盡量減少繪製像素或顯示區域的次數,進而提升繪製效能。您也可以使用剪裁,在使用者介面設計和動畫中建立有趣的效果。

舉例來說,當您繪製一疊重疊的卡片 (如下所示) 時,與其從底部開始完整繪製每張卡片,通常只繪製可見部分會更有效率。之所以說「通常」,是因為裁剪作業也會產生費用,而且整體而言,Android 系統會進行許多繪圖最佳化作業。

如要只繪製資訊卡的可見部分,請為每張資訊卡指定裁剪區域。舉例來說,在下圖中,如果對圖片套用裁剪矩形,系統只會顯示矩形內的圖片部分。

裁剪區域通常是矩形,但可以是任何形狀或形狀組合,甚至是文字。您也可以指定要納入或排除剪裁區域內的區域。舉例來說,您可以建立圓形剪裁區域,只顯示圓圈外的內容。

在本程式碼研究室中,您將嘗試各種剪裁方式。

必備知識

您必須已經熟悉下列項目:

  • 瞭解如何使用 Android Studio 建立及執行含有 Activity 的應用程式。
  • 如何建立及繪製 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」中選取「Smallest Screen Width」,然後按一下「>>」按鈕,將其新增至「Chosen qualifiers」。在「Smallest screen width」(最小螢幕寬度) 方塊中輸入 480,然後按一下「OK」(確定)

  1. 檔案應會顯示在 values 資料夾中,如下所示。

  1. 如果找不到檔案,請切換至應用程式的「Project Files」(專案檔案) 檢視畫面。新檔案的完整路徑如下所示: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:建立並初始化 Paint 和 Path 物件

  1. 切換回專案的「Android」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. ClippedViewpath 下方,為整組形狀周圍的裁剪矩形新增維度變數。
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:設定列和欄的位置

這個應用程式的形狀會顯示在兩欄四列中,取決於上方設定的維度值。這項計算的數學概念不屬於本程式碼研究室的範圍,但請在複製這個步驟提供的程式碼時查看。

  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()

活動內容會維護繪圖狀態堆疊。繪圖狀態包含目前的轉換矩陣和目前的剪裁區域。您可以儲存目前狀態、執行會變更繪圖狀態的動作 (例如平移或旋轉畫布),然後還原儲存的繪圖狀態。(注意:這類似於 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:實作 drawCombinedClippingExample(canvas)

接著,合併圓形和矩形,並繪製任意路徑來定義剪裁區域。

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(canvas)

接著,新增圓角矩形,這是常用的剪裁形狀。

  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:實作 drawTranslatedTextExample(canvas)

繪製文字與其他形狀沒有太大差異,而且你可以對文字套用變形效果。舉例來說,你可以翻譯畫布並繪製文字,藉此翻譯文字。

  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(canvas)

你也可以傾斜文字。也就是以各種方式扭曲。

  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

quickReject(float left, float top, float right, float bottom, Canvas.EdgeType type)

boolean

quickReject(RectF rect, Canvas.EdgeType type)

boolean

quickReject(Path path, Canvas.EdgeType type)

在本練習中,您要在文字下方和 clipRect 內繪製新列,做法與先前相同。

  • 您首先會使用與 clipRect 重疊的矩形 inClipRectangle 呼叫 quickReject()。因此 quickReject() 會傳回 false,clipRect 會填入 BLACK,並繪製 inClipRectangle 矩形。

  • 然後變更程式碼並呼叫 quickReject(),並使用 notInClipRectanglequickReject() 現在會傳回 true,且 clipRect 會填入 WHITE,而 notInClipRectangle 不會繪製。

如果繪圖很複雜,這項功能可快速判斷哪些形狀完全位於裁剪區域外,以及哪些形狀位於裁剪區域內 (部分或全部),因此您可能需要進行額外計算和繪圖。

步驟:實驗 quickReject()

  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,且裁剪區域會填滿白色。

下載完成的程式碼研究室程式碼。

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


您也可以將存放區下載為 ZIP 檔案、將其解壓縮,並在 Android Studio 中開啟。

下載 ZIP 檔

  • 活動的 Context 會維護狀態,保留 Canvas 的轉換和裁剪區域。
  • 使用 canvas.save()canvas.restore() 繪圖,並返回畫布的原始狀態。
  • 如要在畫布上繪製多個形狀,您可以計算形狀的位置,也可以移動 (平移) 繪圖表面的原點。後者可讓您更輕鬆地為重複的繪圖序列建立公用程式方法。
  • 剪裁區域可以是任何形狀、形狀組合或路徑。
  • 您可以新增、減去和交集裁剪區域,取得所需的確切區域。
  • 您可以轉換畫布,對文字套用轉換效果。
  • quickReject() Canvas 方法可讓您檢查指定矩形或路徑是否完全位於目前可見區域之外。

Udacity 課程:

Android 開發人員說明文件:

如要深入瞭解 Android 架構如何繪製到畫面上,請參閱「圖像架構」系列文章。

本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:

  • 視需要指派作業。
  • 告知學員如何繳交作業。
  • 為作業評分。

講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。

如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。

回答問題

第 1 題

您會呼叫哪個方法,有效排除要繪製的形狀?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

第 2 題

Canvas.save()Canvas.restore() 會儲存及還原哪些資訊?

▢ 顏色、線條寬度等。

▢ 僅限目前的轉換

▢ 目前的轉換和裁剪區域

▢ 僅限目前的剪輯區域

第 3 題

Paint.Align 指定:

▢ 如何對齊下列繪圖形狀

▢ 文字的繪製起點位於來源的哪一側

▢ 裁剪區域中的對齊位置

▢ 要將文字的哪一側對齊原點

如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 進階功能程式碼研究室登陸頁面。