如果您的应用界面是基于 View 系统,您可能不想一次全部重写整个界面。本页将帮助您向现有界面中添加新的 Compose 元素。
迁移共享界面
如果您要逐步迁移到 Compose,可能需要在 Compose 和 View 系统中都使用共享界面元素。例如,如果您的应用具有自定义 CallToActionButton
组件,您可能需要在 Compose 和基于 View 的屏幕中都使用它。
在 Compose 中,共享界面元素成为可在整个应用中重复使用的可组合项,无论元素是采用 XML 进行的样式设计还是一个自定义视图。例如,您将为自定义号召性用语 Button
组件创建 CallToActionButton
可组合项。
为了在基于 View 的屏幕中使用可组合项,您需要创建一个从 AbstractComposeView
扩展的自定义视图封装容器。在该容器被替换的 Content
可组合项中,将您创建的可组合项封装在 Compose 主题中,如下例所示:
@Composable fun CallToActionButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ), onClick = onClick, modifier = modifier, ) { Text(text) } } class CallToActionViewButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var text by mutableStateOf("") var onClick by mutableStateOf({}) @Composable override fun Content() { YourAppTheme { CallToActionButton(text, onClick) } } }
请注意,可组合项参数在自定义视图中会成为可变变量。这会使自定义 CallToActionViewButton
视图在使用视图绑定等功能时变得可膨胀且可以使用,像传统视图一样。请参见下面的示例:
class ViewBindingActivity : ComponentActivity() { private lateinit var binding: ActivityExampleBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) binding.callToAction.apply { text = getString(R.string.greeting) onClick = { /* Do something */ } } } }
如果自定义组件包含可变状态,请参阅状态可信来源部分。
迁移应用主题
Material Design 是推荐用于为 Android 应用设置主题的设计系统。
对于基于 View 的应用,可以使用三个 Material 版本:
- 使用 AppCompat 库(即
Theme.AppCompat.*
)的 Material Design 1 - 使用 MDC-Android 库(即
Theme.MaterialComponents.*
)的 Material Design 2 - 使用 MDC-Android 库(即
Theme.Material3.*
)的 Material Design 3
对于 Compose 应用,可以使用两个 Material 版本:
- 使用 Compose Material 库(即
androidx.compose.material.MaterialTheme
)的 Material Design 2 - 使用 Compose Material 3 库(即
androidx.compose.material3.MaterialTheme
)的 Material Design 3
如果应用的设计系统符合要求,建议您使用最新版本 - Material 3。View 和 Compose 都有相应的迁移指南:
- 在 View 中从 Material 1 迁移到 Material 2
- 在 View 中从 Material 2 迁移到 Material 3
- 在 Compose 中从 Material 2 迁移到 Material 3
在 Compose 中创建新界面时,无论您使用的是哪个版本的 Material Design,都请确保先应用 MaterialTheme
,然后再应用任何从 Compose Material 库发出界面的可组合项。Material 组件(Button
、Text
等)依赖于现有的 MaterialTheme
,如果没有 MaterialTheme,这些组件的行为将处于未定义状态。
所有 Jetpack Compose 示例都使用基于 MaterialTheme
构建的自定义 Compose 主题。
如需了解详情,请参阅 Compose 中的设计系统和将 XML 主题迁移到 Compose。
WindowInsets 和 IME 动画
从 Compose 1.2.0 开始,您可以在布局中使用修饰符处理 WindowInsets
。IME 动画也受支持。
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MaterialTheme {
MyScreen()
}
}
}
}
@Composable
fun MyScreen() {
Box {
LazyColumn(
modifier = Modifier
.fillMaxSize() // fill the entire window
.imePadding() // padding for the bottom for the IME
.imeNestedScroll(), // scroll IME at the bottom
content = { }
)
FloatingActionButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp) // normal 16dp of padding for FABs
.navigationBarsPadding() // padding for navigation bar
.imePadding(), // padding for when IME appears
onClick = { }
) {
Icon( /* ... */)
}
}
}
图 2. IME 动画
优先考虑将状态与呈现分开
过去,View
是有状态的。View
管理的字段用于描述要显示的内容以及显示方式。将 View
转换为 Compose 时,需要将正在渲染的数据隔离开以实现单向数据流,状态提升中对此进行了详细说明。
例如,View
具有 visibility
属性,用于描述该 View 是可见、不可见还是已消失。这是 View
固有的属性。虽然其他代码可能会改变 View
的可见性,但只有 View
本身知道它当前的真实可见性。用于确保 View
可见的逻辑很容易出错,并且通常与 View
本身相关联。
相比之下,Compose 通过使用 Kotlin 中的条件逻辑,可以轻松显示完全不同的可组合项:
if (showCautionIcon) {
CautionIcon(/* ... */)
}
根据设计,CautionIcon
不需要知道或关心其显示的原因,也没有 visibility
的概念:它要么在组合中,要么不在。
通过将状态管理与内容呈现逻辑完全分开,您能够以状态转换的形式更自由地更改将内容显示到界面的方式。能够在需要时提升状态还会提高可组合项的可重用性,因为状态所有权更灵活。
提升封装组件和可重用组件
View
元素通常对自己所处位置有所感知:在 Activity
、Dialog
、Fragment
内或另一个 View
层次结构中的某个位置。由于 View
通常是从静态布局文件膨胀而来,因此其整体结构往往非常严格。这会使耦合更紧密,并且使 View
更难以更改或重复使用。
例如,自定义 View
可能假定它具有某种类型的子视图(具有特定 ID),并直接根据某项操作更改其属性。这使得这些 View
元素紧密耦合在一起:如果自定义 View
找不到子级,则可能会发生崩溃或损坏;而如果没有自定义 View
父级,子级可能会无法重复使用。
这在具有可重用可组合项的 Compose 中则不是什么问题。父级可以轻松指定状态和回调,因此可以编写可重用可组合项,而不必知道它们具体将被用在哪里。
var isEnabled by rememberSaveable { mutableStateOf(false) }
Column {
ImageWithEnabledOverlay(isEnabled)
ControlPanelWithToggle(
isEnabled = isEnabled,
onEnabledChanged = { isEnabled = it }
)
}
在上面的示例中,所有三个部分封装程度更高,但耦合程度更低:
ImageWithEnabledOverlay
只需知道当前的isEnabled
状态,而不需知道ControlPanelWithToggle
的存在,甚至不需要知道如何控制它。ControlPanelWithToggle
不知道ImageWithEnabledOverlay
的存在。isEnabled
可能以零种、一种或多种方式显示,而ControlPanelWithToggle
无需更改。对父级而言,
ImageWithEnabledOverlay
或ControlPanelWithToggle
的嵌套深度无关紧要。这些子项的目的可能是为变化添加动画效果、换出内容或将内容传递给其他子项。
此模式称为“控制反转”,CompositionLocal
文档中对此做了更详细的介绍。
处理屏幕尺寸的变化
针对不同尺寸的窗口提供不同的资源是创建自适应 View
布局的主要方式之一。虽然在确定屏幕级别的布局时仍可选择使用限定资源,但 Compose 可让您使用常规条件逻辑完全在代码中更改布局,从而更轻松地实现此目的。如需了解详情,请参阅支持不同的屏幕尺寸。
此外,如需了解 Compose 提供了哪些技术来构建自适应界面,请参阅构建自适应布局。
使用 View 实现嵌套滚动
如需详细了解如何在可滚动的 View 元素与可滚动的可组合项之间实现嵌套滚动互操作(相互嵌套),请仔细阅读嵌套滚动互操作性。
Compose 在 RecyclerView 中的运用
自 RecyclerView 版本 1.3.0-alpha02 发布以来,RecyclerView 中的可组合项性能一直非常出色。请确保您使用的 RecyclerView 版本不低于 1.3.0-alpha02,以便切实感受这些好处。