Slot API 详解
Slot API 是 Jetpack Compose 中一种重要的设计模式,直译为「插槽式 API」,其核心思想是在组件内部预留一个或多个可插入内容的“空位”(即 Slots),允许父组件通过参数传递自定义内容。这种方式彻底颠覆了传统 UI 开发中通过继承或复杂配置定制组件的行为,大幅提升了灵活性和复用性。
1. Slot API 的核心思想
- 类比现实场景:想象一个相框(父组件),它本身只负责边框样式和固定照片的位置,而具体放哪张照片(子内容)由用户决定。这里的“相框”就是通过 Slot API 预留了一个插槽。
-
技术本质:
- 父组件通过
@Composable
函数参数(通常是 Lambda 表达式)接收子内容。 - 子内容可以是任意 Composable 组件,甚至是复杂的布局。
- 父组件仅控制子内容的布局位置或基础样式,不限制具体实现。
- 父组件通过
2. 经典示例解析
示例 1:Scaffold 的 Slot 设计
Scaffold(
// 预留的多个 Slots
topBar = { TopAppBar(title = { Text("首页") }) }, // Slot 1:顶部栏
floatingActionButton = { FloatingActionButton(onClick = {}) { Icon(...) } }, // Slot 2:悬浮按钮
content = { innerPadding -> // Slot 3:主要内容区域
LazyColumn(Modifier.padding(innerPadding)) { ... }
}
)
-
Slot 作用:
-
topBar
:允许插入自定义的顶部栏(如TopAppBar
或完全自定义布局)。 -
floatingActionButton
:定义悬浮按钮的位置和基础行为。 -
content
:主要内容区域,自动适应其他 Slot 的占位空间。
-
示例 2:自定义 Card 组件
@Composable
fun CustomCard(
header: @Composable () -> Unit, // Slot 1:头部
content: @Composable () -> Unit // Slot 2:内容主体
) {
Card {
Column {
Box(Modifier.background(Color.LightGray)) { header() }
Spacer(Modifier.height(8.dp))
content()
}
}
}
// 使用示例
CustomCard(
header = { Text("标题", style = MaterialTheme.typography.h6) }, // 自定义头部
content = { Text("这里是卡片内容...") } // 自定义内容
)
-
优势:通过
header
和content
两个 Slots,将卡片的布局结构与具体内容完全解耦。
3. 与传统设计的对比
传统实现方式(继承或配置参数)
// 传统 Android 中自定义一个带标题的 CardView
public class TitleCardView extends CardView {
private TextView titleView;
public void setTitle(String text) {
titleView.setText(text);
}
public void setContent(View view) {
// 将 view 添加到内容区域
}
}
-
痛点:
- 需要预先定义所有可能的配置方法(如
setTitle
)。 - 内容类型受限(例如
setContent
只能接受View
对象)。 - 扩展性差,新增功能需修改父类。
- 需要预先定义所有可能的配置方法(如
Compose Slot API 实现
@Composable
fun TitleCard(
title: @Composable () -> Unit, // Slot 1:标题(允许任意组件)
content: @Composable () -> Unit // Slot 2:内容(允许任意组件)
) {
Card {
Column {
Box(Modifier.padding(8.dp)) { title() }
Divider()
content()
}
}
}
// 使用示例:标题可以是图标+文字,内容可以是网格布局
TitleCard(
title = {
Row {
Icon(Icons.Filled.Star, "星标")
Text("高级功能")
}
},
content = {
LazyVerticalGrid(...) { ... }
}
)
-
优势:
- 内容完全开放,支持任意 Composable。
- 无需预先定义配置方法,扩展性极强。
4. Slot API 的常见应用场景
场景 1:基础组件扩展
-
Button:通过
content: @Composable () -> Unit
允许插入图标+文字组合。 - AlertDialog:自定义标题、正文和按钮区域。
场景 2:复杂容器
- ModalDrawer:定义抽屉的头部、主体和底部内容。
- NavigationRail:允许插入多个导航项和尾部组件。
场景 3:高度可复用的业务组件
@Composable
fun UserProfileCard(
avatar: @Composable () -> Unit, // Slot 1:用户头像
username: @Composable () -> Unit, // Slot 2:用户名
actions: @Composable RowScope.() -> Unit // Slot 3:操作按钮(支持 Row 布局)
) {
Card {
Row {
Box(Modifier.size(48.dp)) { avatar() }
Column {
username()
Row(horizontalArrangement = Arrangement.End) { actions() }
}
}
}
}
5. Slot API 的设计原则
- 最小化预设:父组件只控制必要的布局或样式,不限制子内容的具体实现。
-
类型灵活性:Slot 参数应尽可能通用,如使用
@Composable () -> Unit
。 -
命名语义化:通过参数名明确 Slot 用途(如
header
、footer
)。 -
作用域控制:使用
@Composable (RowScope.() -> Unit)
限制子内容的作用域(如只能在 Row 内使用weight
)。
6. 深入:Slot API 的底层实现
Slot API 本质上是 Compose 编译器对 Lambda 表达式的处理优化。当父组件调用子内容时:
// 父组件定义
@Composable
fun Parent(content: @Composable () -> Unit) {
content() // 调用子内容
}
// 使用处
Parent {
Text("Hello")
}
- 编译器优化:Compose 会将 Lambda 转换为一个可重组的节点,父组件的重组不会影响子内容,除非输入参数变化。
总结:为什么 Slot API 是 Compose 的核心?
- 解耦性:分离容器与内容,避免继承链的臃肿。
- 灵活性:像拼图一样自由组合 UI。
- 可维护性:每个 Slot 独立变化,减少代码冲突。
练习建议:尝试将项目中某个传统自定义 View 改写为 Compose 组件,并使用 Slot API 设计至少两个可配置的内容区域。
网友评论