Android 复杂UI界面分模块解耦的一次实践

一、复杂UI页面开发的问题

常见的比较复杂的UI界面,比如电商首页,我们看看某电商的首页部分UI:

上面是截取的首页部分,如果这个首页如果不分模块开发会遇到哪些问题?

  • 开发任务不方便分割,一个人开发的话周期会很长
  • 在XML文件中写死首页布局不够灵活
  • 逻辑和UI塞在一起不方便维护
  • 首页不能动态化配置
  • UI和逻辑难以复用

那如何解决这个问题? 下面是基于基于 BRVAH 3.0.11版本 实现的复杂页面分模块的UI和逻辑的解耦。

二、解决思路

使用RecyclerView在BRVAH中利用不同的ViewType灵活的组装页面。但也面临一些问题,比如:

  • 如何实现模块间的通讯和互传数据?
  • 如何实现模块整理刷新和局部刷新?

下面都会给出答案。

三、具体实践

我们先看看模块拆分组装UI实现的效果:

模块二中有三个按钮,前面两个按钮可以启动和停止模块一中的计数,最后一个按钮获取模块一中的计数值。对应的就是模块间通讯和获取数据。

先看看模块一中的代码:

/**
 * 模块一具有Activity生命周期感知能力
 */
class ModuleOneItemBinder(
 private val lifecycleOwner: LifecycleOwner
) : QuickViewBindingItemBinder<ModuleOneData, LayoutModuleOneBinding>(),
 LifecycleEventObserver, MultiItemEntity {
 private var mTimer: Timer? = null
 private var mIsStart: Boolean = true    //是否开始计时
 private var number: Int = 0
 private lateinit var mViewBinding: LayoutModuleOneBinding
 init {
 lifecycleOwner.lifecycle.addObserver(this)
 }
 @SuppressLint("SetTextI18n")
 override fun convert(
 holder: BinderVBHolder<LayoutModuleOneBinding>,
 data: ModuleOneData
 ) {
 //TODO 根据数据设置模块的UI
 }
 override fun onCreateViewBinding(
 layoutInflater: LayoutInflater,
 parent: ViewGroup,
 viewType: Int
 ): LayoutModuleOneBinding {
 mViewBinding = LayoutModuleOneBinding.inflate(layoutInflater, parent, false)
 return mViewBinding
 }
 /**
 * 向外暴露调用方法
 * 开始计时
 */
 fun startTimer() {
 if (mTimer != null) {
 mIsStart = true
 } else {
 mTimer = fixedRateTimer(period = 1000L) {
 if (mIsStart) {
 number++
 //修改Adapter中的值,其他模块可以通过Adapter取到这个值,也可以通过接口抛出去,这里是提供另一种思路。
 (data[0] as ModuleOneData).text = number.toString()
 mViewBinding.tv.text = "计时:$number"
 }
 }
 }
 }
 /**
 * 向外暴露调用方法
 * 停止计时
 */
 fun stopTimer() {
 mTimer?.apply {
 mIsStart = false
 }
 }
 /**
 * 生命周期部分的处理
 */
 override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
 when (event) {
 Lifecycle.Event.ON_DESTROY -> {
 //页面销毁时计时器也取消和销毁
 lifecycleOwner.lifecycle.removeObserver(this)
 mTimer?.cancel()
 mTimer = null
 }
 else -> {}
 }
 }
 /**
 * 设定itemType
 */
 override val itemType: Int
 get() = MODULE_ONE_ITEM_TYPE
}

模块一向外暴露了startTimer()stopTimer()二个方法,并且让模块一具备了Activity的生命周期感知能力,用于在页面销毁时取消和销毁计时。具备页面生命周期感知能力是模块很重要的特性。

再看看模块二中的代码:

