Sheet是一种UI组件,出现在屏幕底部主内容之上,提供额外的操作或信息。Bottom Sheet是最常见的类型,在Material Design 3(Google,2024)指南中定义。与对话框不同,Sheet不会完全阻止与背景的交互,并支持折叠和展开状态。理解Sheet的类型和行为对于在移动应用程序中构建便捷的导航至关重要。
要点
Sheet是一种表面UI组件,从屏幕底部弹出并显示额外内容或操作,不会将用户带离当前屏幕。与全屏过渡不同,Sheet保留上下文:用户可以看到背景屏幕并理解Sheet从何处出现。
Sheet的架构基于一个可改变高度的容器。在最小状态(折叠)下,只有部分内容可见——通常是一个标题或小面板。用户可以向上拖动Sheet以展开它,或向下滑动以关闭它。这种行为类似于从一堆纸下面抽出一张物理纸张——因此得名。
Google在Material Design 3(2024)指南中强调Sheet使用的两个关键场景:显示主工具栏上无法容纳的额外操作,以及在不切换到单独屏幕的情况下显示表单或详细信息。在iOS上,Apple从iOS 15开始通过UISheetPresentationController添加了对Sheet的系统支持,这证实了该模式的普适性。
Material Design定义了三种类型的Bottom Sheet,每种都有其自己的行为和应用领域。选择正确的类型直接影响用户体验和平台指南的符合性。
标准Bottom Sheet是一种非模态组件:用户可以在不关闭Sheet的情况下与背景屏幕交互。例如,当用户在后台窗口中浏览产品列表时,可以在Bottom Sheet中看到过滤器并在不关闭面板的情况下更改它们。Sheet根据内容调整其高度——从屏幕的30%到90%。
标准Bottom Sheet不阻止背景并允许与主内容交互。这是与模态Sheet的主要区别,模态Sheet需要用户执行必需操作才能继续工作。当额外信息对主要场景不关键时,请使用标准Sheet。
模态Bottom Sheet阻止与背景屏幕的交互,直到用户在Sheet内执行操作。此类Sheet始终以展开状态显示并需要明确关闭——通过按钮、操作或向下滑动。Material Design推荐将模态Sheet用于登录表单、确认和关键设置。
在Android上,模态Bottom Sheet通过BottomSheetDialogFragment实现,它会自动阻止背景。在iOS上,UISheetPresentationController的模态变体使用.mediumDetent参数提供类似行为。仅当上下文需要用户注意时才应使用模态Sheet。
扩展Bottom Sheet结合了标准和模态的属性:它以非模态形式从折叠状态开始,但在向上拖动时变为模态。实际上,这意味着用户可以首先快速浏览内容,然后决定——展开Sheet进行详细研究或关闭它。
此模式在地图和导航器中特别流行,底部面板显示关于路线点的简短信息,向上滑动时展开显示详细信息。Google Maps正是使用这种扩展的Sheet来显示关于地点和路线的信息。
| Sheet类型 | 背景阻止 | 初始状态 | 示例 |
|---|---|---|---|
| 标准 | 否 | 折叠 | 目录中的过滤器 |
| 模态 | 是 | 展开 | 登录表单 |
| 扩展 | 部分 | 折叠 | 地点卡片 |
Android SDK提供了多个API来实现Bottom Sheet,从经典的View方法到现代的Jetpack Compose。适用于Android的Material Components Library包含BottomSheetDialogFragment和BottomSheetBehavior的现成实现。
BottomSheetBehavior是来自Material Components的一个类,可以附加到任何View并管理其在屏幕上的位置。开发人员设置三种状态:STATE_COLLAPSED(折叠)、STATE_EXPANDED(展开)和STATE_HIDDEN(隐藏)。Behavior自动处理拖拽手势和动画。
val sheet = findViewById<View>(R.id.bottom_sheet)
val behavior = BottomSheetBehavior.from(sheet)
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
behavior.peekHeight = 200
Jetpack Compose通过Material 3中的ModalBottomSheet组件提供更声明式的API。与View方法不同,Compose中的Bottom Sheet是一个接收状态和内容的composable函数。Sheet在拖拽时自动动画化显示、隐藏和高度变化。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FilterSheet(onDismiss: () -> Unit) {
ModalBottomSheet(onDismissRequest = onDismiss) {
Text("Filters")
Button(onClick = onDismiss) { Text("Apply") }
}
}
BottomSheetDialogFragment是一个专门的DialogFragment,以Bottom Sheet形式显示。它自动处理模态性、生命周期和配置更改。开发人员只需重写onCreateView并返回Sheet布局。根据Google文档(Android Developers,2024),BottomSheetDialogFragment是经典Android中模态Bottom Sheet的推荐方式。
Apple从iOS 15开始通过UISheetPresentationController引入了Sheet的系统支持。在此之前,开发人员通过自定义过渡或第三方库手动实现Bottom Sheet。Apple的原生Sheet支持detents——预定义的展开级别。
UISheetPresentationController是一个内置的呈现控制器,将View Controller显示为Sheet。开发人员设置一个detents数组——Sheet可以停止的高度值。iOS 16添加了具有任意高度的自定义detents。Apple建议使用medium(.medium)和large(.large)detents作为主要选择。
let viewController = MySheetViewController()
if let sheet = viewController.sheetPresentationController {
sheet.detents = [.medium, .large]
sheet.prefersGrabberVisible = true
sheet.preferredCornerRadius = 16
}
present(viewController, animated: true)
SwiftUI提供了.sheet修饰符,将Sheet显示绑定到状态。当published变量变为true时,SwiftUI自动显示Sheet。SwiftUI还通过.presentationDetents修饰符支持detents,该修饰符在iOS 16中添加。
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") { showSheet = true }
.sheet(isPresented: $showSheet) {
FilterView()
.presentationDetents([.medium, .large])
}
}
}
跨平台框架也支持Bottom Sheet,将行为适应目标平台。Flutter和React Native提供了具有不同灵活性的内置和自定义实现。
Flutter SDK包含两种用于显示Bottom Sheet的方法:showBottomSheet(标准)和showModalBottomSheet(模态)。Flutter中的Material Design遵循与Android中相同的原则——Sheet从底部出现,支持拖拽和可配置高度。对于具有detents的扩展行为,使用DraggableScrollableSheet。
Scaffold.of(context).showBottomSheet((context) {
return Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text("Sheet Content"),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: Text("Close")
)
]
)
);
})
React Native在其核心中没有内置的Bottom Sheet组件,因此开发人员使用第三方库。最流行的是@gorhom/bottom-sheet(在GitHub上有超过7000颗星,2024)。它提供了一个手势驱动的Bottom Sheet,支持吸附点、动画和固定标题。该库使用Reanimated 2和Gesture Handler编写,实现60 FPS的性能。
正确使用Bottom Sheet需要遵循平台指南并理解Sheet适用的场景。让我们看看设计Sheet时的关键建议和常见错误。
Bottom Sheet最适用于相对于屏幕上主内容而言是附加的操作。过滤器、排序、选项选择、关于元素的简短信息——典型场景。Material Design建议将Sheet用于不应占据整个屏幕但比Action Sheet或Popup Menu需要更多空间的工具。
不要将Bottom Sheet用于关键警告或错误——对话框更适合这些场景。避免使用包含大量需要滚动输入字段的Sheet:用户难以触及键盘并失去上下文。如果Sheet包含超过七个操作元素,请考虑使用单独的屏幕而不是Sheet。
典型错误是将peekHeight(折叠状态下的高度)设置为屏幕的40%以上。用户无法理解他们看到的是整个Sheet还是可以展开它。Material Design建议peekHeight在屏幕高度的15-30%范围内,使内容的显著部分保持隐藏从而鼓励拖拽。
在iOS上,用户期望Sheet可以从任何位置向下滑动关闭。如果您禁用了此手势(通过UISheetPresentationController.prefersEdgeAttachedInCompactHeight),用户尝试以常规方式关闭Sheet时会感到沮丧。Apple Human Interface Guidelines(2024)强调滑动关闭是iOS用户的基本期望。
常见问题解答
Bottom Sheet不会完全阻止背景并支持拖拽,而对话框是模态的并需要必需操作。Bottom Sheet更适合附加选项,对话框用于关键确认。
可以,但需要在Android清单中设置adjustResize或adjustPan。在iOS上,键盘会自动抬起Sheet。Flutter和React Native需要通过MediaQuery进行手动处理以正确定位。
推荐在标准Sheet中不超过5-7个元素。如果内容更多——使用滚动或按类别划分。Material Design建议在展开状态下不要超过屏幕高度的90%。
对于模态Sheet,在Android上设置setCancelable(true)或在iOS上设置isModalInPresentation = false。在Jetpack Compose中,onDismissRequest自动处理点击背景。在SwiftUI中,向下滑动是默认行为。
支持,所有原生实现都支持VoiceOver和TalkBack。确保FocusManager在打开时正确将焦点移动到Sheet并在关闭时返回。自定义实现需要手动设置无障碍功能。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。