android-ux について
Android 開発者が既存画面の Material Design 3 準拠を確認し、見落としやすいプラットフォーム仕様を扱うためのリファレンススキルです。折りたたみ端末、コントラスト、モーション、アクセシビリティ、デザイントークンを解説します。
主な機能
- 01Material 3 準拠監査
- 02折りたたみ姿勢のレイアウト指針
- 03アプリ内コントラスト設定の指針
- 04モーショントークンと動きの抑制
- 05アクセシビリティとテーマの確認
- 06違反を探す簡易 grep チェック
活用シーン
- 01リリース前の既存画面のレビュー
- 02折りたたみ姿勢に合わせたレイアウト調整
- 03モーションとコントラストの動作確認
- 04ハードコードされたスタイルや Material 2 の import の検出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rcosteira79/ android-skills --skill android-uxインストール後、Claude Code で/android-uxを入力して読み込まれたか確認します。
詳細
Android UX
このリファレンスでは、プラットフォームに関する見落としやすい事実と、既存の画面が M3 に準拠しているかレビューする方法を説明します。M3 に準拠した UI を構築する基本は扱いません。
見落としやすいプラットフォームの事実
折りたたみ式デバイスの姿勢
折りたたみ式デバイスでは、ウィンドウサイズクラス以外の姿勢も考慮する必要があります。WindowInfoTracker と FoldingFeature(Jetpack WindowManager)で検出し、ヒンジをまたいでインタラクティブなコンテンツや重要な情報を配置しないでください。
| 姿勢 | 検出方法 | レイアウトの動作 |
|---|---|---|
| フラット(展開状態) | 折りたたみ機能なし | 幅に基づいて Medium/Expanded として扱います |
| 半開き、水平折り(テーブルトップ) | HALF_OPENED + Orientation.HORIZONTAL | 上半分にコンテンツ、下半分にコントロールを配置します |
| 半開き、垂直折り(ブック) | HALF_OPENED + Orientation.VERTICAL | 左側にリスト、右側に詳細を配置します |
| 折りたたみ状態(カバースクリーン) | — | Compact として扱います |
@Composable
fun FoldAwareLayout() {
val context = LocalContext.current
val layoutInfo by WindowInfoTracker.getOrCreate(context)
.windowLayoutInfo(context) // accepts @UiContext — Activity, InputMethodService, or createWindowContext()
.collectAsStateWithLifecycle(initialValue = WindowLayoutInfo(emptyList()))
val fold = layoutInfo.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()
when {
fold?.state == FoldingFeature.State.HALF_OPENED ->
if (fold.orientation == FoldingFeature.Orientation.HORIZONTAL) TabletopLayout() else BookLayout()
else -> StandardAdaptiveLayout() // by window size class
}
}
M3 のコントラストレベル(ユーザーが調整できるコントラスト)
Compose の dynamicLightColorScheme/dynamicDarkColorScheme はシステムのコントラスト設定を自動的に読み取ります(SDK 34 以降)が、コントラスト用のパラメーターは公開していません。アプリ内でコントラストを調整できるようにするには(Standard 0.0/Medium 0.5/High 1.0 のトーン距離)、Hct + SchemeContent からスキームを構築します。
// com.google.android.material:material (MDC-Android), package com.google.android.material.color.utilities
// Marked @RestrictTo(LIBRARY_GROUP) — internal API, may change between versions; re-check on version bumps.
val hct = Hct.fromInt(0xFF6750A4.toInt())
val scheme = SchemeContent(hct, /* isDark = */ false, /* contrastLevel = */ 1.0)
val colorScheme = lightColorScheme(
primary = Color(scheme.primary),
onPrimary = Color(scheme.onPrimary),
// ... map remaining roles
)
マルチプラットフォーム対応や安定したパブリック API を求める場合は、com.materialkolor:material-color-utilities が KMP の代替手段になります。
モーション:期間トークンとモーション低減
任意のミリ秒数ではなく、M3 の期間段階にイージングを組み合わせます(short* → FastOutSlowIn、medium* → Emphasized(De/Ac)celerate、long*/extraLong* → Emphasized)。利用可能な場合は MotionScheme(Compose M3 1.4 以降)を使用し、そうでない場合はテーマ内の一元管理された場所で期間を管理します。
| トークングループ | 範囲 | 代表的な用途 |
|---|---|---|
short1…short4 | 50–200ms | マイクロインタラクション、状態変更(リップル、選択、スイッチ) |
medium1…medium4 | 250–400ms | 標準的な遷移(画面の表示/終了、展開、表示) |
long1…long4 | 450–600ms | コンテナ変換、大きなサーフェス間のフェードスルー |
extraLong1…extraLong4 | 700–1000ms | タブレット/折りたたみ式デバイスでの共有要素/ヒーロー遷移 |
アニメーションは中断可能にし、入力をブロックしないでください。モーション低減に対応してください — Compose には組み込みの LocalReducedMotion がないため、ANIMATOR_DURATION_SCALE を読み取って提供します。
val LocalReducedMotion = staticCompositionLocalOf { false }
@Composable
fun AppTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val reduceMotion = remember {
Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) == 0f
}
CompositionLocalProvider(LocalReducedMotion provides reduceMotion) { MaterialTheme { content() } }
}
// at the call site
AnimatedVisibility(
visible = isVisible,
enter = if (LocalReducedMotion.current) EnterTransition.None else fadeIn() + slideInVertically(),
) { Content() }
(staticCompositionLocalOf とキーなしの remember は、設定を Activity ごとに一度読み取ります。ライブ更新するには、compositionLocalOf と ANIMATOR_DURATION_SCALE の ContentObserver に切り替えてください。)
M3 準拠監査
画面や機能の Material Design 3 準拠状況をレビューする際に使用します。記憶だけに頼ると見落としやすい項目です。各カテゴリを合格/一部合格/不合格で評価し、リリース前に一部合格/不合格の項目を修正してください。
1. カラートークン
- すべての色が
MaterialTheme.colorSchemeのロールを参照していること — ハードコードされた hex/ARGB を使用しないこと。 - ロールを正しく使用していること:主要なアクションには
primary、目立たせない要素/アクセントにはsecondary/tertiary、エラーにはerrorを使用し、on*を正しく組み合わせること。サーフェスを重ねる場合は、任意のグレーではなくsurfaceContainerLow/Highを使用すること。 - アウトライン:3:1 が必要なインタラクティブな境界(フィールドの枠線、フォーカスリング)には
outline、装飾的な区切り線にはoutlineVariantを使用すること。 - Android 12 以降ではダイナミックカラーを使用し、静的なフォールバックを用意すること。
2. タイポグラフィ
- すべてのテキストが
MaterialTheme.typographyに基づいていること — インラインのfontSize/fontWeightを使用しないこと。 - スケールを正しく使用していること:ヒーローには
display*、セクション見出しにはheadline*、カード/ダイアログのタイトルにはtitle*、コンテンツにはbody*、ボタン/キャプションにはlabel*を使用すること。
3. 形状
- 角丸はハードコードされた
RoundedCornerShapeではなく、MaterialTheme.shapesから取得すること。 - スケール:チップには
extraSmall(4dp)、カードにはsmall(8dp)、ダイアログにはmedium(12dp)、シートにはlarge(16dp)、FAB にはextraLarge(28dp)を使用すること。形状の表現に一貫性を持たせること。
4. 標高とサーフェス
- 標高はドロップシャドウではなく、トーンカラー(サーフェスコンテナー)で表現します。シャドウによる標高はダイアログ/メニュー/FAB に限って使用します。
- 手動の
shadowElevationよりも、ElevatedCard/ElevatedButtonを使用します。 - コンテナーの階層はトーンに沿って単調に変化させます(
surfaceContainerLowest < … < Highest)。隣り合うレイヤーがどちらかのテーマで同じ色に表示されると、標高の手掛かりが失われます。
5. コンポーネント
- M3 コンポーネント(
androidx.compose.material3.*)を使用し、M2(androidx.compose.material.*)は使用しません。また、1 つの画面で M2 と M3 を混在させません。 - 本来の用途に沿って使用します。主なアクションには
FloatingActionButton、グループ化されたコンテンツにはCard、画面レベルのアクションにはTopAppBarを使用します。
簡単な grep チェック
プロジェクトのルートから実行してください。一致した項目はそれぞれカテゴリ 1/3/5 の違反に該当するため、人によるレビューの前に確認してください。
rg --type kt 'Color\(0x[0-9a-fA-F]{6,8}\)' --files-with-matches | head # 1: hardcoded colors
rg --type kt 'RoundedCornerShape\(\s*\d+(?:\.\d+)?\s*\.dp\s*\)' --files-with-matches | head # 3: hardcoded radii
rg --type kt 'import androidx\.compose\.material\.' --files-with-matches | head # 5: Material 2 imports
6. レイアウトと間隔
- パディング/マージンには 8dp グリッドを使用します。レスポンシブな Compact/Medium/Expanded のブレークポイントを設定し、画面は標準レイアウト(フィード、リストと詳細、補助ペイン)に対応させます。
- 幅の広い画面ではコンテンツ幅を制限します。Large では本文コンテンツを約 840~1040dp に制限して中央揃えにします。全幅を使用するのはナビゲーション部分であり、段落には使用しません。
- 折りたたみ式デバイスの姿勢に対応します(ヒンジをまたいでコンテンツを配置しません。該当する場合はテーブルトップ/ブックレイアウトを使用します)。
- Medium 以上ではダイアログを画面中央に配置し(全画面にはしません)、Expanded 以上ではボトムシートをサイドシートに切り替えます。
7. ナビゲーション
- ナビゲーションコンポーネントは画面幅に合わせます(ボトムバー/レール/ドロワー)。項目にはアイコンとラベルの両方を付け、現在の遷移先は
selectedとアクティブインジケーターで示します。
8. モーション
- トランジションには M3 のイージングと時間トークンを使用します。共有要素には
Emphasizedを 300~500ms で使用し、入場/退場には M3 のパターン(フェードスルー、コンテナ変形)を適用します。中断可能にし、動きを抑える設定を尊重します。
9. アクセシビリティ
- 意味のある画像/アイコンには
contentDescriptionを設定します。コントラストは WCAG AA を満たします(本文は 4.5:1、大きな文字と UI コンポーネントは 3:1)。タッチターゲットは 48dp 以上にします。 - 複合項目は
semantics(mergeDescendants = true)で結合し、TalkBack が 1 つの単位として読み上げるようにします(結合時はアイコンのcontentDescription = nullにします)。 - セクションタイトルには
semantics { heading() }を設定し、スクリーンリーダーのユーザーがセクション間を移動できるようにします。 - 読み上げ順序は論理的に設定します(必要に応じて
semantics { traversalIndex }を使用します)。サポートされている場合は、ユーザーがコントラスト(標準/中/高)を選択できるようにすることも検討します。
10. テーマの一貫性
MaterialThemeは 1 つだけにします(ネストしたテーマや競合するテーマは使用しません)。ライトテーマとダークテーマの両方をテストします。カスタムテーマ拡張にはグローバル変数ではなくCompositionLocalを使用し、ブランドカラーはコンポーネントごとの上書きではなく、カスタムColorSchemeで設定します。
監査サマリーテンプレート
Screen: [name] Date: [date]
| # | Category | Score | Notes |
|---|---------------------|---------|------------------------|
| 1 | Color tokens | Pass | |
| 2 | Typography | Partial | bodySmall hardcoded |
| 5 | Components | Fail | M2 Scaffold still used |
| 6 | Layout & spacing | Partial | No tablet breakpoint |
| 9 | Accessibility | Partial | Missing headings |
…
Action items:
- [ ] ...