mcpField
X
xml-to-compose-migration

xml-to-compose-migration

github.com/new-silvermoon开发992432 次安装13 周前更新
代码仓库

关于 xml-to-compose-migration

此迁移技能帮助 Android 开发者在保留功能的同时,将 XML 布局转换为符合 Jetpack Compose 惯例的实现。内容涵盖布局映射、状态迁移、渐进式互操作和验证。

主要功能

  1. 01XML 布局到 Compose 的映射
  2. 02状态与事件迁移
  3. 03XML 和 Compose 渐进式互操作
  4. 04常见控件转换模式
  5. 05无障碍与视觉验证

适用场景

  1. 01将 XML 页面替换为 Compose
  2. 02把 RecyclerView 迁移为 LazyColumn
  3. 03在现有 XML 应用中嵌入 Compose
  4. 04验证迁移后的界面和状态

安装

复制下面的命令,在终端中运行。

$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 布局转换为符合惯用写法的 Jetpack Compose,同时保留功能并采用 Compose 模式。本技能涵盖布局映射、状态迁移和渐进式采用策略。

工作流

1. 分析 XML 布局
  • 确定根布局类型(ConstraintLayout、LinearLayout、FrameLayout 等)。
  • 列出所有 View 控件及其关键属性。
  • 映射数据绑定表达式(@{})或 View Binding 引用。
  • 确定需要特殊处理的自定义 View。
  • 记下是否使用了 include、merge 或 ViewStub。
2. 规划迁移
  • 决定采用:完全重写还是渐进式迁移(使用 ComposeView/AndroidView)。
  • 确定状态来源(ViewModel、LiveData、savedInstanceState)。
  • 列出要提取为独立 Composable 的可复用组件。
  • 如果使用 Navigation 组件,规划导航集成方式。
3. 转换布局

使用下方的布局映射表,将每个 View 转换为对应的 Compose 元素。

4. 迁移状态
  • 将 LiveData 观察转换为 StateFlow 收集或 observeAsState()。
  • 使用 Compose 状态替代 findViewById/ViewBinding。
  • 将点击监听器转换为 lambda 参数。
5. 测试与验证
  • 对比 XML 和 Compose 版本的视觉效果。
  • 测试无障碍功能(内容描述、触控目标)。
  • 验证状态在配置变更后是否得以保留。

布局映射参考

容器布局
XML 布局Compose 对应项说明
LinearLayout (vertical)Column使用 Arrangement 和 Alignment
LinearLayout (horizontal)Row使用 Arrangement 和 Alignment
FrameLayoutBox子项彼此叠放
ConstraintLayoutConstraintLayout (Compose)使用 createRefs() 和 constrainAs
RelativeLayoutBox 或 ConstraintLayout简单重叠时优先使用 Box
ScrollViewColumn + Modifier.verticalScroll()或使用 LazyColumn 显示列表
HorizontalScrollViewRow + Modifier.horizontalScroll()或使用 LazyRow 显示列表
RecyclerViewLazyColumn / LazyRow / LazyGrid最常见的迁移方式
ViewPager2HorizontalPager来自 accompanist 或 Compose Foundation
CoordinatorLayout自定义实现 + Scaffold配合滚动行为使用 TopAppBar
NestedScrollViewColumn + Modifier.verticalScroll()优先使用 Lazy 系列组件
常见控件
XML 控件Compose 对应项说明
TextViewText使用 style → TextStyle
EditTextTextField / OutlinedTextField需要状态提升
ButtonButton使用 onClick lambda
ImageViewImage使用 painterResource() 或 Coil
ImageButtonIconButton内部使用 Icon
CheckBoxCheckbox需要 checked 和 onCheckedChange
RadioButtonRadioButton在分组中配合 Row 使用
SwitchSwitch需要状态提升
ProgressBar (circular)CircularProgressIndicator
ProgressBar (horizontal)LinearProgressIndicator
SeekBarSlider需要状态提升
SpinnerDropdownMenu + ExposedDropdownMenuBox模式较复杂
CardViewCard来自 Material 3
ToolbarTopAppBar在 Scaffold 内使用
BottomNavigationViewNavigationBarMaterial 3
FloatingActionButtonFloatingActionButton在 Scaffold 内使用
DividerHorizontalDivider / VerticalDivider
SpaceSpacer使用 Modifier.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)
  • 已正确提升状态(用户输入没有内部可变状态)
  • 已将点击处理器转换为 lambda
  • 已移除 RecyclerView 适配器(改用 LazyColumn/LazyRow)
  • 已移除 ViewBinding/DataBinding
  • 已集成导航(NavHost 或互操作)
  • 已应用主题(MaterialTheme)
  • 已保留无障碍支持(内容描述、触控目标)
  • 已添加用于开发的 Preview 注解
  • 已删除旧的 XML 文件

参考资料

评论

支持 Markdown