[Android] 9-patchを用いて画像の一部を伸縮させる
背景
メッセージアプリのように吹き出しの中にTextを表示し、Textの領域に応じて吹き出しの大きさを変えるケースはよくあると思います。
このときにpng等の画像を用いると、画像全体が均一に伸縮されます。
例えば、bubble.pngをText領域に応じて拡大表示する場合、以下の問題が発生しています。
- 吹き出しの縁の凸凹が強調される
- tail部分(赤丸部分)のデザインが崩れる
本来の意図としては、吹き出しの縁の部分を除いたText部分のみを拡大したいです。
Androidでこのような描画をする方法に、9-patch画像を用いる方法があります。
bubble.png
bubble.pngをText領域に応じて拡大した画像
9-patch画像の概要
9-patch画像は、拡張子が.9.pngのファイルであり、画像の周囲1ピクセルに情報を埋め込んだものです。
以下のような情報を埋め込んでいます。
- 上辺:水平方向に伸縮可能な範囲
- 左辺:垂直報告に伸縮可能な範囲
- 底辺:水平方向のテキスト表示領域
- 右辺:垂直方向のテキスト表示領域
9-patch画像での情報埋め込み(公式から抜粋)
9-patch画像にしたbubble.9.pngを用いて先ほどのテキストを表示すると、このようにきれいに表示できます。
bubble.9.pngをText領域に応じて拡大した画像
9-patch画像の作成方法
AndroidStudioの機能を用いて、以下の手順で実装できます。
- AndroidStudioでpng画像を右クリックし、「Create 9-patch file」をクリック
- ファイル名を指定して.9.pngファイルを作成
- .9.pngファイルを開き、GUIで伸縮領域を定義して保存
伸縮領域はGUIで簡単に定義できます。
緑の部分が一方向に伸縮できる領域、ピンクの部分が垂直・水平両方に伸縮できる領域です。
伸縮領域の定義
注意点として、.9.pngファイルを保存してビルドをすると以下のエラーが発生します。
そのため、元々の.pngファイルは削除する必要があります。
また、グラデーションがある画像や複雑な形をした画像では綺麗に描画されないため、他の方法を検討してください。
Source Code
9-patch画像はJetpack ComposeのpainterResourceでは表示ができません。
そこで、Modifier.drawBehindを利用してCanvasに表示します。
@Composable
fun DynamicSpeechBubble(
text: String,
@DrawableRes bubbleImageRes: Int,
paddingValues: PaddingValues,
) {
val context = LocalContext.current
Box(
Modifier.draw9Patch(context = context, ninePatchRes = bubbleImageRes)
) {
Text(
text = text,
color = Color.Black,
modifier = Modifier.padding(paddingValues)
)
}
}
fun Modifier.draw9Patch(
context: Context,
@DrawableRes ninePatchRes: Int,
) = this.drawBehind {
drawIntoCanvas {
ContextCompat.getDrawable(context, ninePatchRes)?.let { ninePatch ->
ninePatch.run {
bounds = Rect(0, 0, size.width.toInt(), size.height.toInt())
draw(it.nativeCanvas)
}
}
}
}
参考
Discussion