裁剪畫布物件

本程式碼研究室是 Kotlin 進階課程的一部分。只要您按部就班完成程式碼研究室,就能發揮本課程的最大效益。不過,您不一定要這麼做。所有課程程式碼研究室清單均列於進階 Android 版的 Kotlin 程式碼研究室到達網頁中。

引言

關於這個程式碼研究室,裁剪可用於定義圖片、畫布或點陣圖的區域,這些區域會選擇性繪製或未繪製在螢幕上。剪裁的其中一種是減少 overdraw。覆蓋是指在畫面上顯示一個像素以上,以顯示最終圖像。減少過度繪製時,系統會減少繪製像素像素或顯示區域的次數,以盡可能提高繪圖效能。您還可以在剪輯設計和動畫中運用剪輯功能創造出有趣的效果。

舉例來說,當您繪製如下所示的重疊卡片時,與其先由下而下分別擷取整個卡片,通常只希望顯示可見部分。因為「剪輯作業往往需要花費許多成本,而且整體而言,Android 系統的許多繪圖作業都經過最佳化處理。

如果只要繪製資訊卡的顯示部分,請為每張資訊卡指定複製的區域。如下圖所示,為圖片套用裁剪矩形時,系統只會顯示該矩形內的部分。

剪輯區域通常是一個矩形,但可以是任何形狀或形狀的組合 (甚至是文字)。此外,您還可以指定是否要在剪輯區域中納入或排除該地區。例如,你可以建立圓形剪輯區域,然後只顯示圓圈外的事物。

在這個程式碼研究室中,您將嘗試各種剪輯方式。

須知事項

您應該很熟悉:

  • 如何使用 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 資源檔案]
  2. 在「新增資源檔案」對話方塊中,呼叫 dimens 檔案。在「可用的限定詞」中,選取 [最小螢幕寬度],然後按一下 [>>] 按鈕,即可將按鈕新增到「選擇的限定詞」中。在「最小螢幕寬度」方塊中輸入 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:設定列和欄的位置

這個應用程式的形狀分為兩欄和四列,取決於上方設定的維度值。此程式碼的數學不屬於此程式碼研究室的一部分,但您將複製至此步驟中指定的程式碼時,請仔細查看。

  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:實作繪圖差異剪輯範例(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:實作 PaintCircularClippingExample(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(畫布)

接著,加入程式碼以繪製第二列和欄的兩個裁剪矩形的交集。

請注意,視您的螢幕解析度而定,這個區域的外觀會有所不同。以 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:實作繪圖合併裁剪範例(畫布)

接下來,請合併形狀、圓形和矩形,然後繪製任何路徑以定義剪輯區域。

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:實作 PaintTranslatedTextExample(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 (Antialiased:由於可能會經過鋸齒處理,而四捨五入至最接近的像素) 或 BW (黑白:僅將圓角四捨五入至最接近的像素邊界) 來計算。

quickReject() 有多個版本,您也可以在說明文件中找到這些版本。

boolean

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

boolean

quickRejected(RectF rect, Canvas.EdgeType type)

boolean

quickRejected(路徑 path, Canvas.EdgeType type)

在本練習中,您要繪製新的一列、在文字下方以及 clipRect 內部,就像之前一樣。

  • 您先呼叫 quickReject(),其中包含一個與 clipRect 重疊的矩形 inClipRectangle。所以 quickReject() 會傳回 false,clipRectBLACK 填入,且繪製了 inClipRectangle 矩形。

  • 然後變更代碼並撥打 quickReject()notInClipRectanglequickReject() 現在傳回 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() 會傳回 falseinClipRectangle

  1. drawQuickRejectExample()中,更改代碼以quickReject()反應notInClipRectangle.現在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 指定:

▢ 如何對齊以下繪圖形狀

▢ 文字的來源來源

▢ 剪輯區域中的位置

▢ 文字的哪一側應與來源對齊

如要瞭解本課程中其他程式碼研究室的連結,請參閱 Kotlin 的進階 Android 程式碼研究室到達網頁。