LoginSignup
4
6

【Android】Glanceでウィジェット開発〜導入編〜

Last updated at Posted at 2023-12-07

内容が長くなったので続編に分けました

  1. 導入編 ← HERE
  2. 状態管理編
  3. 非同期処理編

安定版リリース 🎉

Jetpack Composeライクにウィジェットを開発できる androidx.glance のバージョン 1.0.0 が9月に公開されました!

本記事では Glance 入門として簡単なウィジェットを開発します

  • セットアップ
  • GlanceでUI作成
  • Material3のカラーシステムを利用

hello_glance.gif

作成したアプリ・ウィジェットのコードはGitHubで公開しています

Glanceのセットアップ

build.gradle
dependencies {
   implementation "androidx.glance:glance-appwidget:1.0.0"
   implementation "androidx.glance:glance-material3:1.0.0"
}

Glance ウィジェット開発の基本

公式ドキュメントに従って進めます

AppWidgetProviderInfo メタデータを追加する

ウィジェットの基本情報を定義します。基本的にはRemoteViewによるウィジェット開発と同じで、各種属性の詳細は公式ドキュメントに記載があります。ただし、android:initialLayoutはXMLファイルが無いのでライブラリ側で事前定義されたものを指定します。

res/xml/appwidget_info.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 13)。

  • android:previewImage ウィジェットのプレビュー画像(指定なしだとアプリアイコン)
  • android:description ウィジェットの説明文
  • android:label ウィジェットの名前(後述のAndroidManifest.xmlで指定)

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属性はウィジェットを追加するとき表示される値です

AndroidManifest.xml
<?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の利用

  1. 定義したGlanceThemeでラップする
  2. 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)
}

動作例

widget_dark_mode.gif

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 に重ねて表示する方法があります。

res/drawable/widget_background.xml
<?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()
    }
4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6