class ModuleTwoItemBinder(private val moduleTwoItemBinderInterface: ModuleTwoItemBinderInterface) :
 QuickViewBindingItemBinder<ModuleTwoData, LayoutModuleTwoBinding>(), MultiItemEntity {
 @SuppressLint("SetTextI18n")
 override fun convert(
 holder: BinderVBHolder<LayoutModuleTwoBinding>,
 data: ModuleTwoData
 ) {
 holder.viewBinding.btStartTimer.setOnClickListener {  //接口实现
 moduleTwoItemBinderInterface.onStartTimer()
 }
 holder.viewBinding.btStopTimer.setOnClickListener {  //接口实现
 moduleTwoItemBinderInterface.onStopTimer()
 }
 holder.viewBinding.btGetTimerNumber.setOnClickListener {  //接口实现
 holder.viewBinding.tv.text =
 "获取到的模块一的计时数据:" + moduleTwoItemBinderInterface.onGetTimerNumber()
 }
 }
 /**
 * 可以做局部刷新
 */
 override fun convert(
 holder: BinderVBHolder<LayoutModuleTwoBinding>,
 data: ModuleTwoData,
 payloads: List<Any>
 ) {
 super.convert(holder, data, payloads)
 if (payloads.isNullOrEmpty()) {
 convert(holder, data)
 } else {
 //TODO 根据具体的payloads做局部刷新
 }
 }
 override fun onCreateViewBinding(
 layoutInflater: LayoutInflater,
 parent: ViewGroup,
 viewType: Int
 ): LayoutModuleTwoBinding {
 return LayoutModuleTwoBinding.inflate(layoutInflater, parent, false)
 }
 override val itemType: Int
 get() = MODULE_TWO_ITEM_TYPE
}

模块二中有一个ModuleTwoItemBinderInterface接口对象,用于调用接口方法,具体接口实现在外部。convert有全量刷新和局部刷新的方法,对于刷新也比较友好。

接着看看是如何把不同的模块拼接起来的:

class MultipleModuleTestAdapter(
 private val lifecycleOwner: LifecycleOwner,
 data: MutableList<Any>? = null
) : BaseBinderAdapter(data) {
 override fun getItemViewType(position: Int): Int {
 return position + 1
 }
 /**
 * 给类型一和类型二设置数据
 */
 fun setData(response: String) {
 val moduleOneData = ModuleOneData().apply { text = "模块一数据:$response" }
 val moduleTwoData = ModuleTwoData().apply { text = "模块二数据:$response" }
 //给Adapter设置数据
 setList(arrayListOf(moduleOneData, moduleTwoData))
 }
 /**
 * 添加ItemType类型一
 */
 fun addItemOneBinder() {
 addItemBinder(
 ModuleOneData::class.java,
 ModuleOneItemBinder(lifecycleOwner)
 )
 }
 /**
 * 添加ItemType类型二
 */
 fun addItemTwoBinder(moduleTwoItemBinderInterface: ModuleTwoItemBinderInterface) {
 addItemBinder(
 ModuleTwoData::class.java,
 ModuleTwoItemBinder(moduleTwoItemBinderInterface)
 )
 }
}` 

class MainModuleManager(
 private val activity: MainActivity,
 private val viewModel: MainViewModel,
 private val viewBinding: ActivityMainBinding
) {
 private var multipleModuleTestAdapter: MultipleModuleTestAdapter? = null
 /**
 * 监听请求数据的回调
 */
 fun observeData() {
 viewModel.requestDataLiveData.observe(activity) {
 //接口请求到的数据
 initAdapter(it)
 }
 }
 private fun initAdapter(response: String) {
 //创建Adapter
 multipleModuleTestAdapter = MultipleModuleTestAdapter(activity)
 //设置RecyclerView
 viewBinding.rcy.apply {
 layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
 adapter = multipleModuleTestAdapter
 }
 //创建ModuleTwoItemBinder的接口实现类
 val moduleTwoItemBinderImpl = ModuleTwoItemBinderImpl(multipleModuleTestAdapter)
 //添加Item类型,组装UI,可以根据后台数据动态化
 multipleModuleTestAdapter?.addItemOneBinder()
 multipleModuleTestAdapter?.addItemTwoBinder(moduleTwoItemBinderImpl)
 //给所有的Item添加数据
 multipleModuleTestAdapter?.setData(response)
 }
 /**
 * 刷新单个模块的数据,也可以刷新单个模块的某个部分,需要设置playload
 */
 fun refreshModuleData(position: Int, newData: Any?) {
 multipleModuleTestAdapter?.apply {
 newData?.let {
 data[position] = newData
 notifyItemChanged(position)
 }
 }
 }
}

MultipleModuleTestAdapter中定义了多种ViewType,通过MainModuleManager返回的数据,动态的组装添加ViewType

最后就是在MainActivity中调用MainModuleManager,代码如下:

class MainActivity : AppCompatActivity() {
 private val mainViewModel: MainViewModel by viewModels()
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 val activityMainBinding: ActivityMainBinding =
 ActivityMainBinding.inflate(layoutInflater)
 setContentView(activityMainBinding.root)
 //请求数据
 mainViewModel.requestData()
 //拆分RecyclerView的逻辑
 val mainModuleManager = MainModuleManager(this, mainViewModel, activityMainBinding)
 //回调数据到MainModuleManager中
 mainModuleManager.observeData()
 //TODO 如果有其他控件编写其他控件的逻辑
 }
  
}

这样我们通过定义不同的ItemBinder实现了模块的划分,通过定义接口实现了模块间的通讯,通过后台返回数据动态的组装了页面。

其他代码一并写在末尾,方便阅读和理解:

image.png

ModuleConstant

`object ModuleConstant {
 //ItemType
 const val MODULE_ONE_ITEM_TYPE = 0
 const val MODULE_TWO_ITEM_TYPE = 1
}` 

ModuleOneDataModuleTwoData都是data类,内容完全一致,随便定义的:

`data class ModuleOneData(
 var text: String? = ""
)

