Drawer(Navigation Drawer)——一个交互式面板,从屏幕左侧(或右侧)边缘滑出并显示导航链接。在 Android 上通过 DrawerLayout 实现,在 iOS 上通过自定义解决方案(如 SideMenu)或 iPad 上的标准 UISplitViewController 实现。有关更多规范详情,请阅读 Material Design 3 文档。
要点
Drawer(Navigation Drawer)——一种导航元素,以面板形式从屏幕左侧边缘通过滑动手势或点击“汉堡”图标(三条水平线)滑出。Drawer 包含导航链接、标题和可选元素:用户头像、通知计数器、分隔线。面板可以覆盖内容(Modal)或推移内容(Standard)。
Material Design 3(2026)将 Navigation Drawer 定义为适用于具有 6+ 个导航点的应用程序的组件。与 Bottom Navigation 不同,Drawer 不占用屏幕上的固定位置,仅在用户请求时显示。这释放了内容空间,但需要额外的操作才能打开。Google 建议将 Drawer 用于具有深层部分层次结构的应用程序。
UX Planet(2025)的研究表明:Drawer 适用于具有 5-15 个部分的应用程序,但导航时间比 Bottom Navigation 高 40%。用户会错过屏幕折叠线(滚动)下方的菜单项。因此,重要的部分应位于 Drawer 的顶部,而不太重要的部分(关于应用、设置)应位于底部,用分隔线隔开。
Modal Drawer——滑出覆盖内容,使背景变暗。用于屏幕宽度有限的移动设备。选择项目后,Drawer 自动关闭。Standard Drawer(Permanent)——一个固定面板,始终可见,推移内容。用于屏幕宽度 900 dp 以上的平板电脑和桌面设备。Material 3 还定义了 Rail Drawer——适用于平板电脑的紧凑版本,仅显示图标。
| 类型 | 行为 | 用途 |
|---|---|---|
| Modal | 覆盖内容,使背景变暗 | 手机,临时导航 |
| Standard | 推移内容,固定 | 平板电脑,桌面(900+ dp) |
| Rail | 带图标的紧凑侧面板 | 平板电脑,自适应导航 |
DrawerLayout——Android 中实现滑动侧边菜单的标准 ViewGroup。DrawerLayout 包含两个子元素:第一个——主要内容(带有 NavHostFragment 的 FrameLayout),第二个——带有菜单的 NavigationView。DrawerLayout 处理滑动手势和通过 ActionBarDrawerToggle 进行的程序化打开/关闭。
// 使用 DrawerLayout 和 NavigationView 的 Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var drawerLayout: DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
drawerLayout = binding.drawerLayout
val navView = binding.navView
val navController = findNavController(R.id.nav_host_fragment)
// 将 Drawer 连接到 Navigation Component
NavigationUI.setupActionBarWithNavController(
this, navController, drawerLayout
)
NavigationUI.setupWithNavController(navView, navController)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(
findNavController(R.id.nav_host_fragment),
drawerLayout
)
}
}
// XML: activity_main.xml
// <androidx.drawerlayout.widget.DrawerLayout
// android:id="@+id/drawerLayout"
// ...>
// <!-- 主要内容 -->
// <FrameLayout android:id="@+id/nav_host_fragment"
// android:layout_width="match_parent"
// android:layout_height="match_parent" />
// <!-- Navigation Drawer -->
// <com.google.android.material.navigation.NavigationView
// android:id="@+id/navView"
// android:layout_width="wrap_content"
// android:layout_height="match_parent"
// android:layout_gravity="start"
// app:menu="@menu/nav_menu" />
// </androidx.drawerlayout.widget.DrawerLayout>NavigationView——用于 Drawer 内容的 Material Design 组件。支持标题(headerLayout)、菜单项(menu)、副标题、复选框和标志。app:menu 属性指向包含导航点的 XML 菜单。NavigationUI 自动突出显示活动项并与 NavController 同步状态。
iOS 没有内置的“Drawer”组件——Apple 不建议在 iPhone 上使用侧边菜单(HIG,2026)。然而,对于 iPad,使用带有 primary 和 secondary 面板的 UISplitViewController。对于 iPhone,侧边菜单通过第三方库(SideMenu、SwiftUI-side-menu)或带有内容偏移的自定义解决方案实现。在 SwiftUI 中,侧边菜单通过带有偏移动画的 ZStack 创建。
import SwiftUI
struct DrawerView<Content: View, Menu: View>: View {
let menuWidth: CGFloat = 280
@Binding var isOpen: Bool
@ViewBuilder let content: Content
@ViewBuilder let menu: Menu
var body: some View {
ZStack(alignment: .leading) {
content
.offset(x: isOpen ? menuWidth : 0)
.animation(.easeInOut, value: isOpen)
if isOpen {
menu
.frame(width: menuWidth)
.transition(.move(edge: .leading))
Color.black.opacity(0.3)
.ignoresSafeArea()
.onTapGesture { isOpen = false }
}
}
}
}
// 使用
struct ContentView: View {
@State private var showDrawer = false
var body: some View {
DrawerView(isOpen: $showDrawer) {
NavigationStack {
HomeView()
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { showDrawer.toggle() }) {
Image(systemName: "line.horizontal.3")
}
}
}
}
} menu: {
DrawerMenuView()
}
}
}对于 iOS 的生产项目,经常使用 SideMenu 库(GitHub,8k+ 星标)。它提供现成的 UIViewController,支持手势、背景模糊、自定义宽度和动画。SideMenu 支持左侧和右侧菜单、多个层级以及通过 UINavigationController 与 UIKit 集成。替代方案——iPad 上的 UISplitViewController(系统级,无自定义动画)。
Drawer 允许广泛的自定义。在 Android 中,NavigationView 支持 headerLayout——放置在菜单项上方的自定义布局。其中放置了头像、用户名、电子邮件和封面。菜单项通过 <menu> 进行分组,包含副标题、分隔线和标志。在 iOS 中,SideMenu 的自定义包括设置背景颜色、模糊、宽度和圆角。
// NavigationView 的自定义 header
val headerView = binding.navView.inflateHeaderView(R.layout.nav_header)
headerView.findViewById<ImageView>(R.id.iv_avatar)
.setImageResource(R.drawable.avatar)
headerView.findViewById<TextView>(R.id.tv_name)
.text = "John Doe"
// 配置菜单项
val menuItem = binding.navView.menu.findItem(R.id.nav_notifications)
menuItem.isChecked = true
// 添加徽章
val badge = binding.navView.getOrCreateBadge(R.id.nav_notifications)
badge.number = 15
badge.isVisible = trueMaterial Design 建议以下 Drawer 结构:标题(姓名、头像)、主要导航项(5-8)、分隔线、次要项(设置、帮助)、页脚(关于应用、版本)。在 Material 3 中,NavigationView 还支持通过 itemShape 和 itemBackground 自定义活动项的突出显示。
Drawer 对于具有 6+ 个部分、设置和帮助页面的应用程序有效。不适用于快速访问部分至关重要的应用程序——在这种情况下,Bottom Navigation 更好。Material Design 建议将 Drawer 与 Bottom Navigation 结合使用:主导航——Bottom(3-5 个标签),附加导航——Drawer(设置、帮助、关于应用)。
实现 Drawer 的主要错误:菜单过载(15+ 项未分组)、重要部分隐藏在折叠线下方、缺少图标(无图标的文本扫描速度慢 30%)、动画延迟(应为 ≤ 200 ms)。最佳实践:带有用户名的标题、每项都有图标、组之间的分隔线、活动部分突出显示、支持滑动手势打开。
常见问题解答
Drawer——通过手势打开的隐藏侧边菜单,适用于 6+ 个部分和深层层次结构。Bottom Navigation——始终可见的底部面板,适用于 3-5 个部分。Drawer 节省屏幕空间,但需要额外的操作才能打开,这会使导航时间增加 40%。
SwiftUI 没有内置的 Drawer。使用 ZStack 配合内容偏移和动画,如上例所示。生产环境使用库:SideMenu(Swift Package)或围绕 UISplitViewController 的自定义封装。在 iPad 上使用 preferredDisplayMode 为 .oneBesideSecondary 的 UISplitViewController。
Apple HIG(2026)指出:侧边菜单隐藏内容并违反直接操作原则。iOS 建议使用 Tab Bar 进行导航。Drawer 在 iPhone 上仅用于设置和附加操作,不用于主导航。在 iPad 上,Drawer 被系统标准的 UISplitViewController 取代。
在 Android 中,DrawerLayout 自动处理从左侧边缘的滑动。在 iOS 中,自定义 Drawer 需要 UIPanGestureRecognizer 或 Edge Pan Gesture(SwiftUI——带有 DragGesture 的 .gesture)。重要提示:滑动可能与系统性的“返回”手势冲突——在 iOS 上,左侧边缘被系统手势占用。
可以。在 Android 中,为 NavigationView 设置 android:layout_gravity="end"。在 iOS 中,使用 edge: .trailing 创建菜单。右侧 Drawer 较少使用——用于附加选项、过滤器或聊天。主导航根据 Material Design 和用户期望保持在左侧。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。