xml-to-compose-migration by new-silvermoon/awesome-android-agent-skills
npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill xml-to-compose-migration系统性地将 Android XML 布局转换为地道的 Jetpack Compose,在保留功能的同时采用 Compose 模式。本技能涵盖布局映射、状态迁移和增量采用策略。
ConstraintLayout、LinearLayout、FrameLayout 等)。@{})或视图绑定引用。include、merge 或 ViewStub 的使用。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
ComposeViewAndroidView应用下面的布局映射表,将每个视图转换为其对应的 Compose 等价物。
LiveData 观察转换为 StateFlow 收集或 observeAsState()。findViewById / ViewBinding。| XML 布局 | Compose 等价物 | 备注 |
|---|---|---|
LinearLayout (vertical) | Column | 使用 Arrangement 和 Alignment |
LinearLayout (horizontal) | Row | 使用 Arrangement 和 Alignment |
FrameLayout | Box | 子项相互堆叠 |
ConstraintLayout | ConstraintLayout (Compose) | 使用 createRefs() 和 constrainAs |
RelativeLayout | Box 或 ConstraintLayout | 简单重叠优先使用 Box |
ScrollView | Column + Modifier.verticalScroll() | 对于列表,或使用 LazyColumn |
HorizontalScrollView | Row + Modifier.horizontalScroll() | 对于列表,或使用 LazyRow |
RecyclerView | LazyColumn / LazyRow / LazyGrid | 最常见的迁移 |
ViewPager2 | HorizontalPager | 来自 accompanist 或 Compose Foundation |
CoordinatorLayout | 自定义 + Scaffold | 使用具有滚动行为的 TopAppBar |
NestedScrollView | Column + Modifier.verticalScroll() | 优先使用 Lazy 变体 |
| XML 控件 | Compose 等价物 | 备注 |
|---|---|---|
TextView | Text | 使用 style → TextStyle |
EditText | TextField / OutlinedTextField | 需要状态提升 |
Button | Button | 使用 onClick lambda |
ImageView | Image | 使用 painterResource() 或 Coil |
ImageButton | IconButton | 内部使用 Icon |
CheckBox | Checkbox | 需要 checked + onCheckedChange |
RadioButton | RadioButton | 与 Row 一起用于分组 |
Switch | Switch | 需要状态提升 |
ProgressBar (circular) | CircularProgressIndicator | |
ProgressBar (horizontal) | LinearProgressIndicator | |
SeekBar | Slider | 需要状态提升 |
Spinner | DropdownMenu + ExposedDropdownMenuBox | 更复杂的模式 |
CardView | Card | 来自 Material 3 |
Toolbar | TopAppBar | 在 Scaffold 内部使用 |
BottomNavigationView | NavigationBar | Material 3 |
FloatingActionButton | FloatingActionButton | 在 Scaffold 内部使用 |
Divider | HorizontalDivider / VerticalDivider | |
Space | Spacer | 使用 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_weight | Modifier.weight(1f) |
android:padding | Modifier.padding() |
android:layout_margin | 在父级上使用 Modifier.padding(),或使用 Arrangement.spacedBy() |
android:background | Modifier.background() |
android:visibility="gone" | 条件组合(不发射) |
android:visibility="invisible" | Modifier.alpha(0f) (保留空间) |
android:clickable | Modifier.clickable { } |
android:contentDescription | Modifier.semantics { contentDescription = "" } |
android:elevation | Modifier.shadow() 或组件的 elevation 参数 |
android:alpha | Modifier.alpha() |
android:rotation | Modifier.rotate() |
android:scaleX/Y | Modifier.scale() |
android:gravity | Alignment 参数或 Arrangement |
android:layout_gravity | Modifier.align() |
<!-- XML -->
<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))
}
<!-- XML -->
<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) })
}
}
<!-- XML with Data Binding -->
<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()
)
<!-- XML -->
<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)
}
)
}
<!-- XML: layout_header.xml -->
<merge>
<ImageView android:id="@+id/avatar" />
<TextView android:id="@+id/name" />
</merge>
<!-- Usage -->
<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)
<!-- In your XML layout -->
<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()
}
}
// 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
)
}
// 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)每周安装次数
118
仓库
GitHub 星标数
552
首次出现
2026年1月27日
安全审计
安装于
opencode101
codex99
gemini-cli87
github-copilot84
claude-code83
kimi-cli79
Systematically convert Android XML layouts to idiomatic Jetpack Compose, preserving functionality while embracing Compose patterns. This skill covers layout mapping, state migration, and incremental adoption strategies.
ConstraintLayout, LinearLayout, FrameLayout, etc.).@{}) or view binding references.include, merge, or ViewStub usage.ComposeView/AndroidView).Apply the layout mapping table below to convert each View to its Compose equivalent.
LiveData observation to StateFlow collection or observeAsState().findViewById / ViewBinding with Compose state.| XML Layout | Compose Equivalent | Notes |
|---|---|---|
LinearLayout (vertical) | Column | Use Arrangement and Alignment |
LinearLayout (horizontal) | Row | Use Arrangement and Alignment |
| XML Widget | Compose Equivalent | Notes |
|---|---|---|
TextView | Text | Use style → TextStyle |
EditText | TextField / OutlinedTextField | Requires state hoisting |
Button |
| XML Attribute | Compose Modifier/Property |
|---|---|
android:layout_width="match_parent" | Modifier.fillMaxWidth() |
android:layout_height="match_parent" | Modifier.fillMaxHeight() |
android:layout_width="wrap_content" | Modifier.wrapContentWidth() (usually implicit) |
android:layout_weight |
<!-- XML -->
<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))
}
<!-- XML -->
<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) })
}
}
<!-- XML with Data Binding -->
<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()
)
<!-- XML -->
<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)
}
)
}
<!-- XML: layout_header.xml -->
<merge>
<ImageView android:id="@+id/avatar" />
<TextView android:id="@+id/name" />
</merge>
<!-- Usage -->
<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)
<!-- In your XML layout -->
<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()
}
}
// 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
)
}
// 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 or merge left)Weekly Installs
118
Repository
GitHub Stars
552
First Seen
Jan 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode101
codex99
gemini-cli87
github-copilot84
claude-code83
kimi-cli79
Kotlin Exposed ORM 模式指南:DSL查询、DAO、事务管理与生产配置
1,000 周安装
FrameLayout | Box | Children stack on top of each other |
ConstraintLayout | ConstraintLayout (Compose) | Use createRefs() and constrainAs |
RelativeLayout | Box or ConstraintLayout | Prefer Box for simple overlap |
ScrollView | Column + Modifier.verticalScroll() | Or use LazyColumn for lists |
HorizontalScrollView | Row + Modifier.horizontalScroll() | Or use LazyRow for lists |
RecyclerView | LazyColumn / LazyRow / LazyGrid | Most common migration |
ViewPager2 | HorizontalPager | From accompanist or Compose Foundation |
CoordinatorLayout | Custom + Scaffold | Use TopAppBar with scroll behavior |
NestedScrollView | Column + Modifier.verticalScroll() | Prefer Lazy variants |
Button |
Use onClick lambda |
ImageView | Image | Use painterResource() or Coil |
ImageButton | IconButton | Use Icon inside |
CheckBox | Checkbox | Requires checked + onCheckedChange |
RadioButton | RadioButton | Use with Row for groups |
Switch | Switch | Requires state hoisting |
ProgressBar (circular) | CircularProgressIndicator |
ProgressBar (horizontal) | LinearProgressIndicator |
SeekBar | Slider | Requires state hoisting |
Spinner | DropdownMenu + ExposedDropdownMenuBox | More complex pattern |
CardView | Card | From Material 3 |
Toolbar | TopAppBar | Use inside Scaffold |
BottomNavigationView | NavigationBar | Material 3 |
FloatingActionButton | FloatingActionButton | Use inside Scaffold |
Divider | HorizontalDivider / VerticalDivider |
Space | Spacer | Use Modifier.size() |
Modifier.weight(1f) |
android:padding | Modifier.padding() |
android:layout_margin | Modifier.padding() on parent, or use Arrangement.spacedBy() |
android:background | Modifier.background() |
android:visibility="gone" | Conditional composition (don't emit) |
android:visibility="invisible" | Modifier.alpha(0f) (keeps space) |
android:clickable | Modifier.clickable { } |
android:contentDescription | Modifier.semantics { contentDescription = "" } |
android:elevation | Modifier.shadow() or component's elevation param |
android:alpha | Modifier.alpha() |
android:rotation | Modifier.rotate() |
android:scaleX/Y | Modifier.scale() |
android:gravity | Alignment parameter or Arrangement |
android:layout_gravity | Modifier.align() |