Communitygithub.com

lerchenflo/claudeskills

Jetpack Compose アプリにエッジ トゥ エッジ対応を追加し、よくある問題を解決するためのスキルです。ナビゲーションバーやステータスバーに隠れたり重なったりするUIコンポーネントの修正、IMEインセットの修正、システムバーの視認性向上を行います。

claudeskills とは?

このAIエージェントスキルは、Android Jetpack Composeプロジェクトを段階的にエッジ トゥ エッジ対応へと移行するための包括的なガイドです。`enableEdgeToEdge`の追加、システムインセットの適用、IMEやリスト、ダイアログ、アダプティブScaffoldなど様々なシナリオに対応した正しい実装パターンと誤ったパターンをコード例と共に解説します。スキルは計画→実装→チェックリストの3ステップで構成され、Claude CodeやCursorなどのAIエージェントがプロジェクトを解析しながら自動的に移行を進められるよう設計されています。特に二重パディングを防ぐためのインセット適用ルールや、`fitInside`を用いたIME対応の推奨パターンなど、実践的なベストプラクティスを網羅しています。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lerchenflo/claudeskills/tree/HEAD/android/edge-to-edge

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

前提条件

  • プロジェクトはJetpack Composeを使用している必要があります。
  • プロジェクトのターゲットSDKは35以降である必要があります。SDKが35未満の場合は、SDKを35に引き上げてください。

ステップ1: 計画

  1. すべてのActivityクラスを特定して分析し、エッジ トゥ エッジ対応が既にあるものを確認します。エッジ トゥ エッジ対応がない各Activityについて、エッジ トゥ エッジ対応を計画します。
  2. 各Activity内のすべてのリストとFABコンポーネントを特定して分析し、既存のエッジ トゥ エッジ対応の有無を確認します。エッジ トゥ エッジ対応がない各コンポーネントについて、エッジ トゥ エッジ対応を計画します。
  3. 各ActivityでTextField、OutlinedTextField、BasicTextFieldをスキャンします。見つかった場合は、このスキルのIMEセクションに従って、IMEが入力フィールドを隠さないことを必ず確認してください。

ステップ2: エッジ トゥ エッジ対応を追加

  1. まだenableEdgeToEdgeを呼び出していない各ActivityのonCreate内のsetContentの前にenableEdgeToEdgeを追加します。
  2. ソフトキーボードを使用するすべてのActivityについて、AndroidManifest.xmlにandroid:windowSoftInputMode="adjustResize"を追加します。

ステップ3: インセットを適用

  • アプリはシステムインセットを適用するか、コンテンツをルーラーに合わせて、重要なUIがタップ可能な状態を保つ必要があります。二重パディングを避けるため、1つの方法のみを選択してください。

    1. 推奨: 利用可能な場合はScaffoldを使用し、PaddingValuesをcontentラムダに渡します。
    Scaffold { innerPadding ->
        // innerPadding はシステムバーとScaffoldコンポーネントの両方を考慮します
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .consumeWindowInsets(innerPadding),
            contentPadding = innerPadding
        ) { /* コンテンツ */ }
    }
    
    1. 推奨: 利用可能な場合は、マテリアルコンポーネントの自動インセット処理またはパディング修飾子を使用します。

      • Material 3コンポーネントは以下のコンポーネントのセーフエリアを自動管理します:
        • TopAppBar
        • SmallTopAppBar
        • CenterAlignedTopAppBar
        • MediumTopAppBar
        • LargeTopAppBar
        • BottomAppBar
        • ModalDrawerSheet
        • DismissibleDrawerSheet
        • PermanentDrawerSheet
        • ModalBottomSheet
        • NavigationBar
        • NavigationRail
      • Material 2コンポーネントの場合は、windowInsetsパラメータを使用してBottomAppBar、TopAppBar、BottomNavigationに手動でインセットを適用します。親コンテナにパディングを適用しないでください。代わりに、App Barコンポーネントに直接インセットを渡します。親コンテナにパディングを適用すると、App Barの背景がシステムバー領域に描画されなくなります。例えば、TopAppBarの場合は以下のいずれか1つのみを選択してください:
        1. 推奨: TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets)
        2. TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))
        3. TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
    2. Scaffoldの外部のコンポーネントには、Modifier.safeDrawingPadding()やModifier.windowInsetsPadding(WindowInsets.safeDrawing)などのパディング修飾子を使用します。

      Box(
          modifier = Modifier
              .fillMaxSize()
              .safeDrawingPadding()
      ) {
          Button(
              onClick = {},
              modifier = Modifier.align(Alignment.BottomCenter)
          ) {
              Text("ログイン")
          }
      }
      
    3. 深くネストされたコンポーネントでパディングが過剰な場合は、WindowInsetsRulers(例: Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current))を使用します。コードサンプルについてはIMEセクションを参照してください。

    4. カスタムヘッダーや装飾的なスクリムなどの要素をシステムバーと同じ寸法にする必要がある場合は、インセットサイズ修飾子(例: Modifier.windowInsetsTopHeight(WindowInsets.systemBars))を使用します。コードサンプルについてはリストセクションを参照してください。

