2021/07/06
UI ComponentにJetpackComposeを採用したアプリを開発してみました
Jetpack Composeをメインで使ったアプリをリリースしました。
Jetpack Composeとは
Jetpack Composeは宣言的なコードでUIを記述するためのフレームワークです。
簡単にいうと、Htmlみたいな感じでUIを作ることができます。
SwiftUIやFlutterなんかもその流れをくんでいるので、メインストリームになってきているんだと思います。
私はWebアプリの開発の経験が多く、Html(最近はNuxtjsがメイン)のような感じでUI構築ができる点で、Jetpack Composeはとてもうれしい存在です。
従来のxmlを使ったレイアウトの開発は、何かデザインツール(photoshopとかsketchとか)をいじっている感じがして苦手でした。
今回Jetpack Composeを使ってみてとても良かったので、(私は)今後のAndroidアプリ開発では必ず、Jetpack Composeを採用しようと考えています。
・・ですが、まだたまにLayout周りで原因不明のクラッシュがあったりするので、(大きめのアプリやクラッシュにシビアなタイプのアプリケーションであれば)安定版がリリースされるまでJetpack Composeの採用は先延ばしにした方が良いと思います。
Jetpack Composeを使ったUI Componentの例
ついですが、今回はJetpack Composeで、ステータスアイコン付きのアバターUIコンポーネントの作り方を紹介します。
以下のような見た目のAvatarコンポーネントを作ってみます。
完成版
以下のようなソースコードになりました。
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.google.accompanist.coil.rememberCoilPainter
@Composable
fun Avatar(
imageUrl: String,
size: Dp = 50.dp,
statusColor: Color? = null,
) {
val darkTheme: Boolean = isSystemInDarkTheme()
Box(contentAlignment = Alignment.TopEnd) {
Image(
painter = rememberCoilPainter(imageUrl),
contentDescription = null,
modifier = Modifier
.size(size)
.clip(CircleShape)
.border(1.dp, if (darkTheme) Gray600 else Gray200, CircleShape),
contentScale = ContentScale.Crop
)
statusColor?.let { color ->
val statusColorSize = size / 5
Canvas(
modifier = Modifier
.size(statusColorSize)
.offset(x = -statusColorSize / 4, y = statusColorSize / 4)
.border(1.dp, if (darkTheme) Gray800 else Gray50, CircleShape)
) {
val statusColorSizePx = statusColorSize.toPx()
drawCircle(
color = color,
radius = statusColorSizePx / 2f,
)
}
}
}
}
@Preview("avatar default")
@Preview("avatar dark theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun AvatarPreview() {
Surface(
modifier = Modifier.fillMaxSize(),
color = if (isSystemInDarkTheme()) Gray800 else Gray50
) {
Box(contentAlignment = Alignment.Center) {
Avatar(
imageUrl = "https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
statusColor = Green500,
)
}
}
}
ちょっとした解説
step1 Boxで囲む
まずは外側をBox
で囲みます。
Box(contentAlignment = Alignment.TopEnd) {
// content
}
これはSwiftUIでいえばZStack
で、htmlだったらdiv
(ちょっと違う?)みたいな感じでしょうか。
このBoxにstyelをつけるような感じで、contentAlignment
にTopEnd
を指定します。(ステータスアイコンを右上隅に配置したい)すると、中身が右上隅から配置されるような感じになります。
step2 円形の画像を配置する
先程作成したBoxの中に画像を配置します。
Box(contentAlignment = Alignment.TopEnd) {
// Imageを追加
Image(
painter = rememberCoilPainter(imageUrl),
contentDescription = null,
modifier = Modifier
.size(size)
.clip(CircleShape)
.border(1.dp, if (darkTheme) Gray600 else Gray200, CircleShape),
contentScale = ContentScale.Crop
)
}
rememberCoilPainter
はcoilというgoogle製のライブラリの関数で、画像のurlを元に非同期で画像データをインターネットから取得し、描画までしてくる優れものです。
ここで重要なのが、modifier
で、styleを細かく変更するときに使います。
今回は、以下のような感じで指定しています。
- Modifier.size(size): アバター画像の表示サイズを設定
- Modifier.clip(CircleShape): 円形に切り取り
- Modifier.border(1.dp, Gray200, CircleShape): グレーの色で、1pxの円形のボーダーを設定
また、以下のプロパティも設定しておきます。
- contentDescriptionでaltのようなものの設定
contentScale = ContentScale.Crop
で表示サイズにあうように切り取ってくれる設定
step3 ステータスアイコンを追加
Box内のImageの後に追加します。
(Box内のコンテンツで後の方に記述されているものが、より上に表示されるため、画像の上にステータスアイコンが乗っかって表示されます。)
Box(contentAlignment = Alignment.TopEnd) {
Image(...)
// 以下を追加
statusColor?.let { color ->
val statusColorSize = size / 5
Canvas(
modifier = Modifier
.size(statusColorSize)
.offset(x = -statusColorSize / 4, y = statusColorSize / 4)
.border(1.dp, if (darkTheme) Gray800 else Gray50, CircleShape)
) {
val statusColorSizePx = statusColorSize.toPx()
drawCircle(
color = color,
radius = statusColorSizePx / 2f,
)
}
}
}
- statusColorが設定されていれば(nullでなければ)、円形のステータスアイコンを表示します。
Canvas
とdrawCircle
を使って、statusColorで指定された色のアイコンを描画します。- Modifier.offsetをうまく使い、少し枠にかぶるくらいの位置に表示します。
使い方
最後に、Previewのコードにもある通り、Avatar
を呼び出して、Avatarコンポーネントを使うことができるようになります。
@Preview("avatar default")
@Preview("avatar dark theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun AvatarPreview() {
// 画面サイズ全体に背景色をつける
Surface(
modifier = Modifier.fillMaxSize(),
color = if (isSystemInDarkTheme()) Gray800 else Gray50
) {
Box(contentAlignment = Alignment.Center) {
// 作成したAvatar画像の関数を使って、Avatarを表示(画像URL及びステータスアイコンの色を指定)
Avatar(
imageUrl = "https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
statusColor = Green500,
)
}
}
}
注意事項
Gray600
とかGreen500
とかの色の定義はこちらのものを使っています。- 事前にaccompanist-coilのライブラリを依存関係に追加しておく必要があります。
dependencies {
final def accompanist_version = '0.11.0'
implementation "com.google.accompanist:accompanist-coil:$accompanist_version"
}
※ リリースしたアプリ内でもソースコードを公開しています。
Avatarコンポーネントだけでなく、いくつか紹介していますので、ご興味のある方は、ぜひインストールして使ってみてください。
以上です。
関連する記事
UI ComponentにJetpackComposeを採用したアプリを開発してみました
Jetpack Composeメインのアプリ開発を行いました。アプリ内で紹介しているAvatar Componentの開発方法を紹介します。
[Jetpack Compose]プログラミング言語のシンタックスハイライト
github.com/kbiakov/CodeView-Androidを少し改造して、Jetpack Composeでコードビューを実装してみました
[Jetpack Compose]Analytics / Crashlytics / FCMを導入
FirebaseをJetpack Composeアプリに導入して、プッシュ通知を受取り対応する画面を開くようにしました。またAnalyticsやCrashlyticsも同時に導入しました。
[Jetpack Compose]ローディングアニメーションつきカードコンポーネントの作成
rememberinfinitetransitionをうまく活用し、Jetpack Composeでローディングアニメーションつきのカードコンポーネントを作成しました。