在 Android 开发中,如何高效地封装页面布局并处理点击事件?本文通过实战演示,教你如何实现类似 Flutter 中 Scaffold
的高复用、灵活页面结构。我们将从基类封装入手,结合观察者模式、委托模式和 Lambda 表达式,优化布局初始化与点击事件处理逻辑。同时,针对不同项目规模,探讨是否需要引入第三方库,并推荐 Jetpack Navigation 和 Material Components 等实用工具。无论你是初学者还是资深开发者,本文都将为你提供一套完整的、可落地的 Android 页面封装方案,助你提升开发效率与代码质量。
完整优化封装方案
1. 基类封装
基类负责初始化页面结构和注册点击事件,但不直接处理点击逻辑。点击事件的处理通过监听器、委托或 Lambda 表达式实现。
1.1 基类代码
abstract class BaseScaffoldActivity : AppCompatActivity() {
// 点击事件监听器
private var onDrawerItemSelected: ((Int) -> Unit)? = null
private var onBottomNavigationItemSelected: ((Int) -> Unit)? = null
private var onFabClicked: (() -> Unit)? = null
// 设置监听器
fun setOnDrawerItemSelectedListener(listener: (Int) -> Unit) {
this.onDrawerItemSelected = listener
}
fun setOnBottomNavigationItemSelectedListener(listener: (Int) -> Unit) {
this.onBottomNavigationItemSelected = listener
}
fun setOnFabClickListener(listener: () -> Unit) {
this.onFabClicked = listener
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_base_scaffold)
// 初始化组件
setupDrawer()
setupBottomNavigation()
setupFloatingActionButton()
}
// 设置侧边栏
private fun setupDrawer() {
val navigationView = findViewById<NavigationView>(R.id.navigationView)
navigationView.setNavigationItemSelectedListener { menuItem ->
onDrawerItemSelected?.invoke(menuItem.itemId)
true
}
}
// 设置底部导航栏
private fun setupBottomNavigation() {
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
bottomNavigationView.setOnNavigationItemSelectedListener { menuItem ->
onBottomNavigationItemSelected?.invoke(menuItem.itemId)
true
}
}
// 设置悬浮按钮
private fun setupFloatingActionButton() {
val fab = findViewById<FloatingActionButton>(R.id.floatingActionButton)
fab.setOnClickListener {
onFabClicked?.invoke()
}
}
}
2. 布局文件
基类的布局文件定义了通用的页面结构,包括 Toolbar
、DrawerLayout
、BottomNavigationView
和 FloatingActionButton
。
2.1 布局文件 (activity_base_scaffold.xml
)
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 页面主体内容 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 顶部工具栏 -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:title="My App" />
<!-- 页面内容容器 -->
<FrameLayout
android:id="@+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- 底部导航栏 -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_navigation_menu" />
</LinearLayout>
<!-- 侧边栏 -->
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/drawer_menu" />
<!-- 悬浮按钮 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_add" />
</androidx.drawerlayout.widget.DrawerLayout>
3. 子类使用
子类可以通过 Lambda 表达式、监听器接口 或 委托类 处理点击事件。
3.1 使用 Lambda 表达式
class MainActivity : BaseScaffoldActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 Lambda 设置点击事件
setOnDrawerItemSelectedListener { itemId ->
when (itemId) {
R.id.nav_item_1 -> {
// 处理侧边栏点击事件
}
R.id.nav_item_2 -> {
// 处理侧边栏点击事件
}
}
}
setOnFabClickListener {
// 处理悬浮按钮点击事件
}
}
}
3.2 使用监听器接口
class MainActivity : BaseScaffoldActivity(), OnDrawerItemSelectedListener, OnFabClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 注册监听器
setOnDrawerItemSelectedListener(this)
setOnFabClickListener(this)
}
override fun onDrawerItemSelected(itemId: Int) {
when (itemId) {
R.id.nav_item_1 -> {
// 处理侧边栏点击事件
}
R.id.nav_item_2 -> {
// 处理侧边栏点击事件
}
}
}
override fun onFabClicked() {
// 处理悬浮按钮点击事件
}
}
3.3 使用委托类
class MainActivity : BaseScaffoldActivity() {
private val drawerItemDelegate = DrawerItemDelegate()
private val fabClickDelegate = FabClickDelegate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 绑定委托
setOnDrawerItemSelectedListener(drawerItemDelegate::onDrawerItemSelected)
setOnFabClickListener(fabClickDelegate::onFabClicked)
}
}
4. 委托类实现
将点击事件逻辑封装到独立的委托类中,提升代码复用性。
4.1 侧边栏点击事件委托
class DrawerItemDelegate {
fun onDrawerItemSelected(itemId: Int) {
when (itemId) {
R.id.nav_item_1 -> {
// 处理侧边栏点击事件
}
R.id.nav_item_2 -> {
// 处理侧边栏点击事件
}
}
}
}
4.2 悬浮按钮点击事件委托
class FabClickDelegate {
fun onFabClicked() {
// 处理悬浮按钮点击事件
}
}
5. 是否需要引用库
5.1 不需要引用库
- 适用场景:小型项目或对性能要求较高的场景。
- 优点:轻量、可控、学习成本低。
5.2 需要引用库
- 适用场景:中大型项目或需要快速实现复杂功能的场景。
- 推荐库:
- Jetpack Navigation:页面导航。
- Material Components:Material Design 组件。
- Hilt:依赖注入。
- Coil:图片加载。
5.3 示例:结合 Jetpack Navigation
dependencies {
implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.navigation:navigation-fragment-ktx:2.7.5"
implementation "androidx.navigation:navigation-ui-ktx:2.7.5"
}
在基类中集成 Navigation:
abstract class BaseScaffoldActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_base_scaffold)
// 初始化 Navigation
val navHostFragment = supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHostFragment.navController
// 设置 Toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
// 设置底部导航栏
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
bottomNavigationView.setupWithNavController(navController)
}
}
6. 总结
通过以上优化,我们实现了以下目标:
- 职责分离:基类只负责初始化页面结构和注册点击事件,点击逻辑由子类、监听器或委托类处理。
- 灵活性:支持多种方式处理点击事件(Lambda、监听器、委托)。
- 复用性:通用的点击事件逻辑可以封装到委托类中,供多个页面复用。
根据项目需求选择合适的方案,推荐结合 Jetpack Navigation 和 Material Components 使用,以提升开发效率。