Android 页面封装实战:打造高复用、灵活的 Scaffold 式布局与事件处理

发布于:2025-03-14 ⋅ 阅读:(16) ⋅ 点赞:(0)

在 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. 布局文件

基类的布局文件定义了通用的页面结构,包括 ToolbarDrawerLayoutBottomNavigationViewFloatingActionButton

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. 总结

通过以上优化,我们实现了以下目标:

  1. 职责分离:基类只负责初始化页面结构和注册点击事件,点击逻辑由子类、监听器或委托类处理。
  2. 灵活性:支持多种方式处理点击事件(Lambda、监听器、委托)。
  3. 复用性:通用的点击事件逻辑可以封装到委托类中,供多个页面复用。

根据项目需求选择合适的方案,推荐结合 Jetpack Navigation 和 Material Components 使用,以提升开发效率。