设计思想培养:装饰者模式下的RecyclerView添加头、尾

news2025/1/16 2:02:08

用一个设计模式培养高复用、低耦合思想

  • 前言
  • Android中的装饰者
  • 代码实现
    • 第一步:创建装饰器DecorateAdapter
    • 第二步:处理头部、中间内容、尾部的绑定关系
    • 第三步:装饰器的使用
    • 第四步:改进、直接封装一个View出来
  • 总结

前言

一个高复用、低耦合的代码不会让你在第一次去实现代码的时候感到舒服
但是他会在你后面做扩展、和同类需求的时候,直呼真香!!!

最近写需求,借用到装饰者思想做了RecyclerView的头和尾的扩展
感觉很不错,赶紧拿出来说一说,嘻嘻

ps:本篇文章只是帮助大家,在实现需求的过程中也能潜移默化的使用设计模式来优化代码

Android中的装饰者

首先一句话总结装饰者:就是不通过继承的方式,来扩展某个对象的功能,达到我们想要实现的效果。
举两个小例子,不细说,因为前面已经讲过装饰者模式相关的内容

  • InputStream装饰者

InputStream装饰者:在Android中,我们经常需要读取文件或者网络流。InputStream是用于读取字节流的抽象类,而Android提供了许多装饰者类来扩展InputStream的功能。
例如,BufferedInputStream和DataInputStream都是InputStream的装饰者类,它们添加了缓冲和数据类型转换的功能。

FileInputStream fileInputStream = new FileInputStream("example.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
  • View装饰者

Android中的UI组件都继承自View类,而Android提供了装饰者类来扩展View的功能
例如,可以使用LayoutInflater来给布局文件添加一些额外的装饰

LayoutInflater inflater = LayoutInflater.from(context);
View originalView = inflater.inflate(R.layout.original_layout, parentLayout, false);
ViewDecorator viewDecorator = new ViewDecorator(originalView);
View decoratedView = viewDecorator.decorate();
parentLayout.addView(decoratedView);

基于这种思想,在做RecyclerView的头和尾的扩展想到了,利用这种装饰者扩展的方式去加头和尾,而保留中间数据层的原有属性。

代码实现

利用DecorateAdapter去装饰RecyclerView.Adapter,从而扩展HeadView、FooterView
在这里插入图片描述
中间内容我们使用contentAdapter来表示,假设就是简单的Item结构,就不做代码介绍了。

第一步:创建装饰器DecorateAdapter

创建装饰器DecorateAdapter并实现增加头部和尾部Item的相关方法、和成员变量
并将contentAdapter作为原始的内容列表

class DecorateAdapter(
    val contentAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), IdecorateList {

    /**save head View*/
    private val headerList: MutableList<View> by lazy {
        mutableListOf()
    }

    /**save footer view*/
    private val footerList: MutableList<View> by lazy {
        mutableListOf()
    }

    /**常规方法*/
    ......
    ......

    override fun getItemCount(): Int {
        //记得加上contentAdapter的Item条目
        return contentAdapter.itemCount + headerList.size + footerList.size
    }

    override fun addHeaderView(header: View) {
        if (!headerList.contains(header)) {
            headerList.add(header)
            refreshList()
        }
    }

    override fun removeHeaderView(header: View) {
        if (headerList.contains(header)) {
            headerList.remove(header)
            refreshList()
        }
    }

    override fun addFooterView(foot: View) {
        if (!footerList.contains(foot)) {
            footerList.add(foot)
            refreshList()
        }
    }

    override fun removeFooterView(foot: View) {
        if (footerList.contains(foot)) {
            footerList.remove(foot)
            refreshList()
        }
    }

    override fun refreshList() {
        notifyDataSetChanged()
    }
}

第二步:处理头部、中间内容、尾部的绑定关系

  • 处理头部、中间、尾部的不同绑定的视图:也就是createHeaderViewHolder处理
/**创建头部的ViewHolder*/
private fun createHeaderViewHolder(view: View): RecyclerView.ViewHolder {
    return HeaderViewHolder(view)
}

/**创建尾部的ViewHolder*/
private fun createFooterViewHolder(view: View): RecyclerView.ViewHolder {
    return FooterViewHolder(view)
}

override fun onCreateViewHolder(parent: ViewGroup, position: Int): RecyclerView.ViewHolder {

    /**头部样式展示*/
    if (headerList.isNotEmpty() && position in 0 until headerList.size) {
        return createHeaderViewHolder(headerList[position])
    }

    /**中间内容展示*/
    val startPosition = if (headerList.isNotEmpty()) headerList.size else 0
    val endPosition =
        if (headerList.isNotEmpty()) headerList.size + contentAdapter.itemCount else contentAdapter.itemCount
    if (position in startPosition until endPosition) {
        return contentAdapter.onCreateViewHolder(parent, position)
    }

    /**尾部样式展示*/
    return createFooterViewHolder(footerList[position - endPosition]) /**注意这里的取值*/
}
  • 处理处理头部、中间、尾部的数据绑定:也就是onBindViewHolder的处理
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

    if (headerList.isNotEmpty() && position in 0 until headerList.size) {
        return
    }
    /**中间的内容数据绑定*/
    val startPosition = if (headerList.isNotEmpty()) headerList.size else 0
    val endPosition =
        if (headerList.isNotEmpty()) headerList.size + contentAdapter!!.itemCount else contentAdapter!!.itemCount
    if (position in startPosition until endPosition) {
        /**注意计算的时候,要减去HeadView*/
        contentAdapter?.onBindViewHolder(holder, position - headerList.size)
    }
}

