この記事はレコチョク Advent Calendar 2022の14日目の記事となります。
はじめに
はじめまして、秦基博さんと日向坂46さんに日々癒しと元気をいただいている休井です。
株式会社レコチョクでAndroidの開発をしています。
最近はJetpack Composeを使用してUI作成をすることが多いのですが、何となく難しそうで触れてこなかったCanvas APIについて、実際に使ってみながらご紹介したいと思います。
Canvas APIについて
Androidでは、Jetpack ComposeというUIツールキットが提供されており、宣言的にUIを作成することができます。
CanvasはそのJetpack Composeに含まれるAPIで、画面内の位置(座標)を用いてレイアウトの設定を行うことができるので、かなり自由度高くカスタムレイアウトを作成することができます。
基本的な使い方
線を引く
drawLine()
を使用します。
start
とend
で線の開始と終了を指定して線を引くことができます。
@Composable
fun DrawLine() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val width = size.width
val height = size.height
drawLine(
color = Color.Black,
start = Offset.Zero,
end = Offset(width, height),
strokeWidth = 5F
)
}
}
四角形を描く
drawRect()
を使用します。
topLeft
で描画位置を決め、size
で大きさを指定して四角形を描くことができます。
@Composable
fun DrawRect() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val width = size.width
val height = size.height
drawRect(
color = Color.Black,
topLeft = Offset(width / 3, height / 3),
size = Size(width / 3, height / 3)
)
}
}
角丸の四角形にしたい場合は、drawRoundRect()
を使いcornerRadius
に丸の半径を設定します。
@Composable
fun DrawRoundRect() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val width = size.width
val height = size.height
drawRoundRect(
color = Color.Black,
topLeft = Offset(width / 3, height / 3),
size = Size(width / 3, height / 3),
cornerRadius = CornerRadius(x = 100F, y = 100F)
)
}
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F2980857%2Fd73a67ad-ba70-9d35-7312-c13bed1aefb9.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=e59a124ea1b1e2048705f33f15bfde51)
円を描く
drawCircle()
を使用します。
center
で描画位置を決め、radius
で半径を設定して円を描くことができます。
@Composable
fun DrawCircle() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val width = size.width
val height = size.height
drawCircle(
color = Color.Black,
radius = 400F,
center = Offset(width / 2, height / 2)
)
}
}
また、楕円を描きたい場合は、drawOval()
を使用します。
topLeft
で描画位置を決め、size
で大きさを設定して楕円を描くことができます。
@Composable
fun DrawOval() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val width = size.width
val height = size.height
drawOval(
color = Color.Black,
topLeft = Offset(width / 4, height / 3),
size = Size(width = width / 2, height = height / 5)
)
}
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F2980857%2Fc5c2ddbd-7ae6-5eac-1bb0-90cbb3017e46.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=689e5aec804d8564932b6bd66557661f)
弧を描く
drawArc()
を使用します。
startAngle
で開始位置(0F
が3時の方向)を決め、sweepAngle
で弧の角度を設定して弧を描くことができます。
また、useCenter
をtrue
にすると、扇型に描画することができます。
@Composable
fun DrawArc() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val width = size.width
drawArc(
color = Color.Black,
startAngle = 0F,
sweepAngle = 90F,
useCenter = true,
size = Size(width = width / 2, height = width / 2)
)
drawArc(
color = Color.Black,
startAngle = 0F,
sweepAngle = 90F,
useCenter = false,
size = Size(width = width / 2, height = width / 2),
style = Stroke(width = 5F),
topLeft = Offset(0F, width / 2)
)
}
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F2980857%2F657f9d31-475c-cd2e-4e6f-021341a29e12.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=bef94f337eb730f139a4da27b7adff88)
点を描く
drawPoints()
を使用します。
points
で描きたい点の位置をリストで指定して点を描くことができます。
また、pointMode
で描き方、cap
で点の形を設定できます。
@Composable
fun DrawPoints() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val height = size.height
drawPoints(
points = listOf(
Offset(300F, height / 2),
Offset(500F, height / 2),
Offset(700F, height / 2),
Offset(900F, height / 2),
Offset(1100F, height / 2),
),
pointMode = PointMode.Points,
color = Color.Black,
strokeWidth = 50F,
cap = StrokeCap.Square
)
}
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F2980857%2Fe3fb026d-0c23-54a4-3119-004a8934cb8b.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=e3eeb55e91009de0f0a1c08f140431c3)
パスを決めて描く
drawPath()
を使用します。
path
を作成しdrawPath()
に渡すことで、作成したpath
に色を付けることができます。
@Composable
fun DrawPath() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val width = size.width
val height = size.height
val path = Path()
path.moveTo(width / 2, height / 3) // pathの開始位置
path.lineTo(width / 2, height / 2) // 開始位置→(width / 2, height / 2)
path.lineTo(width, height / 3) // (width / 2, height / 2)→(width, height / 3)
drawPath(
path = path,
color = Color.Black
)
}
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F2980857%2F57bcf92e-17a0-aa47-a1f6-1454eb8ecd08.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=db025c13dd4d84e000ed253405fdce96)
使ってみた
Canvasでできることと使い方の基本について確認できたので、単純なアナログ時計のレイアウトを描いてみました。
@Composable
fun Clock() {
Canvas (
modifier = Modifier
.size(360.dp)
) {
drawCircle(
color = Color.Black,
radius = 180.dp.toPx(),
center = center,
style = Stroke(width = 3.dp.toPx())
)
drawPoints(
points = listOf(
center,
Offset(180.dp.toPx(), 12.dp.toPx()),
Offset(264.dp.toPx(), 35.dp.toPx()),
Offset(325.dp.toPx(), 97.dp.toPx()),
Offset(348.dp.toPx(), 180.dp.toPx()),
Offset(325.dp.toPx(), 263.dp.toPx()),
Offset(264.dp.toPx(), 325.dp.toPx()),
Offset(180.dp.toPx(), 348.dp.toPx()),
Offset(96.dp.toPx(), 325.dp.toPx()),
Offset(35.dp.toPx(), 263.dp.toPx()),
Offset(12.dp.toPx(), 180.dp.toPx()),
Offset(35.dp.toPx(), 97.dp.toPx()),
Offset(96.dp.toPx(), 35.dp.toPx()),
),
pointMode = PointMode.Points,
color = Color.Black,
cap = StrokeCap.Square,
strokeWidth = 5.dp.toPx()
)
drawLine(
color = Color.Black,
start = center,
end = Offset(center.x, 40.dp.toPx()),
strokeWidth = 5.dp.toPx()
)
drawLine(
color = Color.Black,
start = center,
end = Offset(center.x + 100.dp.toPx(), center.y),
strokeWidth = 5.dp.toPx()
)
}
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F2980857%2Fe606575f-fa36-587f-e94e-496263596f99.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=f7d9c2f66d32941da1136944af7f0436)
center
でCanvasの中心のOffsetを取得したり、dp.toPx()
でDpからピクセルへの変換をしたりもできるので、機種によってレイアウトが崩れるという心配も少なそうですね。
おわりに
今回はJetpack ComposeのCanvas APIの使い方についてご紹介しました。
私自身今回でほぼ初めて触れましたが、シンプルなレイアウトであればさほど難しくなく作れそうです。
アニメーションをつけたり、テキストを入れたりすることもできるようなので、今後勉強して使いこなせるようになりたいと思います。
最後まで読んでいただきありがとうございました。
明日のレコチョク Advent Calendar 2022は15日目 シンプルな掲示板を素のPHPとCakePHPで作って比較する となります。お楽しみに!
この記事はレコチョクのエンジニアブログの記事を転載したものとなります。