多くのアプリでは、画面上に描画される内容を正確にコントロールする必要があります。それにはボックスや円を画面の適切な場所に配置するといった簡単なことから、グラフィック要素をさまざまなスタイルで配置するといった複雑なことまでが含まれます。
修飾子と DrawScope を使用した基本的な描画
Compose でカスタムのものを描画する基本的な方法は、Modifier.drawWithContent、Modifier.drawBehind、Modifier.drawWithCache などの修飾子を使用する方法です。
たとえば、コンポーザブルの後ろに何かを描画するには、drawBehind 修飾子を使用して描画コマンドの実行を開始します。
Spacer( modifier = Modifier .fillMaxSize() .drawBehind { // this = DrawScope } )
必要なものが描画するコンポーザブルのみであれば、Canvas コンポーザブルを使用できます。Canvas コンポーザブルは、使い勝手のよい Modifier.drawBehind のラッパーです。Canvas は他の Compose UI 要素と同じように、レイアウト内に配置します。Canvas 内で、スタイルと場所を正確にコントロールして要素を描画できます。
すべての描画修飾子は、自身の状態を保持する、スコープ設定された描画環境である DrawScope を自動的に公開します。これにより、グラフィック要素のグループに対してパラメータを設定できます。DrawScope には size(DrawScope の現在のサイズを指定する Size オブジェクト)などの便利なフィールドがあります。
何かを描画するには、DrawScope のさまざまな描画関数のどれかを使用します。たとえば、次のコードでは画面の左上隅に長方形を描画します。
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F drawRect( color = Color.Magenta, size = canvasQuadrantSize ) }

さまざまな描画修飾子の詳細については、グラフィック修飾子のドキュメントをご覧ください。
座標系
画面に何かを描画するには、アイテムのオフセット(x、y)とサイズを把握しておく必要があります。DrawScope の多くの描画メソッドでは、位置とサイズがデフォルトのパラメータ値で示されます。デフォルトのパラメータ値では通常、キャンバスの [0, 0] ポイントにアイテムが配置され、描画領域内にデフォルトの size で表示されます。上記の例では、長方形が左上に配置されています。アイテムのサイズと位置を調整するには、Compose の座標系を理解しておく必要があります。
座標系([0,0])の原点は、描画領域の左上端ピクセルになります。x は右に行くほど大きくなり、y は下に行くほど大きくなります。
![左上が [0, 0]、右下が [width, height] になっている座標系を示したグリッド](https://developer.android.com/static/develop/ui/compose/images/graphics/introduction/compose_coordinate_system_drawing.png?hl=ja)
たとえば、キャンバス領域の右上隅から左下隅に斜線を描画するには、DrawScope.drawLine() 関数を使用して、それぞれ対応する x 位置と y 位置で開始位置と終了位置のオフセットを指定します。
Canvas(modifier = Modifier.fillMaxSize()) { val canvasWidth = size.width val canvasHeight = size.height drawLine( start = Offset(x = canvasWidth, y = 0f), end = Offset(x = 0f, y = canvasHeight), color = Color.Blue ) }
基本的な変形
DrawScope には変形の機能があり、描画コマンドを実行する位置と方法を変更できます。
拡縮
描画オペレーションのサイズを要素に基づき拡大するには、DrawScope.scale() を使用します。scale() のようなオペレーションは、対応するラムダ内のすべての描画オペレーションに適用されます。たとえば、次のコードは scaleX を 10 倍、scaleY を 15 倍に拡大します。
Canvas(modifier = Modifier.fillMaxSize()) { scale(scaleX = 10f, scaleY = 15f) { drawCircle(Color.Blue, radius = 20.dp.toPx()) } }

移動
DrawScope.translate() を使用すると、描画オペレーションを上、下、左、右に移動できます。たとえば次のコードは、描画結果を右に 100 ピクセル、上に 300 ピクセル移動します。
Canvas(modifier = Modifier.fillMaxSize()) { translate(left = 100f, top = -300f) { drawCircle(Color.Blue, radius = 200.dp.toPx()) } }

回転
DrawScope.rotate() を使用して、描画オペレーションをピボット ポイントを中心として回転させます。たとえば次のコードは、長方形を 45 度回転させます。
Canvas(modifier = Modifier.fillMaxSize()) { rotate(degrees = 45F) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }

rotate() を使用して現在の描画スコープに回転を適用し、長方形を 45 度回転させたものインセット
関数 DrawScope.inset() を使用して現在の DrawScope のデフォルト パラメータを調整し、描画境界の変更と描画結果の移動ができます。
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F inset(horizontal = 50f, vertical = 30f) { drawRect(color = Color.Green, size = canvasQuadrantSize) } }
このコードによって、描画コマンドに効果的にパディングを追加できます。

複数の変形
複数の変形を描画結果に適用するには、DrawScope.withTransform() 関数を使用します。この関数は、すべての変更を組み合わせて単一の変形を作成し、適用します。withTransform() を使用すると、ネストされた変形を Compose が一つずつ計算して保存する必要がなく、すべての変形が 1 回のオペレーションで実行されるため、個々の変形に対してネスト呼び出しを行うよりも効率的です。
たとえば次のコードは、移動と回転の両方を長方形に適用します。
Canvas(modifier = Modifier.fillMaxSize()) { withTransform({ translate(left = size.width / 5F) rotate(degrees = 45F) }) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }

withTransform を使用して、回転と移動の両方を適用し、長方形を回転させて左に移動したもの一般的な描画オペレーション
テキストを描画する
Compose でテキストを描画するには、通常は Text コンポーザブルを使用します。ただし、DrawScope を使用している場合や、テキストを手動でカスタマイズして描画する場合は、DrawScope.drawText() メソッドを使用します。
テキストを描画するには、rememberTextMeasurer を使用して TextMeasurer を作成し、Measurer で drawText を呼び出します。
val textMeasurer = rememberTextMeasurer() Canvas(modifier = Modifier.fillMaxSize()) { drawText(textMeasurer, "Hello") }

テキストを測定する
テキストの描画は、他の描画コマンドとは若干仕組みが異なります。通常は、描画コマンドでサイズ(幅と高さ)を指定して、図形や画像を描画します。テキストの場合は、レンダリングされるテキストのサイズをコントロールするパラメータがいくつかあります(フォントサイズ、フォント、合字、文字間隔など)。
Compose では TextMeasurer を使用して、上記の要素に応じて、測定したテキストサイズにアクセスできます。テキストの後ろに背景を描画する場合は、次のように測定した情報を使用して、テキストが占める領域のサイズを取得します。
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()), style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
このコード スニペットにより、テキストの背景がピンク色になります。

制約、フォントサイズ、測定したサイズに影響するプロパティを調整すると、新しいサイズが報告されます。width と height の両方を固定サイズに設定すると、テキストは TextOverflow の設定に従います。たとえば次のコードは、コンポーザブル領域の高さ 1/3、幅 1/3 でテキストをレンダリングし、TextOverflow を TextOverflow.Ellipsis に設定します。
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixed( width = (size.width / 3f).toInt(), height = (size.height / 3f).toInt() ), overflow = TextOverflow.Ellipsis, style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
テキストは制約に沿って描画され、末尾に省略記号が表示されるようになります。

TextOverflow.Ellipsis画像を描画する
DrawScope を使用して ImageBitmap を描画するには、ImageBitmap.imageResource() を使用して画像を読み込み、drawImage を呼び出します。
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog) Canvas(modifier = Modifier.fillMaxSize(), onDraw = { drawImage(dogImage) })

ImageBitmap の描画基本的な図形を描画する
DrawScope には、さまざまな図形描画関数があります。図形を描画するには、定義済みの描画関数(drawCircle など)を使用します。
val purpleColor = Color(0xFFBA68C8) Canvas( modifier = Modifier .fillMaxSize() .padding(16.dp), onDraw = { drawCircle(purpleColor) } )
API |
出力 |
|
|
|
|
|
|
|
|
|
|
|
|
|
パスを描画する
パスは一連の数学的な指示で、実行すると描画になります。DrawScope では、DrawScope.drawPath() メソッドを使用してパスを描画できます。
たとえば、三角形を描画するとします。描画領域のサイズを使用して、lineTo() や moveTo() などの関数でパスを生成します。次に、この新しく作成したパスを使用して drawPath() を呼び出して、三角形を描画します。
Spacer( modifier = Modifier .drawWithCache { val path = Path() path.moveTo(0f, 0f) path.lineTo(size.width / 2f, size.height / 2f) path.lineTo(size.width, 0f) path.close() onDrawBehind { drawPath(path, Color.Magenta, style = Stroke(width = 10f)) } } .fillMaxSize() )

Path を作成して描画Canvas オブジェクトへのアクセス
DrawScope では、Canvas オブジェクトに直接アクセスできません。DrawScope.drawIntoCanvas() を使用すると、関数を呼び出せる Canvas オブジェクト自体にアクセスできます。
たとえば、キャンバスに描画するカスタム Drawable がある場合は、キャンバスにアクセスし、Drawable#draw() を呼び出して Canvas オブジェクトで渡します。
val drawable = ShapeDrawable(OvalShape()) Spacer( modifier = Modifier .drawWithContent { drawIntoCanvas { canvas -> drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt()) drawable.draw(canvas.nativeCanvas) } } .fillMaxSize() )

Drawable を描画詳細
Compose での描画の詳細については、次のリソースをご覧ください。
- グラフィック修飾子 - さまざまなタイプの描画修飾子について詳細を説明しています。
- ブラシ - コンテンツのペイントをカスタマイズする方法を説明しています。
- Custom layouts and graphics in Compose - Android Dev Summit 2022 - レイアウトとグラフィックを使用して Compose でカスタム UI を作成する方法を説明しています。
- JetLagged サンプル - カスタムグラフを描画する方法を示した Compose のサンプルです。
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- グラフィック修飾子
- Compose のグラフィック
- Jetpack Compose でのアライメント ライン