第三步:装饰器的使用

把内容区域的ContentAdapter,通过DecorateAdapter进行包装,之后调用包装后的decorateAdapter进行添加HeadView

decorate = findViewById(R.id.rv_decorate)
val decorateAdapter = DecorateAdapter(ContentAdapter())
decorate.adapter = decorateAdapter
decorate.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

decorateAdapter.addHeaderView(
    LayoutInflater.from(this).inflate(R.layout.layout_header, decorate, false)
)

第四步:改进、直接封装一个View出来

把装饰者封在内部
毕竟我们在调用的时候,还是想当一个View直接拿来用,改进一下

class decorateRecyclerView : RecyclerView {

    private var DecorateAdapter: DecorateAdapter? = null

    @JvmOverloads
    constructor(context: Context, attributes: AttributeSet? = null) : super(context, attributes)

    override fun setAdapter(adapter: Adapter<ViewHolder>?) {
        DecorateAdapter = DecorateAdapter(adapter!!)
        super.setAdapter(DecorateAdapter)
    }

    fun addHeaderView(header: View) {
        DecorateAdapter?.addHeaderView(header)
    }

    fun removeHeaderView(header: View) {
        DecorateAdapter?.removeHeaderView(header)
    }

    fun addFooterView(foot: View) {
        DecorateAdapter?.addFooterView(foot)
    }

    fun removeFooterView(foot: View) {
        DecorateAdapter?.removeFooterView(foot)
    }
}

调用 & 使用

decorate = findViewById(R.id.rv_decorate)
decorate.adapter = ContentAdapter()
decorate.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

decorate.addHeaderView(
    LayoutInflater.from(this).inflate(R.layout.layout_header, decorate, false)
)
decorate.addFooterView(
    LayoutInflater.from(this).inflate(R.layout.layout_footer, decorate, false)
)

总结

本篇呢,只是想说一下如何去潜移默化的使用设计模式去改变我们的代码。
举了一个扩展RecyclerView头和尾的例子。
如果不用设计模式的话,我想你的头和尾的处理逻辑都是需要冗余在ContentAdapter中的
问题:
1、那么你的ContentAdapter的定义界限是什么?整个页面的业务?那难免太难维护了
2、如果只需要服用ContentAdapter怎么处理?粘贴复制,多造一个类出来?

而通过上面我们介绍的装饰者模式就能解决这两个:复用、耦合的问题

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统备考学习 day11 (4.1.1~4.1.9)

操作系统备考学习 day11 第四章 文件管理4.1文件系统基础4.1.1 文件的基本概念文件的属性文件的逻辑结构操作系统向上提供的功能文件如何存放在外存 4.1.2 文件的逻辑结构顺序文件索引文件索引顺序文件 4.1.3 文件目录文件控制块单级目录结构两级目录结构多级目录结构 又称树形…

2023年四川省网络与信息安全技能大赛 决赛个人赛Writeup

文章目录 Web前端验证PHP_Try MiscHelloWorld密码在这easy_log Cryptobaser 线下“断网”CTF个人赛&#xff0c;题都很简单(新手级难度)&#xff0c;总共10道题目&#xff0c;解了6题。 赛题附件请自取&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1lgNEBO7a1L4KLE2t…

Chrome如何解决http自动转为https问题

开发中总遇到http被浏览器转为https导致无法访问404 具体配置如下&#xff1a; 就能正常访问你的http不安全地址

逻辑(css3)_强制不换行

需求 如上图做一个跑马灯数据&#xff0c;时间、地点、姓名、提示文本字数都不是固定的。 逻辑思想 个人想法是给四个文本均设置宽度&#xff0c;不然会出现不能左对齐的现象。 此时四个文本均左对齐&#xff0c; 垂直排列样式也比较好看&#xff0c;但是出现一个缺点&#…

eDNA放大招:看完这篇文献,你的茶包还香吗?

eDNA在过去几年彻底改变了生物监测领域&#xff0c;一起来看它在生活中的应用吧。节肢动物&#xff08;无脊椎动物&#xff0c;如昆虫、甲壳类等&#xff09;在全球生态系统平衡维护中发挥重要作用。eDNA作为传统节肢动物监测的替代方案发挥出巨大的潜力。 最近一项发表于《Bi…

设备树(以STM32MP1为例)

1.设备树&#xff08;Device Tree&#xff09; 是一种用于描述硬件信息和配置的数据结构&#xff0c;以提供一个统一的方式来描述各种硬件设备的特性和连接方式。 设备树并不是从开始就存在&#xff0c;而是后来加入到Linux中&#xff0c;设备树主要用来描述系统的硬件结构 它是…