ModuleTwoItemBinderImplModuleTwoItemBinderInterface的实现类,通过Adapter能轻松的获取到不同的ItemBinder,所以可以通过接口互相调用彼此的函数。

class ModuleTwoItemBinderImpl(private val multipleModuleTestAdapter: MultipleModuleTestAdapter?) :
 ModuleTwoItemBinderInterface {
 /**
 * 外部实现里面的方法
 */
 override fun onStartTimer() {
 //通过`Adapter`能轻松的获取到不同的`ItemBinder`,所以可以通过接口互相调用彼此的函数
 val moduleOneItemBinder =
 multipleModuleTestAdapter?.getItemBinder(ModuleConstant.MODULE_ONE_ITEM_TYPE + 1) as ModuleOneItemBinder
 moduleOneItemBinder.startTimer()
 }
 override fun onStopTimer() {
 //通过`Adapter`能轻松的获取到不同的`ItemBinder`,所以可以通过接口互相调用彼此的函数
 val moduleOneItemBinder =
 multipleModuleTestAdapter?.getItemBinder(ModuleConstant.MODULE_ONE_ITEM_TYPE + 1) as ModuleOneItemBinder
 moduleOneItemBinder.stopTimer()
 }
 override fun onGetTimerNumber(): String {
 multipleModuleTestAdapter?.apply {
 //通过Adapter可以轻松的拿到其他模块的数据
 return (data[0] as ModuleOneData).text ?: "0"
 }
 return "0"
 }
  
} 

nterface ModuleTwoItemBinderInterface {
 //开始计时
 fun onStartTimer()
 //停止计时
 fun onStopTimer()
 //获取计时数据
 fun onGetTimerNumber():String
}

四、总结

通过定义不同的ItemBinder将页面划分为不同模块,实现UI和交互解耦,单个ItemBinder也可以在其他页面进行复用。通过后台数据动态的添加ItemBinder页面组装更灵活。任务分拆,提高开发效率。

五、注意事项

1、不要把太复杂的UI交互放在单一模块,处理起来费劲。
2、如果二个模块中间需要大量的通讯,写太多接口也费劲,最好看能不能放一个模块。
3、数据最好请求好后再塞进去给各个ItemBinder用,方便统一处理UI。当然如果各个模块想自己处理UI,那各个模块也可以自己去请求接口。毕竟模块隔离,彼此也互不影响。
4、页面如果不是很复杂,不需要拆分成模块,不需要使用这种方式,直接一个XML搞定,清晰简单。

Android 学习笔录

Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/114539.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【UE5 Cesium】actor随着视角远近来变化其本身大小

效果 步骤 1. 首先我将“DynamicPawn”设置为默认的pawn类 2. 新建一个父类为actor的蓝图&#xff0c;添加一个静态网格体组件 当事件开始运行后添加一个定时器&#xff0c;委托给一个自定义事件&#xff0c;每2s执行一次&#xff0c;该事件每2s获取一下“DynamicPawn”和acto…

《算法通关村—如何使用中序和后序来恢复一颗二叉树》

《算法通关村—如何使用中序和后序来恢复一颗二叉树》 中序&#xff1a;3 4 8 6 7 5 2 1 10 9 11 15 13 14 12 后序&#xff1a;8 7 6 5 4 3 2 10 15 14 13 12 11 9 1 通过后续遍历我们知道根节点是1&#xff0c;通过知道根节点是1&#xff0c;我们就可以从中序序列知道那些 …

材质之选:找到适合你的地毯

当谈到家居装饰时&#xff0c;地毯是一个经常被忽视的重要元素。但事实上&#xff0c;地毯在家居中扮演了至关重要的角色&#xff0c;不仅可以增加舒适感&#xff0c;还可以改善室内的整体氛围。在这篇文章中&#xff0c;我们将探讨地毯的选择、尺寸、形状和材质&#xff0c;以…

0基础学习PyFlink——使用DataStream进行字数统计

大纲 sourceMapSplittingMapping ReduceKeyingReducing 完整代码结构参考资料 在《0基础学习PyFlink——模拟Hadoop流程》一文中&#xff0c;我们看到Hadoop在处理大数据时的MapReduce过程。 本节介绍的DataStream API&#xff0c;则使用了类似的结构。 source 为了方便&…

使用 Docker 搭建一个“一主一从”的 MySQL 读写分离集群(超详细步骤)

目录 一、前提二、MySQL 生产安装1&#xff0c;拉取mysql2&#xff0c;查看mysql镜像3&#xff0c; 启动 mysql 容器4&#xff0c;修改mysql的中文编码5&#xff0c;查看验证mysql的中文编码 三、Mysql主机 mysql_master 的安装与配置1&#xff0c; 拷贝master容器2&#xff0c…

stable-diffusion 电商领域prompt测评集合

和GhostReivew一个思路&#xff0c;还是从比较好的图片或者是civitai上找一些热门的prompt&#xff0c;从小红书上也找到了不少的prompt&#xff0c;lexica.art上也有不少&#xff0c;主要是为了电商场景的一些测评&#xff1a; 小红书、civitai、Lexica、Liblib.ai、 depth o…

在钣金加工领域,迅镭激光切割机广泛使用的原因和优点何在?

激光切割工艺和激光切割设备正在被广泛的板材加工企业逐渐理解并接受&#xff0c;凭借其高效率的加工、高精度的加工、优质的切割断面、三维切割能力等诸多优势&#xff0c;逐步取代了传统的钣金切割设备。 苏州迅镭激光科技有限公司推出的激光切割设备的柔性化程度高&#xff…

降低边际成本:跨境电商的利润增长策略

在竞争激烈的跨境电商领域&#xff0c;降低成本是提高利润的关键。边际成本&#xff0c;即生产或销售一件额外商品所需的额外成本&#xff0c;在跨境电商中起到至关重要的作用。在本文中&#xff0c;我们将探讨降低边际成本的策略&#xff0c;以实现跨境电商的利润增长。 供应链…

centos7中实现多个python版本共存(python2.7、python3.6、python3.9等)

问题描述&#xff1a; 开发环境中&#xff0c;新项目需要在python3.9及以上版本开发&#xff0c;为了不影响之前运行在python3.6上的项目&#xff0c;就需要增加一个python3.9环境。线上直接使用docker部署就可以了。 解决办法 前提&#xff1a;python2.7和python3.6之前已经…

计算机网络 第五章传输层

