主頁 > 移動端開發 > 「RecyclerView中的位置」你真的會正確獲取Item的位置么?

「RecyclerView中的位置」你真的會正確獲取Item的位置么?

2021-08-16 11:25:09 移動端開發

關于Position

我們在使用使用 RecyclerView 的時候,總是不可避免的需要知道其 ItemView 的位置以實作各種各樣的需求:

  • 設定點擊事件:我們需要Item所處的位置,取得View對應的相關資料資訊,進而完成點擊的互動操作,比如一個商品串列,點擊商品的Item時,我們只有知道對應Item的位置,才能拿到Item的資料資訊(譬如商品ID)從而跳轉至正確的商品詳情頁面,
  • 滾動串列至指定的Item位置:這種場景常被應用于RecyclerView的Item選中態發生變化時,滾動RecyclerView的位置,使得當前選中的Item能被用戶可見,此時我們需知道Item相對于RecyclerView的位置,才有可能滾動RecyclerView至正確的位置,

既然位置對于我們日常開發這么重要,那么RecyclerView一定給我們提供了獲取位置的API,沒錯,RecyclerView是提供了獲取位置的方法,還不止一種:

  • onBindViewHolder(holder: ViewHolder, position: Int)
  • getAdapterPosition
  • getBindingAdapterPosition
  • getAbsoluteAdapterPosition
  • getLayoutPosition

你確定你知道他們的具體含義、使用場景以及他們之間的區別么?

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()被廢棄了,官網對此也有說明(官網很重要??):

getAdapterPostion is deprecated

用我那蹩腳的英語大致翻譯一下,就是谷歌覺得這個方法在 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的相對位置,還是絕對位置

相對位置 & 絕對位置?getBindindAdapterPosition 與 getAbsoluteAdapterPosition 的區別

此處的相對位置及絕對位置的叫法,并非官方叫法,而是參考檔案系統中的 相對路徑 和 絕對路徑,提出的一種類似概念,我們舉例說明什么是相對位置和絕對位置,如下圖中的例子:MergeAdapter里包含了A Adapter 和 B Adapter,在頁面的展示上,B 在 A 的后面,我們想獲取B中某一個元素b3的位置,此時的位置有兩種:b3在B中的位置,我把他叫做相對位置,以及b3在整個RecyclerView中所處的位置,我將其稱之為絕對位置,

官方提供的兩個方法getBindingAdapterPostiongetAbsoluteAdapterPosition就是用來獲取ViewHolder的相對位置和絕對位置的,

  • getBindingAdapterPosition將會回傳該ViewHolder相對于它系結的Adapter中的位置,即相對位置,
  • getAbsoluteAdapterPosition將會回傳該ViewHolder相對于RecyclerView的位置,即絕對位置,

回到我們文章開頭提到的兩種典型的RecyclerView中使用Position的場景:

設定點擊事件 & 記錄、操作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()
        }
    }

至于后者,很明顯,我們應該使用getAbsoluteAdapterPosition來操縱RecyclerView的滾動,

當然,如果你的專案完全沒有使用ConcatAdapter,那getBindingAdapterPostion和getAbsoluteAdapterPosition對于你來說,沒有任何區別,不過我仍推薦你按照不同的使用場景選用不同的方法獲取適合的位置引數,畢竟以后用不用ConcatAdapter 誰又說的清楚呢?

getLayoutPosition

那getLayoutPosition又是獲取什么位置的呢?什么場景下我們使用該api來獲取位置呢?

getLayoutPosition,顧名思義,就是獲取該ViewHolder在實際布局中的位置,我們都知道,RecyclerView使用LayoutManager來管理資料集的現實,當開發者呼叫notifyData*()等方法通知RecyclerView重繪UI時,出于性能的考慮,RecyclerView的UI并不會立刻重繪,和Data保持一致,而是通過LayoutManager惰性更新相關布局——這個程序伴隨著時間上的等待,通常情況下,這個等待時間小于16ms,所以,從感官上講,getLayoutPosition與getAbsoluteAdapterPosition十分相似:getAbsoluteAdapterPosition回傳的是該ViewHolder相對于RecyclerView的絕對位置,而getLayoutPosition回傳的是該ViewHolder相對于RecyclerView實際布局的絕對位置,

說具體點,就是adapter和layout的位置會有時間差(通常情況下<16ms), 如果你改變了Adapter的資料然后重繪視圖, layout需要過一段時間才會更新視圖, 在這段時間里面, 這兩個方法回傳的position會不一樣,

notifyDataSetChanged之后并不能馬上獲取Adapter中的position, 要等布局結束之后才能獲取到.

而對于Layout的position, 在notifyItemInserted之后, Layout不能馬上獲取到新的position, 因為布局還沒更新(需要<16ms的時間重繪視圖), 所以只能獲取到舊的, 但是Adapter中的position就可以馬上獲取到最新的position,

所以,對于上面的點擊事件的場景,我們在獲取用戶點擊位置的時候,使用getLayoutPosition可能效果更好,這樣,就能確保用戶點擊的始終是他看到的那個資料(消除16ms帶來的時間差問題),代碼可以改造成下面這樣:

override fun onBindViewHolder(holder: NumberHolder, position: Int) {
        holder.tvNumber.text = "Position: ${list[position]}"
        holder.itemView.setOnClickListener {
            Toast.makeText(it.context, "點擊了:${list[holder.layoutPosition]}", Toast.LENGTH_SHORT).show()
        }
    }

總結

  • 原始碼注釋很重要
  • 官網檔案很重要

遇到這種方法模棱兩可,讓人傻傻分不清楚的情況,作為API呼叫者的我們,需要我們做到的就是適當的閱讀原始碼注釋,結合官方檔案,正確理解他們各自所代表的含義以及可能帶來的影響,合理使用他們,

小編自己在學習提升時,順帶從網上收集整理了一些 Android 開發相關的學習檔案、面試題、Android 核心筆記等等檔案,希望能幫助到大家學習提升,如有需要參考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 訪問查閱,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/294008.html

標籤:其他

上一篇:Android 人臉識別

下一篇:一起了解Android開源框架&&決議okhttp框架任務核心類dispatcher

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more