什么?这个天天使用的API竟然被废弃了?

2023-11-22 14:50
文章标签 使用 api 竟然 天天 废弃

本文主要是介绍什么?这个天天使用的API竟然被废弃了?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

各位小伙伴们,大家早上好。

上个礼拜,我在公众号的某篇文章下面看到这样一条留言:

什么?holder.adapterPosition被划线不推荐使用了?

《第三行代码》这才刚刚出版,竟然就有API被弃用了,我决定对这个问题好好研究一下,并加急写一篇文章进行分析。

仔细一看,holder.adapterPosition这不就是我们平时在RecyclerView里面用于获取点击位置的方法么,常用写法如下:

holder.itemView.setOnClickListener {val position = holder.adapterPositionLog.d("TAG", "you clicked position $position")
}

这个方法相信每个人都用过不下千百遍,这种方法怎么会被废弃呢?于是我到Android的官网去查了一下文档,果然,getAdapterPosition()方法被标记成了废弃:

我帮大家翻译一下这段英文:这个方法当多个adapter嵌套时会存在歧义。如果你是在一个adapter的上下文中调用这个方法,你可能想要调用的是getBindingAdapterPosition()方法。如果你想获得的position是如同在RecyclerView中看到的那样,你应该调用getAbsoluteAdapterPosition()方法。

看完这段解释是不是还是一脸懵逼?但我已经尽可能翻译得准确了。

我在看完这段解释之后也是不能理解,为什么这个方法当多个adapter嵌套时会存在歧义?多个adapter嵌套让我容易联想到RecyclerView中嵌套RecyclerView,但是好像Google长久以来并不推荐这种做法,更不太可能为这种做法废弃API。

百思不得其解的时候,我突然想起来前几天隔壁鸿洋大神的公众号里推荐了一篇文章,讲的是Google新推出了一个MergeAdapter。直觉告诉我,可能是和这个新功能有关。

不过MergeAdapter是在RecyclerView 1.2.0版本中才新增的,而官网目前RecyclerView的最新稳定版本还是1.1.0。1.2.0还在alpha阶段,连beta阶段都没到:

库还没稳定,文档却先标为废弃了,Google这个做法也真是有点急不可耐。

那么MergeAdapter到底有什么作用呢?我简单看了一下介绍就明白了,因为这就是我一直想要追求的功能啊!

它的主要作用很简单,就是将多个Adapter合并到一起。

你可能会说,为什么我的RecyclerView里面会有多个Adapter呢?那是因为你或许还没有遇到过这样的需求,而我就遇到了。

两年前我在做giffun这个项目时,查看GIF图详情的界面就是使用RecyclerView来做的。

可能你没有想到这个界面会是一个RecyclerView,但是它确实就是如此,界面中的内容主要分成了如上图所示的3部分。

那么一个RecyclerView中怎么能显示3种完全不同的内容呢?我当时是在Adapter当中使用了多种不同的viewType来实现的:

override fun getItemViewType(position: Int) = when (position) {0 -> DETAIL_INFO1 -> if (commentCount == -1) {LOADING_COMMENTS} else if (commentCount == 0 || commentCount == -2) {NO_COMMENT} else {HOT_COMMENTS}2 -> ENTER_COMMENTelse -> super.getItemViewType(position)
}

可以看到,这里根据不同的position,返回了不同的viewType。当position是0的时候,返回DETAIL_INFO,也就是gif详情区域。当position是1的时候,返回LOADING_COMMENTS、NO_COMMENT、HOT_COMMENTS中的一种,用于展示评论内容。当position是2的时候,返回ENTER_COMMENT,也就是评论输入框区域。

giffun的源码是完全公开的,你可以到这里查看这个类的完整代码:

https://github.com/guolindev/giffun/blob/master/main/src/main/java/com/quxianggif/feeds/adapter/FeedDetailMoreAdapter.kt

那么这种写法有没有什么问题呢?最主要的问题就是,代码耦合性太高了。其实这几种不同的viewType之间完全没有任何关联性,将它们都写到同一个Adapter当中会让这个类显得比较臃肿,后期也就更加难为维护。

而MergeAdapter就是为了解决这种情况而出现的。它可以让你将几个业务逻辑没有关联的Adapter分开编写,最后再将它们合并到一起,并设置给RecyclerView。

这里我准备使用一个非常简单的例子来演示一下MergeAdapter的用法。

首先,确保你使用的RecyclerView版本不低于1.2.0-alpha02,否则是没有MergeAdapter这个类的:

dependencies {implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha02'
}

接下来创建两个非常简单的Adapter,一个TitleAdapter和一个BodyAdapter,待会我们会用MergeAdapter将这两个Adapter合并到一起。

TitleAdapter代码如下:

class TitleAdapter(val items: List<String>) : RecyclerView.Adapter<TitleAdapter.ViewHolder>() {inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {val text: TextView = view.findViewById(R.id.text)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)val holder = ViewHolder(view)return holder}override fun getItemCount() = items.sizeoverride fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.text.text = items[position]}}

这是一个Adapter最简单的实现,没有任何逻辑在里面,只是为了显示一行文字。item_view是个只包含一个TextView控件的简单布局,这里就不展示其中的代码了。

然后BodyAdapter的代码如下:

