你确定你知道他们的具体含义、使用场景以及他们之间的区别么?
onBindViewHolder 中的 position 参数
通常我们会在onBindViewHolder中通过postion参数绑定 data 和 View,像下面这样:
override fun onBindViewHolder(holder: NumberHolder, position: Int) {
holder.tvNumber.text = “Position: ${list[position]}”
}
很显然,这么做没有任何问题(🐶保命)。
但是如果在这里使用position参数来处理点击事件就会有点不合适了,我们在上述的代码中加一行代码:
override fun onBindViewHolder(holder: NumberHolder, position: Int) {
holder.tvNumber.text = “Position: ${list[position]}”
holder.itemView.setOnClickListener {
Toast.makeText(it.context, “点击了:${list[position]}”, Toast.LENGTH_SHORT).show()
}
}
然后在页面中添加一个“-1”的按钮,功能也很简单:移除列表的第一项数据,代码如下:
fun removeFirstItem(){
list.removeAt(0)
notifyItemRemoved(0)
}
我们来运行下看看效果:
可以看到,如果代码按照我们预期那样,应该是点击哪个位置,就弹出那个位置的position的toast,可是当我们调用removeFirstItem方法移除列表的第一个item后,就会出现 item 和 position 对不上号的情况(点击了postion:1弹出的toast显示点击了:2),这就是在onBindViewHolder中直接使用position参数设置点击事件可能引发的问题。
WHY?
其实原因很简单:使用notifyItem*()
此类方法来删除/添加/更改RecyclerView的数据中的任何一条数据时,RecyclerView并不会调用所有Item的onBindViewHolder
方法更新item的位置,它只会更新notifyItem*()
的位置,所以导致了显示的数据和真实的数据 Position 对应不上的问题。
其实在官方源码的注释中也额外强调了这点(注释很重要⚠️):
Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method again if the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined. For this reason, you should only use the
position
parameter while acquiring the related data item inside this method and should not keep a copy of it. If you need the position of an item later on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will have the updated adapter position.
怎么解决这个问题呢?其实源码的注释也给了解决方法了(注释很重要⚠️),使用getAdapterPosition
。
getAdapterPosition
ViewHolder为我们提供了 getAdapterPosition
方法来获取 ViewHolder 的位置。该方法 总是 返回 ViewHolder 最新的位置,也就意味着使用该方法,即使调用notifyItem*()
此类方法来删除/添加/更改 RecyclerView 的数据,该方法返回的位置也能确保获取的Position是正确的。感兴趣的可以跟上面的写法对比一下看看效果 ~
事情解决了…么?
如果你看完我上一段的解决方法迫不及待的打开了Android Studio
去验证getAdapterPosition
是否真的那么有效,那我先要夸夸你,毕竟
纸上得来终觉浅,绝知此事要躬行。
所以你一定也知道了我要说什么了:getAdapterPosition()
被废弃了,官网对此也有说明(官网很重要⚠️):
用我那蹩脚的英语大致翻译一下,就是谷歌觉得这个方法在 Adapter 嵌套Adapter 的情况下会带来歧义,推荐你考虑使用 getBindingAdapterPosition
或者getAbsoluteAdapterPosition
这两个方法。
相信你刚看完这段解释的时候,一定是像我一样更懵逼了:我本来只想知道为啥弃用getAdapterPosition()
,这家伙倒好,又给我整出来俩方法,还歧义,等等…什么是 Adapter 嵌套 Adapter?好家伙,现在 Adapter 还可以嵌套了么?
你别说,还真可以。如果你恰好使用过阿里开源的vLayout
,就一定不会对 Adapter 嵌套 Adapter 的用法感到陌生。我们都知道对于Android来说,复杂的Feed流页面,我们基本都是通过RecyclerView的多样式布局来实现,通过重写Adapter的getItemViewType
来区分不同的样式,实现不同的UI逻辑,长久以来一直如此。
从来如此,便对么?
这种长久以来的写法,最大的问题就是将不同样式类型的布局耦合在了同一个Adapter中,随着业务的迭代,这个耦合的Adapter很有可能变得异常臃肿,而且这种写法要时刻注意数据的处理要区分ViewType,给日后的维护带来极大的挑战。有没有更好的做法呢?
对于以上问题,阿里给出了vLayout
库来解决,这里就不展开讲了,因为——它停止维护了。谷歌大概是看到了开发者面对这种复杂页面开发和维护时脸上的痛苦面具,所以他们推出了MergeAdapter
这个玩意,简单来说,他就像一个容器,里面可以添加多个Adapter,然后将MergeAdapter设置为RecyclerView的Adapter,从而轻松实现多样式布局的效果。这就是谷歌官网所写的 Adapter 嵌套 Adapter情况:MergeAdapter 里 可能会包含了 多个开发者写的Adapter。
这种情况下,我们如果继续调用getAdapterPosition
就会引发歧义了,因为程序可能并不知道你想要的是ViewHolder的相对位置,还是绝对位置。
69/article/details/119706823)相对位置 & 绝对位置?getBindindAdapterPosition 与 getAbsoluteAdapterPosition 的区别
此处的相对位置及绝对位置的叫法,并非官方叫法,而是参考文件系统中的 相对路径 和 绝对路径,提出的一种类似概念。我们举例说明什么是相对位置和绝对位置。如下图中的例子:MergeAdapter里包含了A Adapter 和 B Adapter,在页面的展示上,B 在 A 的后面,我们想获取B中某一个元素b3的位置,此时的位置有两种:b3在B中的位置,我把他叫做相对位置,以及b3在整个RecyclerView中所处的位置,我将其称之为绝对位置。
官方提供的两个方法getBindingAdapterPostion
与getAbsoluteAdapterPosition
就是用来获取ViewHolder的相对位置和绝对位置的。
-
getBindingAdapterPosition将会返回该ViewHolder相对于它绑定的Adapter中的位置,即相对位置。
-
getAbsoluteAdapterPosition将会返回该ViewHolder相对于RecyclerView的位置,即绝对位置。
回到我们文章开头提到的两种典型的RecyclerView中使用Position的场景: