UI层封装方案.md 15 KB

UI 层封装方案

一、设计目标

将 UI 层的通用功能封装成基类和扩展函数,统一处理公共逻辑,业务层只需简单调用,减少重复代码。

二、架构设计

2.1 现有组件

  • BaseActivity - 基础 Activity 类(已存在)
  • BaseFragment - 基础 Fragment 类(已存在)
  • ActivityExtensions - Activity 扩展函数(已存在)
  • FragmentExtensions - Fragment 扩展函数(已存在)

2.2 核心原则

  • 职责分离:UI 层只负责显示,不处理网络请求
  • MVVM 架构:网络请求在 ViewModel 中处理,Activity 只观察状态
  • 统一封装:通用 UI 功能统一封装,减少重复代码
  • 易于扩展:子类可重写方法自定义实现

三、目录结构

base-common/src/main/java/com/narutohuo/xindazhou/common/
└── ui/                      # UI 层封装
    ├── BaseActivity.kt       # 基础 Activity 类
    ├── BaseFragment.kt      # 基础 Fragment 类
    ├── ActivityExtensions.kt    # Activity 扩展函数
    └── FragmentExtensions.kt    # Fragment 扩展函数

四、功能说明

4.1 BaseActivity - 基础 Activity 类

职责:封装 Activity 通用 UI 功能

功能

  • ViewBinding 支持:自动处理 ViewBinding 初始化
  • 统一的加载状态管理showLoading() / hideLoading()
  • 统一的错误提示showError() / showSuccess()
  • 生命周期管理:自动调用 initView()initObserver()
  • 网络请求封装executeRequest() 方法(可选,简单场景使用)

使用方式

class LoginActivity : BaseActivity<ActivityLoginBinding>() {
    
    private val viewModel: LoginViewModel by viewModels()
    
    override fun getViewBinding(): ActivityLoginBinding {
        return ActivityLoginBinding.inflate(layoutInflater)
    }
    
    override fun initView() {
        binding.btnLogin.setOnClickListener {
            viewModel.login(mobile, password)
        }
    }
    
    override fun initObserver() {
        observeStateFlow(viewModel.loginState) { state ->
            when (state) {
                is LoginState.Loading -> showLoading()
                is LoginState.Success -> {
                    hideLoading()
                    showSuccess(state.message)
                }
                is LoginState.Error -> {
                    hideLoading()
                    showError(state.message)
                }
            }
        }
    }
}

4.2 BaseFragment - 基础 Fragment 类

职责:封装 Fragment 通用 UI 功能

功能

  • ViewBinding 支持:自动处理 ViewBinding 初始化
  • 统一的加载状态管理showLoading() / hideLoading()
  • 统一的错误提示showError() / showSuccess()
  • 生命周期管理:自动调用 initView()initObserver()
  • 网络请求封装executeRequest() 方法(可选,简单场景使用)

使用方式

class LoginFragment : BaseFragment<FragmentLoginBinding>() {
    
    private val viewModel: LoginViewModel by viewModels()
    
    override fun getViewBinding(
        inflater: LayoutInflater,
        container: ViewGroup?
    ): FragmentLoginBinding {
        return FragmentLoginBinding.inflate(inflater, container, false)
    }
    
    override fun initView() {
        binding.btnLogin.setOnClickListener {
            viewModel.login(mobile, password)
        }
    }
    
    override fun initObserver() {
        observeStateFlow(viewModel.loginState) { state ->
            when (state) {
                is LoginState.Loading -> showLoading()
                is LoginState.Success -> {
                    hideLoading()
                    showSuccess(state.message)
                }
                is LoginState.Error -> {
                    hideLoading()
                    showError(state.message)
                }
            }
        }
    }
}

4.3 ActivityExtensions - Activity 扩展函数

职责:提供便捷的扩展函数,简化业务层代码

功能

  • 观察 StateFlowobserveStateFlow() 简化状态观察

使用方式

// 之前:繁琐
override fun initObserver() {
    lifecycleScope.launch {
        viewModel.loginState.collect { state ->
            when (state) { ... }
        }
    }
}

// 现在:简洁
override fun initObserver() {
    observeStateFlow(viewModel.loginState) { state ->
        when (state) { ... }
    }
}

4.4 FragmentExtensions - Fragment 扩展函数

职责:提供便捷的扩展函数,简化业务层代码

功能

  • 观察 StateFlowobserveStateFlow() 简化状态观察

使用方式:同 ActivityExtensions

五、完整 MVVM 架构流程

5.1 数据流向

用户操作(点击按钮)
    ↓
Activity/Fragment(UI 层)
    ↓ 调用
ViewModel(业务逻辑层)
    ↓ 调用
