kako.dev

開発、自作アプリのこと

WearOS for Jetpack Compose の Codelabをやってみた

Google I/O 2022で発表があった「WearOS for Jetpack Compose」を見てから、WearOS開発への意欲が湧いてきたので、WearOS for Jetpack Compose の Codelabに入門してみた。

Codelab

developer.android.com

環境

  • Android Studio Electric Ele
  • WearOS API 30 emulater

Memo

依存関係

  • WaerOSとモバイルで依存関係が変わる点があるが、そのほとんどはJetpackComposeのアーキテクチャレイヤで言えば上位部分

  • Jetpack Composeで使用されている依存関係の多くは、WearOSになっても変わらない

  • 変わる部分は Material、Foundation、Navigation
  • Foundationは併用できそう?
Wear OS の依存関係(androidx.wear.*) 標準の依存関係(androidx.*)
androidx.wear.compose:compose-material androidx.compose.material:material
androidx.wear.compose:compose-navigation androidx.navigation:navigation-compose
androidx.wear.compose:compose-foundation androidx.compose.foundation:foundation

Wear OS 上でも標準の依存関係を使用することは技術的に可能ですが、最適なエクスペリエンスを得るには常に Wear 固有のバージョンを使用することをおすすめします。

MaterialTheme

  • 基本的にはComposeのMaterialThemeとかわらない
  • ただし、shapeはオーバーライドしない
  • WearOSは円形デバイスと非円形デバイス向けに最適化されたデフォルトの Material Wear シェイプがおすすめなので、オーバーライドしない方がいい

🚶‍♀️寄り道

ということでマテリアルテーマのshapeを覗いてみた

package androidx.wear.compose.material

@Immutable
public class Shapes(
    /**
     * Buttons and Chips use this shape
     */
    public val small: CornerBasedShape = RoundedCornerShape(corner = CornerSize(50)),

    public val medium: CornerBasedShape = RoundedCornerShape(4.dp),
    /**
     * Cards use this shape
     */
    public val large: CornerBasedShape = RoundedCornerShape(24.dp),
) 
  • small, medium, large とサイズ別に角丸の値の異なるshapeが定義されてる
  • コメントを見ると、ボタンやチップはsmall, カードはlargeを使うことが推奨されてる
  • 特に意識せずにShapeはオーバーライドせずに使うのがよさそう

シンプルなComposableの実装

ボタン

  • リポジトリをCloneしてきた状態だとテキストが表示されているだけなので、完成に向けてコンテンツ’を作成していく
  • まずはボタンを作成する
  • ButtonにButtonDefaults.LargeButtonSize を使用する
  • WearOS向けに最適化されたプリセットなので推奨

テキスト

  • TextをComposeで実装するのと同様に実装する
  • stringもComposeそのままstringResourceで可能
  • 円形デバイスと非円形デバイスでstring.xmlを定義分けすることが可能
  • 円形デバイスの時は values-round/string.xml で定義、非円形デバイスはvalues/string.xml に定義すればいい

カード

  • AppCardでレイアウトを組む
  • WearOSは、AppCard と TitleCard の 2 つの主要なカードが用意されている

🚶‍♀️寄り道

AppCardとTitleCardの使い分け

https://developer.android.com/training/wearables/compose/cards

  • TitleCard
    1つのアプリからのメッセージなどの情報を表示 下記の3つのスロットを持っている

    • コンテンツのタイトル
    • 時間(optional)
    • 画像またはテキストのコンテンツ
  • AppCard
    複数のアプリケーションからのインタラクティブな要素を表示 下記の5つのスロットを持っている

    • アプリのアイコン
    • アプリの名前
    • 時間
    • コンテンツのタイトル
    • 画像またはテキストのコンテンツ

Wear の一意のComposableの実装

Chip

  • マテリアルデザインのガイドラインにChipはあるが、標準のマテリアル ライブラリに実際にコンポーズ可能な関数は存在しない
  • チップはタップ 1 回で簡単に操作できるように設計されいるので、画面の表示スペースが限られている Wear デバイスに特に適している
  • Chipコンポーザブルは他のコンポーザブル同様なパラメータを多く使用するので、特別なことはない