アダプティブScaffold

  • NavigationSuiteScaffoldは、NavigationRailやNavigationBarなどの自身のコンポーネントのセーフエリアを管理します。ただし、アダプティブScaffold(例: NavigationSuiteScaffold、ListDetailPaneScaffold)はPaddingValuesを内部のコンテンツに伝播しません。ステップ3で説明されているように、個別の画面またはコンポーネント(例: リストのcontentPaddingやFABのパディング)にインセットを適用する必要があります。NavigationSuiteScaffoldの親にsafeDrawingPaddingや類似の修飾子を適用しないでください。これによりクリッピングが発生し、エッジ トゥ エッジ画面が妨げられます。

IME

  • ソフトキーボードを使用する各Activityについて、AndroidManifest.xmlでandroid:windowSoftInputMode="adjustResize"が設定されていることを確認します。SOFT_INPUT_ADJUST_RESIZEは非推奨のため使用しないでください。次に、入力フィールドにフォーカスを維持します。いずれかを選択:
      1. 推奨: コンテンツコンテナにModifier.fitInside(WindowInsetsRulers.Ime.current)を追加します。これはimePadding()よりも優先されます。なぜなら、階層の上流でインセットを消費し忘れることによるガタつきや余分なパディングを減らせるからです。
      1. コンテンツコンテナにimePaddingを追加します。パディング修飾子はModifier.verticalScroll()の前に配置する必要があります。親がcontentWindowInsets(例: contentWindowInsets = WindowInsets.safeDrawing)で既にIMEを考慮している場合は、Modifier.imePadding()を使用しないでください。二重パディングの原因になります。

Scaffoldを使用したIMEのコードパターン

正しい

contentWindowInsetsにIMEインセットが含まれており、innerPaddingとしてcontentラムダに渡されるため正しいです。

// 正しい
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .verticalScroll(rememberScrollState())
    ) { /* コンテンツ */ }
}

fitInsideがcontentWindowInsetsに関係なくIMEインセットにコンテンツを適合させるため正しいです。

// 正しい
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .fitInside(WindowInsetsRulers.Ime.current)
            .verticalScroll(rememberScrollState())
    ) { /* コンテンツ */ }
}

デフォルトのcontentWindowInsetsにはIMEインセットが含まれておらず、imePadding()がIMEインセットを適用するため正しいです:

// 正しい
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .imePadding()
            .verticalScroll(rememberScrollState())
    ) { /* コンテンツ */ }
}

誤り

IMEが開いたときに余分なパディングが発生するため誤りです。IMEインセットが2回適用されます。1回目は渡されたcontentWindowInsetsに含まれるIMEインセットを持つinnerPadding、2回目はimePaddingです:

// 誤り
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .imePadding()
            .verticalScroll(rememberScrollState())
    ) { /* コンテンツ */ }
}

IMEがコンテンツを覆い隠すため誤りです。ScaffoldのデフォルトのcontentWindowInsetsにはIMEインセットが含まれていません。

// 誤り
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .verticalScroll(rememberScrollState())
    ) { /* コンテンツ */ }
}

Scaffoldを使用しないIMEのコードパターン

正しい

以下のコードサンプルでは過剰なパディングは発生しません。

// 正しい
Box(
    // インセット消費済み
    modifier = Modifier.safeDrawingPadding() // または imePadding(), safeContentPadding(), safeGesturesPadding()
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* コンテンツ */ }
}

// 正しい
Box(
    // インセット消費済み
    modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // または WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* コンテンツ */ }
}

// 正しい
Box(
    // インセットは消費されないが、fitInsideにより問題なし
    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // または WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .fitInside(WindowInsetsRulers.Ime.current)
    ) { /* コンテンツ */ }
}

誤り

以下のコードサンプルではIMEインセットが2回適用されるため過剰なパディングが発生します:

// 誤り
Box(
    // インセットは消費されない
    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // または WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* コンテンツ */ }
}

ナビゲーションバーのコントラストとシステムバーアイコン

  • ActivityがWindowCompatのenableEdgeToEdgeを使用する場合、ライトテーマとダークテーマをサポートするアプリでは、システムバーアイコンを視認可能にするためにisAppearanceLightNavigationBarsとisAppearanceLightStatusBarsをデバイステーマの逆に設定する必要があります。テーマファイルでこれを行うことを推奨します。ActivityがComponentActivityのenableEdgeToEdgeを使用する場合はこれを行わないでください。アイコンの色を自動的に処理するためです。

    // `WindowCompat`の`enableEdgeToEdge`を呼び出す場合のみ使用します。
    // テーマファイルに適用してください。
    @Composable
    fun MyTheme(
        darkTheme: Boolean = isSystemInDarkTheme(),
        content: @Composable () -> Unit
    ) {
        val view = LocalView.current
        if (!view.isInEditMode) {
            SideEffect {
                val window = (view.context as? Activity)?.window ?: return@SideEffect
                val controller = WindowCompat.getInsetsController(window, view)
    
                // ライトモードではダークアイコン、ダークモードではライトアイコン
                controller.isAppearanceLightStatusBars = !darkTheme
                controller.isAppearanceLightNavigationBars = !darkTheme
            }
        }
    
        MaterialTheme(content = content)
    }
    
  • 画面がScaffoldまたはNavigationSuiteScaffoldとボトムバー(例: BottomAppBar、NavigationBar)を使用する場合、対応するActivityでwindow.isNavigationBarContrastEnforced = falseをSDK 29+に設定します。これにより、システムがナビゲーションバーに半透明の背景を追加するのを防ぎ、ボトムバーの色が画面の下部まで拡張されることを確認します。