Repository(数据管理层)
    ↓ 调用
RemoteDataSource(网络请求层)
    ↓ 返回数据
Repository
    ↓ 返回数据
ViewModel(更新 UI 状态)
    ↓ 状态变化
Activity/Fragment(观察状态,调用 BaseActivity 方法更新 UI)

5.2 完整示例

1. ViewModel 层(处理业务逻辑)

// LoginViewModel.kt
class LoginViewModel(
    application: Application,
    private val repository: AuthRepository
) : AndroidViewModel(application) {
    
    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
    val loginState: StateFlow<LoginState> = _loginState
    
    fun login(mobile: String, password: String) {
        viewModelScope.launch {
            _loginState.value = LoginState.Loading
            
            repository.login(mobile, password)
                .onSuccess {
                    _loginState.value = LoginState.Success("登录成功")
                }
                .onFailure { e ->
                    _loginState.value = LoginState.Error(e.message ?: "登录失败")
                }
        }
    }
}

2. Activity 层(只负责显示)

// LoginActivity.kt
class LoginActivity : BaseActivity<ActivityLoginBinding>() {
    
    private val viewModel: LoginViewModel by viewModels()
    
    override fun getViewBinding(): ActivityLoginBinding {
        return ActivityLoginBinding.inflate(layoutInflater)
    }
    
    override fun initView() {
        binding.btnLogin.setOnClickListener {
            val mobile = binding.etMobile.text.toString()
            val password = binding.etPassword.text.toString()
            viewModel.login(mobile, password)  // 触发 ViewModel
        }
    }
    
    override fun initObserver() {
        // 使用扩展函数观察状态
        observeStateFlow(viewModel.loginState) { state ->
            when (state) {
                is LoginState.Loading -> showLoading()      // BaseActivity 提供
                is LoginState.Success -> {
                    hideLoading()
                    showSuccess(state.message)              // BaseActivity 提供
                    // 跳转到主页
                }
                is LoginState.Error -> {
                    hideLoading()
                    showError(state.message)                // BaseActivity 提供
                }
            }
        }
    }
}

六、核心功能详解

6.1 ViewBinding 支持

自动处理

  • BaseActivity 自动初始化 binding
  • BaseFragment 自动初始化 binding
  • 子类只需实现 getViewBinding() 方法

优势

  • ✅ 不需要 findViewById
  • ✅ 类型安全
  • ✅ 自动空检查

6.2 加载状态管理

方法

  • showLoading() - 显示加载提示(子类可重写自定义 UI)
  • hideLoading() - 隐藏加载提示(子类可重写自定义 UI)

使用场景

  • 网络请求开始时显示加载
  • 网络请求结束时隐藏加载

6.3 错误提示管理

方法

  • showError(message: String) - 显示错误提示(默认 Toast,子类可重写)
  • showSuccess(message: String) - 显示成功提示(默认 Toast,子类可重写)

使用场景

  • 网络请求失败时显示错误
  • 操作成功时显示成功提示

6.4 状态观察(扩展函数)

方法

  • observeStateFlow(stateFlow, action) - 观察 StateFlow 状态变化

优势

  • ✅ 简化代码:不需要每次都写 lifecycleScope.launch { flow.collect { } }
  • ✅ 自动生命周期管理:Activity/Fragment 销毁时自动取消观察
  • ✅ 一行代码搞定状态观察

6.5 网络请求封装(可选)

方法

  • executeRequest(request, onSuccess, onError, showLoading) - 统一执行网络请求

使用场景

  • 简单业务场景,不需要 ViewModel
  • 直接调用 Repository 的场景

注意

  • ⚠️ 推荐使用 ViewModel 方式(符合 MVVM 架构)
  • ⚠️ 简单场景可以使用此方法

七、使用示例

7.1 推荐方式:使用 ViewModel(MVVM 架构)

class LoginActivity : BaseActivity<ActivityLoginBinding>() {
    
    private val viewModel: LoginViewModel by viewModels()
    
    override fun getViewBinding(): ActivityLoginBinding {
        return ActivityLoginBinding.inflate(layoutInflater)
    }
    
    override fun initView() {
        binding.btnLogin.setOnClickListener {
            viewModel.login(mobile, password)
        }
    }
    
    override fun initObserver() {
        observeStateFlow(viewModel.loginState) { state ->
            when (state) {
                is LoginState.Loading -> showLoading()
                is LoginState.Success -> {
                    hideLoading()
                    showSuccess(state.message)
                }
                is LoginState.Error -> {
                    hideLoading()
                    showError(state.message)
                }
            }
        }
    }
}

7.2 简单方式:直接使用 Repository(简单场景)

class LoginActivity : BaseActivity<ActivityLoginBinding>() {
    
