1、RecyclerView item設置點擊事件
我們一般會在onBindViewHolder中設置點擊事件
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()
}
}
在響應點擊事件方法中我們直接使用了onBindViewHolder方法中的參數position取出數據,去執行相應的操作。這樣真的可以嗎?如果你只是單純的展示數據,不對數據進行任何修改,這樣是沒問題的。
當我們需要刪除數據時,比如刪除第一個item的數據,這個postion就不再準確了。
fun removeFirstItem(){
list.removeAt(0)
notifyItemRemoved(0)
}
看下面的運行效果
你會發現在刪除數據之后,點擊
Position:1
的item彈出的吐司卻是點擊了:2
,其實原因很簡單:在調用notifyItemXX()
此類方法來刪除/添加/更改RecyclerView的數據中的任何一條數據時,之前已經在屏幕上的item并沒有調用onBindViewHolder方法去更新這個position。該怎么解決這個問題呢?這就要提到
getAdapterPosition
。
2、getAdapterPosition
ViewHolder
為我們提供了 getAdapterPosition
方法來獲取 ViewHolder 的位置。該方法總是
返回 ViewHolder 最新的位置,也就意味著使用該方法,即使調用notifyItemXX()
此類方法來刪除/添加/更改 RecyclerView 的數據,該方法返回的位置也能確保獲取的Position是正確的。
遺憾的是這個方法被廢棄了。
在使用Adapter嵌套的場景下,getAdapterPosition會產生歧義,推薦使用
getBindingAdapterPosition
或者getAbsoluteAdapterPosition
這兩個方法。
如果你使用過MergeAdapter
就會對Adapter嵌套不陌生,它就像一個容器,將不同的Adapter添加到其中,然后將MergeAdapter
設置給RecyclerView,這樣就能實現了多種類型的item的效果,而且每種類型的邏輯在獨立的Adapter中,這樣也能實現了一定的解耦。
這種情況下,我們如果繼續調用getAdapterPosition
就會引發歧義了,因為程序可能并不知道你想要的是ViewHolder的相對位置
,還是絕對位置
。
3、getBindindAdapterPosition 與 getAbsoluteAdapterPosition 的區別
此處的相對位置及絕對位置的叫法,并非官方叫法,而是參考文件系統中的 相對路徑 和 絕對路徑,提出的一種類似概念。我們舉例說明什么是相對位置和絕對位置。如下圖中的例子:MergeAdapter里包含了A Adapter 和 B Adapter,在頁面的展示上,B 在 A 的后面,我們想獲取B中某一個元素b3的位置,此時的位置有兩種:b3在B中的位置,我把他叫做相對位置,以及b3在整個RecyclerView中所處的位置,我將其稱之為絕對位置。
官方提供的兩個方法getBindingAdapterPostion與getAbsoluteAdapterPosition就是用來獲取ViewHolder的相對位置和絕對位置的。
- getBindingAdapterPosition將會返回該ViewHolder相對于它綁定的Adapter中的位置,即相對位置。
- getAbsoluteAdapterPosition將會返回該ViewHolder相對于RecyclerView的位置,即絕對位置。
在設置點擊事件時,我們往往使用getBindingAdapterPostion獲取ViewHolder對應的數據項,完成點擊操作。
override fun onBindViewHolder(holder: NumberHolder, position: Int) {
holder.tvNumber.text = "Position: ${list[position]}"
holder.itemView.setOnClickListener {
Toast.makeText(it.context, "點擊了:${list[holder.bindingAdapterPosition]}", Toast.LENGTH_SHORT).show()
}
}
而在操作RecyclerView的滾動狀態,就需要使用getAbsoluteAdapterPosition進行相關的操作。
4、getLayoutPosition
getLayoutPosition,顧名思義,就是獲取該ViewHolder在實際布局中的位置。由于表示的是實際布局中的位置,故只有在布局完成后該方法才能獲取到正確的值。
layout和 adapter中的位置通常情況下是一樣的,只有當 Adapter 里面的內容改變了,而 Layout 還沒來得及繪制的這段時間之內才有可能不一樣,這個時間小于16ms。
如果調用的是 notifyDataSetChanged(),因為要重新繪制所有 Item,所以在繪制完成之前 RecyclerView 是不知道 adapterPosition 的,這時會返回-1(NO_POSITION)。
但如果用的是 notifyItemXX(),那立即就能獲取到正確的 adapterPosition,即使新的 Layout 還沒繪制完成。比如調用notifyItemInserted(0),之前是0的現在就會變成1,因為插入了0, 相當于 RecyclerView 提前幫你計算的,此時getLayoutPosition 還只能獲取到舊的值。
總的來說,大多數情況下用 getAdapterPosition,只要不用 notifyDataSetChanged() 來刷新數據就總能立即獲取到正確 position 值。