RecyclerView 之 findFirstVisibleItemPosition 和 findLastVisibleItemPosition 踩坑

博主聲明:
轉載請在開頭附加本文鏈接及作者信息,並標記爲轉載。本文由博主 威威喵 原創,請多支持與指教。
本文首發於此 博主:威威喵 | 博客主頁:https://blog.csdn.net/smile_running

功能需求

好久沒寫博文了,最近的東西都寫在筆記上了,上班了,時間也不太多,就寫一點在項目中遇到的問題吧。
廢話不多說,我在開發中,遇到一個需求,一個類似餓了麼App的城市選擇器,界面的話,差不多就這樣子
在這裏插入圖片描述
其實,開起來這個功能不難,但是有很多坑需要踩,下面我就來介紹一下我踩過的坑吧。
由於博主之前也寫過類似的demo,只能算demo,而且當時還是用到 ListView 來處理的,所以在項目中,肯定要改爲 RecyclerView。這樣一改的話,之前沒遇到的坑,就撲面而來了。實現細節我就不多說了,可以看我之前寫的一篇文章,詳細的介紹瞭如何實現這種效果。自定義 View 之聯繫人字母索引及定位效果

踩坑一、RecyclerView 複用機制

首先呢,要從這個demo中,將ListView轉爲RecyclerView的方式,第一個需要踩的坑是字母分類,就是城市列表頭頂的一個字母 A…Z
按之前 adapter 的佈局文件來處理,如這樣的
在這裏插入圖片描述
我們適配器佈局,其實是兩個 TextView,然後頂部用於顯示字母,如果頂部的字母都是 A,或者相同的,我們就將後面的 A 隱藏,直到這個字母 A 開頭的城市結束,然後顯示 B 分類。
這樣在 ListView 的適配器中設置,可能沒什麼問題,我們獲取前一個 prePosition 和當前 curPosition 的字母,對比一下是否相同,如果相同,就隱藏 curPosition 的字母,就是將他的 visible 設置爲 GONE 即可。
而在 RecyclerView 中,這樣設置是會出現問題的,而導致的原因就是 RecyclerView 的複用機制,我給大家畫一張圖就明白了,代碼例如這樣的(僞代碼):


String preLetter

public void bindData(){
	String curLetter = item.getLetter()
	if(preLetter.eq(curLetter)){
		// ...
		tv.setVisible(GONE)
	}else{
		preLetter = item.getLetter()
	}
}

在這裏插入圖片描述
我們往下滾動列表,上面這樣寫的邏輯是沒有任何問題的。但是,如果列表往上滾動的話,就會產生問題,大家可以想象一下數據倒過來的情況,它造成的問題是這樣的:
在這裏插入圖片描述
Item 的複用,導致數據需要重新綁定,在 B 類字母開頭的城市過渡到 A 類時,直接將 A 類的最後一個城市頭上標記字母 A,而不是第一個。
所以呢,這就判斷邏輯肯定就不行,因爲邏輯複用問題,我的解決方案就是從數據源入手,在數據源進行區分數據,這裏就提供一個參考,我是這樣做的:在 CityBean 類中,添加一個屬性變量,用於區分類別,比如添加一個 private string letter 變量,我們在數據源的時候,就將數據做一個區分。例如,A 類字母開頭的城市,爲第一個 letter 屬性賦值爲 “A”,其他同一A類的城市,就賦值爲 “” 或者 NuLL 都可以,這樣的話,我們就將數據源給定死了,我就不管你 item 愛怎麼複用,我 CityBean 中含 letter 屬性有值的,就將 TextView 設置 Visiable,沒有的話,就直接 GONE 掉即可。

踩坑二、scrollToPosition()

這個方法想必大家都熟悉,將 RecyclerView item 定位到某一個 position 顯示,那這個方法怎麼就變成坑了呢。其實,還是從 listView 的setSelection(int position) 這個方法說起。setSelection(10) 的話,其實就是將 item 顯示在第 10 條,並且 item 處於當前屏幕的頂部。然而,recyclerView 的 scrollToPosition(int position)方法並不是這樣子,直接看圖吧:
在這裏插入圖片描述
仔細看圖,列表往下滑動的時候,字母分類跑到了底部去了,往上滑動是正常的。仔細觀察,往下滑動的時候,它每次其實是偏移了一個屏幕可見的 item 數量,這就想起來 LayoutManager 中的 findFirstVisible 和 findLastVisible 方法了,既然你偏移了一個可見區域的 item 數量,那我就用
findLastVisible - findFirstVisible 不就行了嗎。可是,實時真的可以嗎?

踩坑三、findLastVisibleItemPosition、findFirstVisibleItemPosition

因爲我是在模擬器上運行的,發現這樣一減的操作,別說,還真管用。後來,由於要發佈到手機上測試一下,發現就崩了。
什麼情況,我明明算好了可是區域的 item 數量,怎麼又這樣了?
在這裏插入圖片描述
很疑惑,我又看了代碼,是這樣寫的

        cityIndex.setOverlayTextView(tvLetter)
        var prePosition = 0
        cityIndex.setOnIndexChangedListener { letter, position ->
            cityData.forEachIndexed { index, cityPicker ->
                if (letter.equals(cityPicker.letter)) {
                    if (prePosition > position) {
                        rvCityPicker.scrollToPosition(index + 1)
                    } else if (prePosition < position) {
                        rvCityPicker.scrollToPosition(index + (manager.findLastVisibleItemPosition() - manager.findFirstVisibleItemPosition()))
                    }
                    prePosition = position
                }
            }
        }

一臉懵逼,然後就去 google 了一下,發現 findFirstVisibleItemPosition 和 findLastVisibleItemPosition 居然存在在偏差問題
在這裏插入圖片描述
具體我是看了這篇文章作者寫的 https://www.jianshu.com/p/ff6082c0867e
後來我覺得這種決解方式太麻煩了,於是又找到了一種更好的解決方法。什麼?LayoutManager 已經爲我們處理了這種情況了!只需要一行代碼即可:

        cityIndex.setOverlayTextView(tvLetter)
        cityIndex.setOnIndexChangedListener { letter, position ->
            cityData.forEachIndexed { index, cityPicker ->
                if (letter.equals(cityPicker.letter)) {
                    manager.scrollToPositionWithOffset(index + 1, 0)
                }
            }
        }

好吧,還真沒用過這個方法。下面來介紹一下這個方法吧

    public void scrollToPositionWithOffset(int position, int offset) {
        mPendingScrollPosition = position;
        mPendingScrollPositionOffset = offset;
        if (mPendingSavedState != null) {
            mPendingSavedState.invalidateAnchor();
        }
        requestLayout();
    }

第一個 position 就很容易懂了,就是我們要移動的 item position,第二個參數是:item 項的開始邊緣與 recyclerview 之間的距離(以像素爲單位),我們設置爲 0 即可。
通過這個方法,RecyclerView 可以愉快的讓我們滾動到 position 並且顯示在頂部了,也解決了通過自己計算 findFirstVisibleItemPosition 和 findLastVisibleItemPosition 偏移問題。來看看效果吧
在這裏插入圖片描述
好了。本篇文章基本就結束了,目前已經踩的坑基本就這些了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章