mcpField
X
xml-to-compose-migration

xml-to-compose-migration

github.com/new-silvermoon開発992432 回インストール13 週間前に更新
リポジトリ

xml-to-compose-migration について

Android XML レイアウトを機能を保ちながら、慣用的な Jetpack Compose に移行するスキルです。全面的な書き換えや段階的な導入を計画する Android 開発者向けに、レイアウト対応、状態移行、相互運用、検証を扱います。

主な機能

  1. 01XML レイアウトから Compose への対応
  2. 02状態とイベントの移行
  3. 03XML と Compose の段階的な相互運用
  4. 04一般的なウィジェットの変換パターン
  5. 05アクセシビリティと表示の検証

活用シーン

  1. 01XML 画面の Compose への置き換え
  2. 02RecyclerView の LazyColumn への移行
  3. 03既存 XML アプリへの Compose の組み込み
  4. 04移行後の UI と状態の検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill xml-to-compose-migration

インストール後、Claude Code で/xml-to-compose-migrationを入力して読み込まれたか確認します。

詳細

XML から Compose への移行

概要

Android XML レイアウトを、Compose のパターンを活用しながら機能を維持して、慣用的な Jetpack Compose に体系的に変換します。このスキルでは、レイアウトの対応付け、状態の移行、段階的な導入戦略について説明します。

ワークフロー

1. XML レイアウトを分析する
  • ルートレイアウトの種類(ConstraintLayout、LinearLayout、FrameLayout など)を特定します。
  • すべての View ウィジェットと主要な属性を一覧にします。
  • データバインディング式(@{})またはビュー バインディング参照を対応付けます。
  • 特別な処理が必要なカスタムビューを特定します。
  • include、merge、ViewStub の使用箇所を確認します。
2. 移行を計画する
  • 全面的な書き換えにするか、ComposeView/AndroidView を使用した段階的な移行にするかを決めます。
  • 状態のソース(ViewModel、LiveData、savedInstanceState)を特定します。
  • 個別の Composable として抽出する再利用可能なコンポーネントを一覧にします。
  • Navigation コンポーネントを使用している場合は、ナビゲーションとの統合を計画します。
3. レイアウトを変換する

以下のレイアウト対応表を使用して、各 View を対応する Compose 要素に変換します。

4. 状態を移行する
  • LiveData の監視を StateFlow の収集または observeAsState() に変換します。
  • findViewById/ViewBinding を Compose の状態に置き換えます。
  • クリックリスナーをラムダパラメーターに変換します。
5. テストして確認する
  • XML 版と Compose 版の表示結果を比較します。
  • アクセシビリティ(コンテンツの説明、タッチターゲット)をテストします。
  • 構成変更後も状態が保持されることを確認します。

レイアウト対応表

コンテナレイアウト
XML レイアウトCompose の対応要素備考
LinearLayout (vertical)ColumnArrangement と Alignment を使用します
LinearLayout (horizontal)RowArrangement と Alignment を使用します
FrameLayoutBox子要素を重ねて配置します
ConstraintLayoutConstraintLayout (Compose)createRefs() と constrainAs を使用します
RelativeLayoutBox または ConstraintLayout単純な重なりには Box を推奨します
ScrollViewColumn + Modifier.verticalScroll()リストには LazyColumn も使用できます
HorizontalScrollViewRow + Modifier.horizontalScroll()リストには LazyRow も使用できます
RecyclerViewLazyColumn / LazyRow / LazyGrid最も一般的な移行パターンです
ViewPager2HorizontalPageraccompanist または Compose Foundation のものを使用します
CoordinatorLayoutカスタム要素 + Scaffoldスクロール動作を設定した TopAppBar を使用します
NestedScrollViewColumn + Modifier.verticalScroll()Lazy 系の要素を推奨します
一般的なウィジェット
XML ウィジェットCompose の対応要素備考
TextViewTextstyle を TextStyle に対応付けます
EditTextTextField / OutlinedTextField状態のホイスティングが必要です
ButtonButtononClick ラムダを使用します
ImageViewImagepainterResource() または Coil を使用します
ImageButtonIconButton内部で Icon を使用します
CheckBoxCheckboxchecked と onCheckedChange が必要です
RadioButtonRadioButtonグループには Row と組み合わせて使用します
SwitchSwitch状態のホイスティングが必要です
ProgressBar (circular)CircularProgressIndicator
ProgressBar (horizontal)LinearProgressIndicator
SeekBarSlider状態のホイスティングが必要です
SpinnerDropdownMenu + ExposedDropdownMenuBoxより複雑なパターンです
CardViewCardMaterial 3 のものを使用します
ToolbarTopAppBarScaffold 内で使用します
BottomNavigationViewNavigationBarMaterial 3
FloatingActionButtonFloatingActionButtonScaffold 内で使用します
DividerHorizontalDivider / VerticalDivider
SpaceSpacerModifier.size() を使用します
属性のマッピング
XML 属性Compose の修飾子/プロパティ
android:layout_width="match_parent"Modifier.fillMaxWidth()
android:layout_height="match_parent"Modifier.fillMaxHeight()
android:layout_width="wrap_content"Modifier.wrapContentWidth()(通常は暗黙的に適用されます)
android:layout_weightModifier.weight(1f)
android:paddingModifier.padding()
android:layout_margin親の Modifier.padding()、または Arrangement.spacedBy() を使用します
android:backgroundModifier.background()
android:visibility="gone"条件付きコンポジション(出力しません)
android:visibility="invisible"Modifier.alpha(0f)(スペースを保持します)
android:clickableModifier.clickable { }
android:contentDescriptionModifier.semantics { contentDescription = "" }
android:elevationModifier.shadow() またはコンポーネントの elevation パラメーター
android:alphaModifier.alpha()
android:rotationModifier.rotate()
android:scaleX/YModifier.scale()
android:gravityAlignment パラメーターまたは Arrangement
android:layout_gravityModifier.align()