    private val repository = AuthRepository()
    
    override fun initView() {
        binding.btnLogin.setOnClickListener {
            executeRequest(
                request = { repository.login(mobile, password) },
                onSuccess = { response ->
                    showSuccess("登录成功")
                    // 处理成功逻辑
                }
            )
        }
    }
}

7.3 自定义 UI 实现

class LoginActivity : BaseActivity<ActivityLoginBinding>() {
    
    // 自定义加载 UI
    override fun showLoading() {
        binding.progressBar.visibility = View.VISIBLE
        binding.btnLogin.isEnabled = false
    }
    
    override fun hideLoading() {
        binding.progressBar.visibility = View.GONE
        binding.btnLogin.isEnabled = true
    }
    
    // 自定义错误提示(使用 Dialog 而不是 Toast)
    override fun showError(message: String) {
        AlertDialog.Builder(this)
            .setTitle("错误")
            .setMessage(message)
            .setPositiveButton("确定", null)
            .show()
    }
}

八、优势

8.1 代码简化

之前

class LoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityLoginBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        // 初始化视图
        binding.btnLogin.setOnClickListener { ... }
        
        // 观察状态
        lifecycleScope.launch {
            viewModel.loginState.collect { state ->
                when (state) {
                    is LoginState.Loading -> {
                        // 显示加载
                        binding.progressBar.visibility = View.VISIBLE
                    }
                    is LoginState.Success -> {
                        // 隐藏加载
                        binding.progressBar.visibility = View.GONE
                        Toast.makeText(this@LoginActivity, state.message, Toast.LENGTH_SHORT).show()
                    }
                    is LoginState.Error -> {
                        // 隐藏加载
                        binding.progressBar.visibility = View.GONE
                        Toast.makeText(this@LoginActivity, state.message, Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }
}

现在

class LoginActivity : BaseActivity<ActivityLoginBinding>() {
    
    override fun getViewBinding() = ActivityLoginBinding.inflate(layoutInflater)
    
    override fun initView() {
        binding.btnLogin.setOnClickListener { ... }
    }
    
    override fun initObserver() {
        observeStateFlow(viewModel.loginState) { state ->
            when (state) {
                is LoginState.Loading -> showLoading()
                is LoginState.Success -> { hideLoading(); showSuccess(state.message) }
                is LoginState.Error -> { hideLoading(); showError(state.message) }
            }
        }
    }
}

代码减少:约 60%+

8.2 统一 UI 风格

  • ✅ 所有 Activity 的加载、错误提示保持一致
  • ✅ 统一的生命周期管理
  • ✅ 统一的代码结构

8.3 易于维护

  • ✅ 通用功能修改只需改基类
  • ✅ 子类可重写方法自定义实现
  • ✅ 职责清晰,易于理解

8.4 符合 MVVM 架构

  • ✅ UI 层只负责显示
  • ✅ 业务逻辑在 ViewModel 中处理
  • ✅ 网络请求在 Repository 中处理
  • ✅ 职责分离,解耦清晰

九、注意事项

9.1 必须实现的方法

  • getViewBinding() - 必须实现,返回 ViewBinding 实例

9.2 可选实现的方法

  • initView() - 初始化视图(可选)
  • initObserver() - 初始化观察者(可选)
  • showLoading() / hideLoading() - 自定义加载 UI(可选)
  • showError() / showSuccess() - 自定义提示 UI(可选)

9.3 推荐使用方式

  • 推荐:使用 ViewModel + StateFlow + observeStateFlow() 扩展函数
  • ⚠️ 可选:简单场景可以使用 executeRequest() 方法

9.4 网络请求处理

  • 推荐:网络请求在 ViewModel 中处理,Activity 只观察状态
  • ⚠️ 可选:简单场景可以在 Activity 中使用 executeRequest() 方法

十、完整架构层次

UI 层(View)
├── BaseActivity / BaseFragment          # UI 基类
│   ├── ViewBinding 支持
│   ├── 加载状态管理
│   ├── 错误提示
│   ├── 生命周期管理
│   └── executeRequest() 便捷方法(可选)
│
├── ActivityExtensions / FragmentExtensions  # 扩展函数
│   └── observeStateFlow() 简化状态观察
│
└── Activity / Fragment                  # 业务 UI 组件
    └── 继承 BaseActivity / BaseFragment

业务逻辑层(ViewModel)
└── ViewModel                           # 处理业务逻辑
    └── 使用 Repository

数据层(Data)
├── Repository                          # 数据仓库
│   └── 继承 ApiBaseRepository
│
└── RemoteDataSource                    # 远程数据源
    └── 继承 ApiBaseRemoteDataSource

十一、参考文档