Google I/O 2022で発表があった「Compose for Wear OS」を見てから、WearOS開発への意欲が湧いてきたので、Compose for Wear OS の Codelabに入門してみた。
Codelab
環境
- 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 シェイプがおすすめなので、オーバーライドしない方がいい
🚶♀️寄り道
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には他にも
checkboxIcon
やradioIcon
がある
ScalingLazyColumn
- LazyColumnを使用することもできるが、円形のデバイスは上部と下部が小さいためアイテムを表示するスペースが少ない
- wearOSでは独自のバージョンの LazyColumn があり、円形のデバイスに対するサポートを強化してる
ScalingLazyColumn
を利用するとアイテムが画面中央に近づくにつれてフルサイズになり、中央から離れると縮小して透明性が上がる- lazyStateを
rememberScalingLazyListState()
を使用する LazyColumn
のかわりにScalingLazyColumn
を使用するcontentPadding
とverticalArrangement
は使用しない- ビューポートの大部分はリストアイテムで埋められるため、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
- 画面右側のインジケーターであり、渡す状態オブジェクトの種類に基づいて現在のインジケーターの場所を示す
PositionIndicator
がScalingLazyColumn
レベルではなく、Scaffold
レベルなのか?について- 円形デバイスなどで画面が湾曲していると、
ScalingLazyColumn
の中央位置にインジケーターがあってはほとんど画面ないには収まらない ScalingLazyColumn
は画面全体を収めるわけではないので、画面全体を収めるScaffold
レベルで指定
positionIndicator = { PositionIndicator( scalingLazyListState = listState ) }
全部できるとこんな感じ
終わり / 完走した感想
- モバイルのJetpack Composeの知見のほとんどを活かせる
- 円形デバイスのサポートが手厚い
- 独自のUIなどでつくろうとせず、WaerOSむけのマテリアルライブリを活用するのが開発は効率的でよさそうに感じた