文章目录 1 传输层的功能2 传输层两种协议&#xff1a;UDP和TCP3 端口和端口号4 UDP数据报特点和首部格式5 UDP校验6 TCP协议的特点7 TCP报文段首部格式8 TCP连接&#xff1a;三次握手建立连接9 TCP连接&#xff1a;四次挥手释放连接10 TCP可靠传输11 TCP流量控制12 TCP拥塞控制…

快速入手maven

文章目录 Maven介绍Maven安装和配置基于IDEA的Maven工程创建梳理Maven工程GAVP属性Idea构建Maven JavaSE工程Idea构建Maven JavaEE工程1. 手动创建2. 插件方式创建 Maven工程项目结构说明Maven核心功能依赖和构建管理依赖传递和冲突依赖导入失败场景和解决方案扩展构建管理和插…

时间复杂度的计算技巧-算法模型中的时间复杂度如何计算,有哪些技巧呢

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下时间复杂度的计算技巧-算法模型中的时间复杂度如何计算&#xff0c;有哪些技巧呢&#xff0c;算法的时间复杂度是评估算法性能和效率的一种方式&#xff0c;它表示算法需要执行多少次基本操作才能完成其任务&#x…

k8s-服务网格实战-入门Istio

istio-01.png 背景 终于进入大家都比较感兴趣的服务网格系列了&#xff0c;在前面已经讲解了&#xff1a; 如何部署应用到 kubernetes服务之间如何调用如何通过域名访问我们的服务如何使用 kubernetes 自带的配置 ConfigMap 基本上已经够我们开发一般规模的 web 应用了&#xf…

app逆向入门之车智赢

声明&#xff1a;本文仅限学习交流使用&#xff0c;禁止用于非法用途、商业活动等。否则后果自负。如有侵权&#xff0c;请告知删除&#xff0c;谢谢&#xff01;本教程也没有专门针对某个网站而编写&#xff0c;单纯的技术研究 目录 案例分析技术依赖参数分析效果展示代码分享…

电压放大器可用于什么场合

电压放大器是电子器件中常见的一种放大器类型&#xff0c;它可以将输入信号的电压放大到更大的幅度&#xff0c;以满足特定应用的需求。电压放大器广泛应用于多个领域和场合&#xff0c;下面将详细介绍一些使用电压放大器的场景。 音频放大器&#xff1a;音频放大器是电压放大器…

Spark的主要概念

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f34a; 1. RDD&#x1f34a; 2. Spark SQL&#x1f34a; 3. Spark Streaming&#x1f34a; 4. MLlib&#x1f34a; 5. GraphX&#x1f34a; 总结 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍…

linux——网络套接字编程

目录 一.简单了解TCP和UDP协议 二.网络字节序 三.socket常见的编程接口 1.介绍接口 2.sockaddr结构 四.简单的UDP网络程序 1.recvfrom和sendto 2.server.cc 3.client.cc 五.简单的TCP通信 1.client.cc 2.server.cc 一.简单了解TCP和UDP协议 此处我们先对TCP(Transm…

零日漏洞预防

零日漏洞&#xff0c;是软件应用程序或操作系统&#xff08;OS&#xff09;中的意外安全漏洞&#xff0c;负责修复该漏洞的一方或供应商不知道该漏洞&#xff0c;它们仍然未被披露和修补&#xff0c;为攻击者留下了漏洞&#xff0c;而公众仍然没有意识到风险。 零日攻击是如何…

AI“走深向实”,蚂蚁蚁盾在云栖大会发布实体产业「知识交互建模引擎」

数字化起步晚、数据分散稀疏、专业壁垒高、行业知识依赖「老师傅」&#xff0c;是很多传统产业智能化发展面临的难题。2023年云栖大会上&#xff0c;蚂蚁集团安全科技品牌蚁盾发布“知识交互建模引擎”&#xff0c;将实体产业知识与AI模型有机结合&#xff0c;助力企业最快10分…

uniapp subNvue 写的视频播放

文件下载地址 https://download.csdn.net/download/weixin_47517731/88500016https://download.csdn.net/download/weixin_47517731/88500016 1:在pages.json中配置视频播放页面 {/* 视频详情页面 */"path": "pages/detail-video/detail","style&q…