Hikari源码分析

总结 连接池关系 1、HikariDataSource构建函数->生成HikariPool对象->调用HikariPool的getConection得到连接 2、HikariPool包含ConcurrentBag 3、ConcurrentBag保存连接&#xff1a;三个集合threadList、sharedList、handoffQueue 4、ConcurrentBag管理连接&#xff1…

maven:编译出现Process terminated解决方法(超全)

maven:编译出现Process terminated解决方法&#xff08;超全&#xff09; 一. 情况一&#xff1a;配置文件 settings. xml 出错&#xff08;解决方法1&#xff09;1.1 项目编译报错如下&#xff1a;1.2 点击【项目名】提示找到出错文件1.3 点击查看出错文件1.4 原因及解决办法 …

LeetCode 2401.最长优雅子数组 ----双指针+位运算

数据范围1e5 考虑nlog 或者n的解法&#xff0c;考虑双指针 因为这里要求的是一段连续的数组 想起我们的最长不重复连续子序列 然后结合一下位运算就好了 是一道双指针不错的题目 class Solution { public:int longestNiceSubarray(vector<int>& nums) {int n nums…

微信朋友圈如何关闭?

怎样关闭微信朋友圈&#xff1f;由于一些比较特殊的原因&#xff0c;有些人不想再发朋友圈了&#xff0c;或者想跟过去的自己说“拜拜”&#xff0c;所以就想把微信朋友圈给关掉。虽然这种需求的人并不多&#xff0c;但却真实存在着。 微信早期版本是有关闭朋友圈开关的&#x…

【C# Programming】委托和lambda表达式、事件

目录 一、委托和lambda表达式 1.1 委托概述 1.2 委托类型的声明 1.3 委托的实例化 1.4 委托的内部机制 1.5 Lambda 表达式 1.6 语句lambda 1.7 表达式lambda 1.8 Lambda表达式 1.9 通用的委托 1.10 委托没有结构相等性 1.11 Lambda表达式和匿名方法的内部机制 1.1…

vite+vue3路由切换滚动条位置重置el-scrollbar

vitevue3路由切换滚动条位置重置 本文目录 vitevue3路由切换滚动条位置重置使用原生滚动条使用el-scrollbaruseRoute和useRouter 当切换到新路由时&#xff0c;想要页面滚到顶部&#xff0c;或者是保持原先的滚动位置&#xff0c;就像重新加载页面那样&#xff0c;vue-router 可…

升级版运算放大器应用电路(二)

网友&#xff1a;你上次分享的经典运算放大电路太棒了&#xff0c;再分享几个吧&#xff01; 工程师&#xff1a;好吧&#xff0c;那你瞪大耳朵&#xff0c;看好了~ 1、仪器放大电路&#xff0c;此电路使用于小信号的放大&#xff0c;一般用于传感器信号的放大。传感器的输出信…

紫光同创PGL50H图像Sobel边缘检测

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGL50H开发平台&#xff08;盘古50K开发板&#xff09; 一&#xff1a;软硬件平台 软件平台&#xff1a;PDS_…

ROS中如何实现将一个基于A坐标系下的三维向量变换到基于B坐标系下?

摘要 ROS中通过tf.TransformListener.lookupTransform方法获取从A坐标系到B坐标系的旋转四元数rot&#xff0c;通过quaternion_multiply函数实现 p ′ q p q − 1 p qpq^{-1} p′qpq−1中的矩阵乘法&#xff0c;从而可以方便获取在新坐标系下的四元数坐标表示 p ′ p p′. 基…

app广告变现,开发者如何提高用户的参与度?

越来越多的开发者已经认识到用户参与移动应用内广告的价值。 1、人性化的消息传递 人性化的消息传递在情感层面上与用户产生共鸣的方式进行沟通&#xff0c;让他们感到被理解、被重视和参与。它在用户之间建立了一种信任感和可信度。对话式和相关的语气建立了超越交易关系的联…

OBS直播软件使用NDI协议输入输出

OBS&#xff08;Open Broadcaster Software&#xff09;是一个免费的开源的视频录制和视频推流软件。其功能强大并广泛使用在视频导播、录制及直播等领域。 OBS可以导入多种素材&#xff0c;除了本地音频、视频、图像外&#xff0c;还支持硬件采集设备&#xff0c;更能支持各种…

致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC]

文章目录 致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用…

多测师肖sir_高级金牌讲师_性能测试之badboy录制脚本02

性能测试之badboy录制脚本 一、下载安装包&#xff0c;点击安装 二、点击我同意 三、选择路径&#xff0c;点击install 打开以下界面&#xff0c;表示安装成功 第二步&#xff1a;录制流程 界面视图&#xff0c;模拟浏览器&#xff0c;能够进行操作 需要录制脚本的URL 点…

英语小作文模板(06求助+描述;07描述+建议)

06 求助描述&#xff1a; 题目背景及要求 第一段 第二段 第三段 翻译成中文 07 描述&#xff0b;建议&#xff1a; 题目背景及要求 第一段 第二段