一般的なパターン

重みを使った LinearLayout

<LinearLayout android:orientation="horizontal">
    <View android:layout_weight="1" />
    <View android:layout_weight="2" />
</LinearLayout>
// Compose
Row(modifier = Modifier.fillMaxWidth()) {
    Box(modifier = Modifier.weight(1f))
    Box(modifier = Modifier.weight(2f))
}
RecyclerView から LazyColumn への移行

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
// Compose
LazyColumn(modifier = Modifier.fillMaxSize()) {
    items(items, key = { it.id }) { item ->
        ItemRow(item = item, onClick = { onItemClick(item) })
    }
}
双方向バインディングを使った EditText

<EditText
    android:text="@={viewModel.username}"
    android:hint="@string/username_hint" />
// Compose
val username by viewModel.username.collectAsState()

OutlinedTextField(
    value = username,
    onValueChange = { viewModel.updateUsername(it) },
    label = { Text(stringResource(R.string.username_hint)) },
    modifier = Modifier.fillMaxWidth()
)
ConstraintLayout の移行

<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:id="@+id/title"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
    <TextView
        android:id="@+id/subtitle"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="@id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>
// Compose
ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
    val (title, subtitle) = createRefs()
    
    Text(
        text = "Title",
        modifier = Modifier.constrainAs(title) {
            top.linkTo(parent.top)
            start.linkTo(parent.start)
        }
    )
    Text(
        text = "Subtitle", 
        modifier = Modifier.constrainAs(subtitle) {
            top.linkTo(title.bottom)
            start.linkTo(title.start)
        }
    )
}
Include/Merge から Composable の抽出へ

<merge>
    <ImageView android:id="@+id/avatar" />
    <TextView android:id="@+id/name" />
</merge>


<include layout="@layout/layout_header" />
// Compose: Extract as a reusable Composable
@Composable
fun HeaderSection(
    avatarUrl: String,
    name: String,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier) {
        AsyncImage(model = avatarUrl, contentDescription = null)
        Text(text = name)
    }
}

// Usage
HeaderSection(avatarUrl = user.avatar, name = user.name)

段階的な移行(相互運用)

XML への Compose の埋め込み

<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
// In Fragment/Activity
binding.composeView.setContent {
    MaterialTheme {
        MyComposable()
    }
}
Compose への XML View の埋め込み
// Use AndroidView for Views that don't have Compose equivalents
@Composable
fun MapViewComposable(modifier: Modifier = Modifier) {
    AndroidView(
        factory = { context ->
            MapView(context).apply {
                // Initialize the view
            }
        },
        update = { mapView ->
            // Update the view when state changes
        },
        modifier = modifier
    )
}

状態の移行

LiveData から Compose への移行
// Before: Observing in Fragment
viewModel.uiState.observe(viewLifecycleOwner) { state ->
    binding.title.text = state.title
}

// After: Collecting in Compose
@Composable
fun MyScreen(viewModel: MyViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
    Text(text = uiState.title)
}
クリックリスナー
// Before: XML + setOnClickListener
binding.submitButton.setOnClickListener {
    viewModel.submit()
}

// After: Lambda in Compose
Button(onClick = { viewModel.submit() }) {
    Text("Submit")
}

チェックリスト

  • すべてのレイアウトを変換済み(include や merge が残っていない)
  • 状態を適切にホイスト済み(ユーザー入力に内部の可変状態を使用していない)
  • クリックハンドラーをラムダに変換済み
  • RecyclerView アダプターを削除済み(LazyColumn/LazyRow を使用)
  • ViewBinding/DataBinding を削除済み
  • ナビゲーションを統合済み(NavHost または相互運用)
  • テーマを適用済み(MaterialTheme)
  • アクセシビリティを維持済み(コンテンツの説明、タッチ領域)
  • 開発用のプレビューアノテーションを追加済み
  • 古い XML ファイルを削除済み

参考資料

コメント