リスト

  • インセットパディング(ScaffoldのinnerPaddingなど)をスクロール可能なコンポーネント(例: LazyColumn、LazyRow)のcontentPaddingパラメータに適用します。リストの親コンテナにModifier.padding()として適用しないでください。コンテンツをクリッピングし、システムバーの背後でスクロールできなくなるためです。
  • システムバーを覆う半透明のコンポーザブルを作成して、アイコンが視認可能な状態を保ちます。
class SystemBarProtectionSnippets : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // enableEdgeToEdge は window.isNavigationBarContrastEnforced = true を設定します
        // これは3ボタンナビゲーションに半透明のスクリムを追加するために使用されます
        enableEdgeToEdge()

        setContent {
            MyTheme {
                // メインコンテンツ
                MyContent()

                // メインコンテンツの描画後、ステータスバーの保護を描画
                StatusBarProtection()
            }
        }
    }
}

@Composable
private fun StatusBarProtection(
    color: Color = MaterialTheme.colorScheme.surfaceContainer,
) {
    Spacer(
        modifier = Modifier
            .fillMaxWidth()
            .height(
                with(LocalDensity.current) {
                    (WindowInsets.statusBars.getTop(this) * 1.2f).toDp()
                }
            )
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        color.copy(alpha = 1f),
                        color.copy(alpha = 0.8f),
                        Color.Transparent
                    )
                )
            )
    )
}

ダイアログ

以下の2つの条件が両方とも真の場合、ダイアログは全画面となり、エッジ トゥ エッジ対応にする必要があります:

  1. DialogPropertiesにusePlatformDefaultWidth = falseが含まれている。
  2. ダイアログがModifier.fillMaxSize()を呼び出している。

全画面ダイアログをエッジ トゥ エッジ対応にするには、DialogPropertiesでdecorFitsSystemWindows = falseを設定します。

Dialog(
    onDismissRequest = { /* 却下を処理 */ },
    properties = DialogProperties(
        // 1. ダイアログが画面の全幅に広がることを許可
        usePlatformDefaultWidth = false,
        // 2. ダイアログがステータスバーとナビゲーションバーの背後に描画されることを許可
        decorFitsSystemWindows = false
    )
) { /* コンテンツ */ }

チェックリスト

  • すべてのActivityがenableEdgeToEdge()を呼び出していますか?
  • AndroidManifest.xmlにadjustResizeが設定されていますか?
  • すべてのTextField、OutlinedTextField、BasicTextFieldに、imePadding()、fitInside、Modifier.safeDrawingPadding()、Modifier.safeContentPadding()、Modifier.safeGesturesPadding()を持つ親、またはcontentWindowInsetsがWindowInsets.safeDrawingもしくはWindowInsets.imeに設定された親がありますか?
  • 最初と最後のリストアイテムが、contentPaddingにインセットを渡すことでシステムバーから離れて描画されていますか?
  • FABはScaffold内に配置されるか、Modifier.safeDrawingPadding()を適用することでナビゲーションバーの上に描画されていますか?
  • プロジェクトはビルドできますか? ./gradlew buildを実行して確認してください。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

lerchenflo/claudeskills

Upgrades, or migrates, an Android project to use Android Gradle Plugin (AGP) version 9. Do not use this skill for migrating Kotlin Multiplatform (KMP) projects.

lerchenflo/claudeskills

Provides instructions for installing and using the `android` CLI. The `android` command-line tool is a critical tool for Android development and helps you create new Android projects, run Android apps on devices, manage and interact with Android virtual devices (including screenshots and UI inspection), manage Android SDK components, look up official Android documentation, and discover and install official Android skills.

lerchenflo/claudeskills

Manages Android performance profiling and debugging. Triggers when the user asks to record or analyze Android performance data, such as system traces, heap dumps, method recordings, callstack samples, memory allocations, or investigate bottlenecks, jank, memory leaks, and app startup issues on Android, or when the user asks to write, debug, or execute ad-hoc SQL queries. Applies to both user and system apps or services.

lerchenflo/claudeskills

Analyzes Android apps to identify key user workflows for AppFunctions such as creating a note, playing media, or sending an automated or AI agent triggered message, voice commands, or system shortcuts, without needing to open the app UI. Generates Kotlin code to expose these workflows to the Android system, allowing agents to discover and execute them on-device. Also refines KDoc documentation to ensure AI agents correctly understand and use the provided functionality.

関連スキル