Toggle Chip

  • Chipに似ているが、ラジオボタン、トグル、チェックボックスの操作が可能
  • ステートの状態を見てスイッチのアイコンを切り替えする場合 ToggleChipDefaults.switchIcon が使える
var checked by remember { mutableStateOf(true) }
ToggleChip(
        checked = checked,
        toggleControl = {
            Icon(
                imageVector = ToggleChipDefaults.switchIcon(checked = checked),
                contentDescription = if (checked) "On" else "Off"
            )
        },
        onCheckedChange = {
            checked = it
        },
...
)
  • ToggleChipDefaultsには他にも checkboxIconradioIcon がある

ScalingLazyColumn

  • LazyColumnを使用することもできるが、円形のデバイスは上部と下部が小さいためアイテムを表示するスペースが少ない
  • wearOSでは独自のバージョンの LazyColumn があり、円形のデバイスに対するサポートを強化してる
  • ScalingLazyColumn を利用するとアイテムが画面中央に近づくにつれてフルサイズになり、中央から離れると縮小して透明性が上がる
  • lazyStateをrememberScalingLazyListState() を使用する
  • LazyColumn のかわりにScalingLazyColumnを使用する
  • contentPaddingverticalArrangement は使用しない
    • ビューポートの大部分はリストアイテムで埋められるため、ScalingLazyColumn のデフォルト設定により、デフォルト視覚効果の改善が保証されるため
  • itemIndex を 0 として autoCentering を設定すると、最初のアイテムに十分なパディングを確保される

Scaffold

  • Scaffoldで一般的なパターンに必要なレイアウト構造が用意されてる
  • トップレベル コンポーネント(時間、周辺減光、スクロール / 位置インジケーター、ページ インジケーター)を備えた 4 つの Wear 固有のレイアウトをサポート
  • アプリバー、FAB、ドロワー、その他のモバイル固有の要素ではない
Scaffold(
            timeText = { },
            vignette = { },
            positionIndicator = { }
        )

TimeText

  • 内部で曲線テキストを使用するため時間を簡単に表示可能
  • マテリアル ガイドラインでは、オーバーレイ(またはアプリ)のどの画面でも上部に時刻を表示するよう求めてる
  • スクロール中は時計を非表示にするような処理をいれて実装
timeText = {
                if (!listState.isScrollInProgress) {
                    TimeText()
                }
            }

Vignette

  • スクロール可能な画面が表示されているときに、ウェアラブル画面の上端と下端をぼかす
  • ユースケースに応じて上端、下端、または両方をぼかすことができます
    • VignettePosition には Top, Bottom, TopAndBottomがあるのでそれで指定可能
  • 画面が 1 つ(スクロール可能な画面)であるため、周辺減光された状態の表示により判読性を改善すると考えている
vignette = {
                Vignette(vignettePosition = VignettePosition.TopAndBottom)
            }

PositionIndicator

  • 画面右側のインジケーターであり、渡す状態オブジェクトの種類に基づいて現在のインジケーターの場所を示す
  • PositionIndicatorScalingLazyColumnレベルではなく、Scaffold レベルなのか?について
  • 円形デバイスなどで画面が湾曲していると、ScalingLazyColumnの中央位置にインジケーターがあってはほとんど画面ないには収まらない
  • ScalingLazyColumnは画面全体を収めるわけではないので、画面全体を収めるScaffold レベルで指定
positionIndicator = {
    PositionIndicator(
        scalingLazyListState = listState
    )
}

全部できるとこんな感じ

終わり / 完走した感想

  • モバイルのJetpack Composeの知見のほとんどを活かせる
  • 円形デバイスのサポートが手厚い
  • 独自のUIなどでつくろうとせず、WaerOSむけのマテリアルライブリを活用するのが開発は効率的でよさそうに感じた