内容が長くなったので続編に分けました
安定版リリース 🎉
Jetpack Composeライクにウィジェットを開発できる androidx.glance のバージョン 1.0.0 が2023年9月に公開されました!
本記事では Glance 入門として簡単なウィジェットを開発します
- セットアップ
- GlanceでUI作成
- Material3のカラーシステムを利用
作成したアプリ・ウィジェットのコードはGitHubで公開しています
Glanceのセットアップ
Jetpack Compose と同様のセットアップ方法に加えて以下の依存も追加します
dependencies {
implementation "androidx.glance:glance-appwidget:1.1.0"
implementation "androidx.glance:glance-material3:1.1.0"
}
Glance ウィジェット開発の基本
公式ドキュメントに従って進めます
AppWidgetProviderInfo メタデータを追加する
ウィジェットの基本情報を定義します。基本的にはRemoteViewによるウィジェット開発と同じで、各種属性の詳細は公式ドキュメントに記載があります。ただし、android:initialLayout
はXMLファイルが無いのでライブラリ側で事前定義されたものを指定します。
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/widget_description"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="110dp"
android:minHeight="40dp"
android:previewImage="@null"
android:resizeMode="none"
android:updatePeriodMillis="0"
android:widgetCategory="home_screen" />
ウィジェットを追加するときのUIを色々カスタマイズできます
-
android:previewImage
ウィジェットのプレビュー画像(指定なしだとアプリアイコン) -
android:description
ウィジェットの説明文(Android12以降のみ表示されます) -
android:label
ウィジェットの名前(後述のAndroidManifest.xml
で指定)
添付画像はPixelのemulatorで撮影しました
端末よって見た目が異なる場合もあります
Android 11 | Android 13 |
---|---|
GlanceAppWidgetの追加
provideGlance
関数でウィジェットのUIを定義します。ここで Jetpack Compose と同じ要領でUIを作成できるのが Glance 最大の利点です!
importする名前空間に注意
Glance でもお馴染みの名前で各種 Composable関数が提供されていますが、import先の名前空間はandroidx.glance.*
です。 androidx.compose.*
とは異なるので混同に気をつけてください。
class HelloWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
// 以下2点以外はだいたい同じ!
// - import androidx.compose -> androidx.glance
// - Modifier -> GlanceModifier
Box(
modifier = GlanceModifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text("Hello glance!")
}
}
}
}
GlanceAppWidgetReceiverの追加
ウィジェットの追加・更新はBroadcastRecevier
経由で行われます。Glanceでは専用のGlanceAppWidgetReceiver
を継承します。
class HelloWidgetReceiver : GlanceAppWidgetReceiver() {
// 実際に表示するUIを定義したGlanceAppWidgetを返す
override val glanceAppWidget: GlanceAppWidget = HelloWidget()
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// 今回はstatelessなため画面更新は不要、特別な対応なし
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
}
AndroidManifestの修正
追加したGlanceAppWidgetReceiver
をマニフェストで宣言し、Androidシステム側に見えるようにします。
android:label
属性はウィジェットを追加するとき表示される値です
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<receiver
android:name=".HelloWidgetReceiver"
android:exported="true"
android:label="@string/widget_label">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_info" />
</receiver>
</application>
</manifest>
Material3 カラーシステムを利用する
Jetpack Compose の利点として Materialデザインをコードベースで利用できる点があります。 GlanceでもMaterial3のカラーシステムをKotlinのコードだけで利用できます。(typographyとshapeは未対応)
GlanceThemeの定義
@Composable
fun WidgetTheme(
dynamicColor: Boolean = true,
content: @Composable () -> Unit,
) {
val colors =
if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
GlanceTheme.colors
} else {
ColorProviders(
// Jetpack Composeでいつも使う色定義
// androidx.compose.material3.ColorScheme
light = LightColorScheme,
dark = DarkColorScheme,
)
}
GlanceTheme(colors) {
content()
}
}
GlanceThemeの利用
- 定義した
GlanceTheme
でラップする -
GlanceTheme
の Composition Local から色リソースにアクセスする
class HelloWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
WidgetTheme {
HelloScreen()
}
}
}
}
@Composable
fun HelloScreen(
modifier: GlanceModifier = GlanceModifier,
) {
Box(
modifier = modifier
.fillMaxSize()
.background(GlanceTheme.colors.surface)
.cornerRadius(8.dp)
.padding(4.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = glanceString(id = R.string.widget_hello_message),
style = TextStyle(
color = GlanceTheme.colors.onSurface,
)
)
}
}
stringResource()
は使えない
以下のようにContext
から直接取得する必要があります
@Composable
fun glanceString(@StringRes id: Int): String {
return LocalContext.current.getString(id)
}
動作例
Tips
ちょっとした工夫や困りポイントの紹介
モジュール分割
Jetpack Compose と Glance のUI実装を別モジュールにすればimportするときandroidx.glance.*
とandroidx.compose.*
の混同を避けられます。ただし Material3 のカラー定義は共通化したいのでテーマのモジュールをさらに別途用意しました。
モジュール | theme | ui | widget |
---|---|---|---|
実装内容 | Material3 のカラーとGlanceTheme 定義 |
Jetpack Compose のUI実装 | Glance のウィジェット実装 |
androidx.compose.material3:material3 |
⭕ | ⭕ | |
androidx.glance:glance-appwidget |
⭕ | ⭕ | |
androidx.glance:glance-material3 |
⭕ | ⭕ |
GlanceModifier.cornerRadius
ドキュメントを見ると怪しい記述が...
Note: Only works on Android S+.
なんと Android S(API 31)未満では角丸になりません。代替案として角丸を定義した drawable に重ねて表示する方法があります。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff"/>
<corners android:radius="8dp" />
</shape>
Box(
modifier = GlanceModifier.fillMaxSize(),
) {
Image(
provider = ImageProvider(R.drawable.widget_background),
contentDescription = null,
colorFilter = ColorFilter.tint(GlanceTheme.colors.surface),
modifier = GlanceModifier.fillMaxSize(),
)
YourContent()
}