1. 程式人生 > >再說Android RecyclerView區域性重新整理那個坑

再說Android RecyclerView區域性重新整理那個坑

關鍵:public final void notifyItemChanged(int position, Object payload)

RecyclerView區域性重新整理大家都遇到過,有時候還說會遇見圖片閃爍的問題。

優化之前的效果:

這裡寫圖片描述

優化之後的效果:

這裡寫圖片描述

如果想單獨更新一個item,我們通常會這樣做,程式碼如下:

mLRecyclerViewAdapter.notifyItemChanged(position);

這裡的position就是那個列表項的索引,呼叫這個方法可以更新一個Item的UI(當然,你要是直接呼叫notifyDataSetChanged()方法也可以,但這樣會造成其他不需要更新的item也會重新整理)。

即便如此,圖片閃爍還是出現了,什麼原因引起來的呢,這裡猜測可以有如下幾個原因:

  1. 流傳甚為廣泛的一種說法,imageView的寬高不固定導致的(wrap_content)?

  2. 是RecyclerView自帶的更新動畫效果導致的?

  3. 是因為圖片載入框架(glide 的 animte)的動畫效果導致的?

  4. getView中(RecyclerView中是onBindViewHolder)載入圖片的時候,設定一個tag,當發現這個imageView的tag和之前的tag一致時就不載入。

這裡我們不再對上面的原因進行具體的分析,針對上面可能引起閃爍的原因進行一一驗證後的結果是令人感到失望的:都不是引起圖片閃爍的根本原因。

那麼怎麼解決這個圖片閃爍的問題呢?通過檢視api,我們發現了另一個方法:

這裡寫圖片描述

重點看payload引數介紹:

payload Optional parameter, use null to identify a "full" update

翻譯過來就是如果payload引數是null,那麼就會來一個“完整的”更新,也就是說會全部更新。

我們再看一下mLRecyclerViewAdapter.notifyItemChanged(position)的原始碼:

這裡寫圖片描述

從原始碼中看到,notifyItemChanged(position)呼叫了 notifyItemRangeChanged(int positionStart, int itemCount)

方法,原始碼如下:

這裡寫圖片描述

notifyItemRangeChanged(int positionStart, int itemCount)方法最終還是呼叫了notifyItemRangeChanged(int positionStart, int itemCount, Object payload)方法,只是payload引數是null。

那麼如果payload傳一個不為null的引數,就可以實現對列表項中的具體控制元件更新了嗎?我們通過程式碼驗證下。

模擬更新一條資料:

這裡寫圖片描述

這裡,我們將payload引數賦值為”jdsjlzx”,當然你也可以賦值為其他值,只要不空就行。

重寫adapter中的onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads)方法:

這裡寫圖片描述

如果payloads列表不是空的,如上圖所示,你就可以在else程式碼塊裡面重新整理你想更新的控制元件了(記得不需要更新的控制元件就不要寫在這裡了)。

注意:

總結

由此看來,RecyclerView做區域性重新整理還是非常容易的,其實就是使用好帶payload引數的這個notifyItemChanged方法,以及重寫帶payload的這個onBindViewHolder方法,在onBindViewHolder中去重新整理你想更新的控制元件即可。

PS:

拿朋友圈來說,我發一張照片,這就是一個item,但這個item裡還要加上贊和評論。
當我有評論和贊要重新整理時,我需要判斷當前要改動的item是否是螢幕中的可見位置。如果是,通過呼叫帶payload引數的這個notifyItemChanged方法更新item,就能達到只刷贊或者只刷評論,而不用重新載入照片(也就是圖片閃爍的原因)的效果。

怎麼判斷當前position是位於螢幕中呢?下面給出參考程式碼:

private void doAnim(int position) {
        int firstItemPosition = layoutManager.findFirstVisibleItemPosition();
        if (position - firstItemPosition >= 0) {
            //得到要更新的item的view
            View view = mRecyclerView.getChildAt(position - firstItemPosition + 1);
            if (null != mRecyclerView.getChildViewHolder(view)) {
                ProductsViewHolder viewHolder = (ProductsViewHolder) mRecyclerView.getChildViewHolder(view);

                //do something

            }

        }

    }

上面程式碼同時也獲取到了ViewHolder檢視,有了ViewHolder,你還可以做其他操作哦(比如item動畫效果)。