キャンバス オブジェクトをクリップする

この Codelab は、Kotlin を使った高度な Android 開発コースの一部です。Codelab を順番に進めると、このコースを最大限に活用できますが、これは必須ではありません。コースの Codelab はすべて、Kotlin を使った高度な Android 開発の Codelab ランディング ページに記載されています。

はじめに

この Codelab では、クリッピングは、画面に選択的に描画されるか、描画されないイメージ、キャンバス、ビットマップの領域を定義する方法です。クリッピングの目的の 1 つは、オーバードローを減らすことです。オーバードローとは、最終的なイメージを表示するために画面上の 1 つのピクセルが複数回描画されることです。オーバードローを減らすと、描画パフォーマンスを最大化するために、ディスプレイのピクセルまたは領域が描画される回数を最小限に抑えることができます。クリッピングを使用して、ユーザー インターフェースのデザインやアニメーションで面白い効果を作成することもできます。

たとえば、次のように重なり合うカードのスタックを描画する場合、各カードを下から上に完全に描画するよりも、通常は表示されている部分のみを描画する方が効率的です。「通常」と書いたのは、クリッピング オペレーションにもコストがかかるためです。全体として、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 と同じレベルで、View を拡張する ClippedView という名前のカスタムビューの新しい Kotlin ファイルとクラスを作成します。次の署名を指定します。残りの作業はすべてこの 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 とします。[利用可能な修飾子] で [最小画面幅] を選択し、[>>] ボタンをクリックして [選択した修飾子] に追加します。[最小画面幅] ボックスに「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] ビューに戻ります。
  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: 図形を設定する

このアプリでは、さまざまな方法でクリップされたシェイプの複数の行と 2 つの列を表示します。

これらには次の共通点があります。

  • コンテナとして機能する大きな長方形(正方形)
  • 大きな長方形を横切る斜線
  • 短いテキスト文字列

このステップでは、リソースからこれらのシェイプのディメンションを設定します。これにより、後で使用するときにディメンションを一度だけ取得すればよくなります。

  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: 行と列の位置を設定する

このアプリのシェイプは、上記のディメンションの値によって決まる 2 列 4 行で表示されます。この計算は Codelab の一部ではありませんが、このステップで指定されたコードをコピーする際に確認してください。

  1. 2 つの列の座標を設定します。
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() では、下のアプリのスクリーンショットに示すように、7 つの異なるクリップされた長方形を描画するメソッドを呼び出します。長方形はすべて同じ方法で描画されます。唯一の違いは、定義されたクリッピング領域と画面上の位置です。

長方形の描画に使用されるアルゴリズムは、次の図と説明のとおりに動作します。要するに、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){
}

アプリは同じ長方形と図形を 7 回描画します。最初はクリッピングなしで、その後はさまざまなクリッピング パスを適用して 6 回描画します。drawClippedRectangle() メソッドは、次の図に示すように、1 つの長方形を描画するコードをファクタリングします。

ステップ 1: drawClippedRectangle() メソッドを作成する

  1. Canvas 型の引数 canvas を受け取る drawClippedRectangle() メソッドを作成します。
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) を実装する

2 つのクリッピング長方形の差を使用して額縁効果を作成する、2 つ目の長方形を描画するコードを追加します。

次の処理を行う以下のコードを使用します。

  1. キャンバスを保存します。
  2. キャンバスの原点を、最初の長方形の右側の最初の行の 2 番目の列の空きスペースに移動します。
  3. 2 つのクリッピング長方形を適用します。DIFFERENCE 演算子は、最初の長方形から 2 番目の長方形を減算します。
  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) を実装する

次に、2 つのクリッピング長方形の交差部分を 2 行 2 列目に描画するコードを追加します。

画面の解像度によって、この領域の見た目は異なります。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) を実装する

クリッピング長方形のインセットを 2 倍にして、長方形の周囲をクリップします。

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 を返し、clipRectBLACK で塗りつぶされ、inClipRectangle の長方形が描画されます。

  • 次に、コードを変更し、notInClipRectangle を使用して quickReject() を呼び出します。quickReject() が true を返し、clipRectWHITE で埋められ、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 の一部が表示されます。これは、2 つの長方形が重なっているため、quickReject()false を返し、inClipRectangle が描画されるためです。

  1. drawQuickRejectExample() で、notInClipRectangle. に対して quickReject() を実行するようにコードを変更します。quickReject()true を返し、クリッピング領域が白で塗りつぶされます。

完成した Codelab のコードをダウンロードします。

$  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 フレームワークが画面に描画する方法の詳細については、グラフィック アーキテクチャ シリーズの記事もご覧ください。

このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。

  • 必要に応じて宿題を与える
  • 宿題の提出方法を生徒に伝える
  • 宿題を採点する

インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。

この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。

以下の質問に回答してください

問題 1

図形が描画されないように効率的に除外するには、どのメソッドを呼び出しますか?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

問題 2

Canvas.save()Canvas.restore() はどのような情報を保存、復元しますか?

▢ 色、線の太さなど

▢ 現在の変換のみ

▢ 現在の変換とクリッピング領域

▢ 現在のクリッピング領域のみ

問題 3

Paint.Align は次の項目を指定します。

▢ 次の図形を配置する方法

▢ テキストの描画元の原点のどちら側か

▢ クリッピング領域内のどの位置に配置するか

▢ テキストのどの側を原点に揃えるか

このコースの他の Codelab へのリンクについては、Kotlin を使った高度な Android 開発の Codelab のランディング ページをご覧ください。