class BodyAdapter(val items: List<String>) : RecyclerView.Adapter<BodyAdapter.ViewHolder>() {inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {val text: TextView = view.findViewById(R.id.text)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)val holder = ViewHolder(view)return holder}override fun getItemCount() = items.sizeoverride fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.text.text = items[position]}}

基本上就是复制过来的代码,和TitleAdapter没有什么区别。

然后我们在MainActivity当中就可以这样使用了:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val titleItems = generateTitleItems()val titleAdapter = TitleAdapter(titleItems)val bodyItems = generateBodyItems()val bodyAdapter = BodyAdapter(bodyItems)val mergeAdapter = MergeAdapter(titleAdapter, bodyAdapter)recyclerView.layoutManager = LinearLayoutManager(this)recyclerView.adapter = mergeAdapter}private fun generateTitleItems(): List<String> {val list = ArrayList<String>()repeat(5) { index ->list.add("Title $index")}return list}private fun generateBodyItems(): List<String> {val list = ArrayList<String>()repeat(20) { index ->list.add("Body $index")}return list}}

可以看到,这里我编写了generateTitleItems()和generateBodyItems()这两个方法,分别用于给两个Adapter生成数据集。然后创建了TitleAdapter和BodyAdapter的实例,并使用MergeAdapter将它们合并到一起。合并的方式很简单,就是将你要合并的所有Adapter的实例都传入到MergeAdapter的构造方法当中即可。

最后,将MergeAdapter设置到RecyclerView当中,整个过程结束。

是不是非常简单?几乎和之前RecyclerView的用法没有任何区别。

现在运行一下程序,效果如下图所示:

可以看到,TitleAdapter和BodyAdapter中的数据是合并到一起显示的,同时也就说明,我们的MergeAdapter已经成功生效了。

到这里为止都还算很好理解,但是接下来,我要给大家一个灵魂拷问了。

如果这时,我想要监听BodyAdapter中元素的点击事件,那么调用getAdapterPosition()方法,获得的到底是BodyAdapter中元素的点击位置,还是合并之后元素的点击位置呢?

你会发现,这个时候getAdapterPosition()方法已经会造成歧义了,这也就是开篇那段英文所描述的问题。

而解决办法当然也很简单,Google废弃了getAdapterPosition()方法,但是却又提供了getBindingAdapterPosition()和getAbsoluteAdapterPosition()这两个方法。从名字上就可以看出来了,一个是用于获取元素位于当前绑定Adapter的位置,一个是用于获取元素位于Adapter中的绝对位置。

如果觉得我上面的解释还不够清楚,通过下面的示例看一下你立马就能明白了。

我们修改BodyAdapter中的代码,在里面加入监听当前元素点击事件的代码,如下所示:

class BodyAdapter(val items: List<String>) : RecyclerView.Adapter<BodyAdapter.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)val holder = ViewHolder(view)holder.itemView.setOnClickListener {val position = holder.bindingAdapterPositionToast.makeText(parent.context, "You clicked body item $position", Toast.LENGTH_SHORT).show()}return holder}...
}

可以看到,这里调用的是getBindingAdapterPosition()方法,并通过Toast弹出当前点击元素的位置。

运行一下程序,效果如下图所示:

很明显,我们获取到的点击位置是元素位于BodyAdapter中的位置。

再修改一下BodyAdapter中的代码,将getBindingAdapterPosition()方法换成getAbsoluteAdapterPosition()方法:

class BodyAdapter(val items: List<String>) : RecyclerView.Adapter<BodyAdapter.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)val holder = ViewHolder(view)holder.itemView.setOnClickListener {val position = holder.absoluteAdapterPositionToast.makeText(parent.context, "You clicked body item $position", Toast.LENGTH_SHORT).show()}return holder}...
}

然后重新运行程序,如下所示:

结果一目了解,获取到的点击位置是元素位于合并后Adapter中的位置。

最后整理一下结论吧:

  • 如果你没有使用MergeAdapter,那么getBindingAdapterPosition()和getAbsoluteAdapterPosition()方法的效果是一模一样的。

  • 如果你使用了MergeAdapter,getBindingAdapterPosition()得到的是元素位于当前绑定Adapter的位置,而getAbsoluteAdapterPosition()方法得到的是元素位于合并后Adapter的绝对位置。

文章写到这里,也就把开篇“木空”同学提出的问题彻底分析完毕,我觉得本篇文章也可以算得上是一篇《第一行代码 第3版》的扩展文章吧。

另外说一下,由于《第一行代码 第3版》已经出版,以后未来我自己编写的所有文章都会使用Kotlin语言,Java就不再使用了,想学习Kotlin语言的朋友们可以考虑一下这本书。

由于这是我第一次尝试编写编程语言类型的内容,本来心里不是特别有底,但是看到第一批读者普遍反馈好评之后,我现在更加坚信这本书的质量了。我的QQ群里有个群友还说,自己之前学过几轮Kotlin了,都没有这本书讲得好,看得我也是心里暖暖的。

文章末尾照例还是给出《第一行代码 第3版》的购买链接,有需要的朋友点击下方小程序即可下单。

另外预告一下,本周四晚,我会开启一场技术直播,两年一度的在线写代码环节又要来啦,详情到时再说,我们周四见!

京东小程序,一键下单购买:

欢迎关注我的公众号

学习技术或投稿

长按上图,识别图中二维码即可关注

这篇关于什么?这个天天使用的API竟然被废弃了?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/410918

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念