欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android的RV列表刷新詳解Payload與Diff方式異同

 更新時(shí)間:2022年10月21日 11:06:21   作者:newki  
這篇文章主要為大家介紹了Android的RV列表刷新詳解Payload與Diff方式異同,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

RecyclerView是我們常用的列表控件,一般來(lái)說(shuō)當(dāng)Item的數(shù)據(jù)改變的時(shí)候我們需要刷新當(dāng)前的Item 。

如何刷新 RV 的列表?基本上有這幾種方式:

notifyDataSetChanged()

notifyItemChanged(int position)

notifyItemChanged(int position, @Nullable Object payload)

一般來(lái)說(shuō)一個(gè) item 是由多個(gè)控件組成的,如 Button、CheckBox、TextView、ImageView、ViewGroup 等組合。當(dāng)我們點(diǎn)擊item的某個(gè)控件時(shí),RV 需要重新計(jì)算布局、刷新視圖來(lái)響應(yīng)交互。假設(shè)一個(gè) item 包含了N多個(gè)控件,如果調(diào)用notifyItemChanged(int position) 時(shí),item 中的每個(gè)控件都需要重新布局顯示,無(wú)形中加大了內(nèi)存和性能的損耗。

就算我們不考慮內(nèi)存和性能的問(wèn)題,那么一些效果也是我們無(wú)法接受的,當(dāng) item 刷新的時(shí)候會(huì)導(dǎo)致內(nèi)部的圖片或item 出現(xiàn)一次閃爍。

以我的精神糧食起點(diǎn)為例,如下

所以我們才需要用到類似 Payload 與 Diff 之類的刷新方式。

下面我們就一起看看它們是怎么使用的。

一、Payload的刷新

我們通過(guò) notifyItemChanged(int position, @Nullable Object payload) 來(lái)刷新指定索引的item。

在 RV 的 Adapter 中的 onBindViewHolder 可以接收到 payloads 參數(shù),這個(gè) payloads 參數(shù)是一個(gè) List 對(duì)象,該對(duì)象不是 null 但可能是空的。

public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
    onBindViewHolder(holder, position);
}

通過(guò) Adapter 的 notifyXXX 函數(shù)的帶有 payload 參數(shù)的函數(shù)可以設(shè)置 payload 對(duì)象,如:

public final void notifyItemChanged(int position, Object payload) {
    mObservable.notifyItemRangeChanged(position, 1, payload);
}

由于 onBindViewHolder 有重載的方法,如果使用了 payloads 的方式,那么我們需要做兼容,如果沒(méi)有 payloads 就去走整個(gè) item 的刷新,如果有 payloads 那么我們就根據(jù)指定的 payload 去刷新指定的數(shù)據(jù)。

@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
    if (!payloads.isEmpty() && payloads.get(0).equals("like")) {
        //如果是我們指定的 payloads ,那么就可以指定刷新
        TipsBean item = mData.get(position);
        holder.tvLikeNum.setText(R.id.tv_tips_like_num, item.likes_count > 0 ? item.likes_count + "" : "Like");
        holder.tvLikeNum.setTextColor(R.id.tv_tips_like_num, item.likes_count > 0 ? CommUtils.getColor(R.color.black) : CommUtils.getColor(R.color.home_item_text_light_gray));
    } else {
       // 如果沒(méi)有 payloads ,或者不是我們指定的,還是返回默認(rèn)的整個(gè)刷新
       super.onBindViewHolder(holder, position);
    }
}

如果沒(méi)有 payload ,當(dāng)調(diào)用 notifyItemChanged 時(shí),RV 會(huì)通過(guò)回調(diào) onBindViewHolder(holder, position) 來(lái)更新當(dāng)前數(shù)據(jù)變化的 item ,此時(shí)會(huì)觸發(fā) 整個(gè) item 中 view 的重新布局和計(jì)算位置,這樣的話只要是其中一個(gè) View 狀態(tài)變化了,最終會(huì)導(dǎo)致整個(gè) item 都需要重新布局一遍。

例如上述的例子,在評(píng)論的列表中,我只點(diǎn)贊了第一個(gè) item ,我們就通過(guò) payload 來(lái)告訴 RV 這個(gè) item 中的 like 文本變化了,那么我們就只需要處理 Like 文本的變化。

二、Diff的刷新與快速實(shí)現(xiàn)方法

每一個(gè) payloads 都要寫(xiě)一次,然后我們?cè)谡{(diào)用 notifyItemChanged 時(shí)萬(wàn)一寫(xiě)錯(cuò)了怎么辦?有沒(méi)有一種方式能讓程序自動(dòng)管理,讓程序幫我們記錄 payloads ? 最好還能幫助我們自動(dòng)排序!

有,我們先看看自動(dòng)排序的方式:SortedList 的方式

SortedList,顧名思義就是排序列表,它適用于列表有序且不重復(fù)的場(chǎng)景。并且SortedList會(huì)幫助你比較數(shù)據(jù)的差異,定向刷新數(shù)據(jù)。而不是簡(jiǎn)單粗暴的notifyDataSetChanged()。

例如我們定義一個(gè)城市排序的對(duì)象:

public class City {
    private int id;
    private String cityName;
    private String firstLetter;
    ...
}

我們需要進(jìn)行排序的規(guī)則定義

public class SortedListCallback extends SortedListAdapterCallback<City> {
    public SortedListCallback(RecyclerView.Adapter adapter) {
        super(adapter);
    }
    /**
     * 排序條件
     */
    @Override
    public int compare(City o1, City o2) {
        return o1.getFirstLetter().compareTo(o2.getFirstLetter());
    }
    /**
     * 用來(lái)判斷兩個(gè)對(duì)象是否是相同的Item。
     */
    @Override
    public boolean areItemsTheSame(City item1, City item2) {
        return item1.getId() == item2.getId();
    }
    /**
     * 用來(lái)判斷兩個(gè)對(duì)象是否是內(nèi)容的Item。
     */
    @Override
    public boolean areContentsTheSame(City oldItem, City newItem) {
        if (oldItem.getId() != newItem.getId()) {
            return false;
        }
        return oldItem.getCityName().equals(newItem.getCityName());
    }
}

再然后在Adapte中使用的時(shí)候,不需要Arrylist,要用排序的集合,新的對(duì)象SortedList排序集合。

public class SortedAdapter extends RecyclerView.Adapter<SortedAdapter.ViewHolder> {
    // 數(shù)據(jù)源使用SortedList
    private SortedList<City> mSortedList;
    private LayoutInflater mInflater;
    public SortedAdapter(Context mContext) {
        mInflater = LayoutInflater.from(mContext);
    }
    public void setSortedList(SortedList<City> mSortedList) {
        this.mSortedList = mSortedList;
    }
    /**
     * 批量更新操作,例如:
     * <pre>
     *     mSortedList.beginBatchedUpdates();
     *     try {
     *         mSortedList.add(item1)
     *         mSortedList.add(item2)
     *         mSortedList.remove(item3)
     *         ...
     *     } finally {
     *         mSortedList.endBatchedUpdates();
     *     }
     * </pre>
     */
    public void addData(List<City> mData) {
        mSortedList.beginBatchedUpdates();
        mSortedList.addAll(mData);
        mSortedList.endBatchedUpdates();
    }
    /**
     * 移除item
     */
    public void removeData(int index) {
        mSortedList.removeItemAt(index);
    }
    /**
     * 清除集合
     */
    public void clear() {
        mSortedList.clear();
    }
    @Override
    @NonNull
    public SortedAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }
    @Override
    public void onBindViewHolder(@NonNull SortedAdapter.ViewHolder holder, final int position) {
      // 。。。
    }
    @Override
    public int getItemCount() {
        return mSortedList.size();
    }
    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View itemView) {
            super(itemView);
        }
    }
}

使用的時(shí)候:

    RecyclerView mRecyclerView = findViewById(R.id.rv);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    mSortedAdapter = new SortedAdapter(this);
    // SortedList初始化
    SortedListCallback mSortedListCallback = new SortedListCallback(mSortedAdapter);
    SortedList mSortedList = new SortedList<>(City.class, mSortedListCallback);
    mSortedAdapter.setSortedList(mSortedList);
    mRecyclerView.setAdapter(mSortedAdapter);
   //添加數(shù)據(jù)
   ...

這樣確實(shí)就能實(shí)現(xiàn)自動(dòng)排序,刷新列表了,相對(duì) notifyDataSetChanged 的暴力刷新,優(yōu)雅一點(diǎn)。但是它沒(méi)有 payload 的功能,這個(gè)刷新只是刷新的是整個(gè) RV 中的部分Item,但還是刷新整個(gè) item 啊。

有沒(méi)有辦法既能排序又能 payload差異化刷新的方式呢?

肯定有哇, DiffUtil 的方式就此誕生,常用的相關(guān)的幾個(gè)類為 DiffUtil AsyncListDiffer ListAdapter(用于快速實(shí)現(xiàn)的封裝類)

DiffUtil 能實(shí)現(xiàn)排序加payload局部刷新的功能:

  • 當(dāng)某個(gè) item 的位置變化,觸發(fā)排序邏輯,有移除和添加的動(dòng)畫(huà)。
  • 當(dāng)某個(gè) item 的位置不變,內(nèi)容變化,觸發(fā) payload 局部刷新。
  • 在子線程中計(jì)算DiffResult,在主線程中刷新RecyclerView。

AsyncListDiffer 又是什么東西,為什么需要它。

其實(shí) AsyncListDiffer 就是集成了 AsyncListUtil + DiffUtil 的功能,由于 DiffUtil在計(jì)算數(shù)據(jù)差異 DiffUtil.calculateDiff(mDiffCallback) 是一個(gè)耗時(shí)操作,需要我們放到子線程去處理,最后在主線程刷新,為了我們開(kāi)發(fā)者更加的方便,谷歌直接提供了 AsyncListDiffer 方便我們直接使用??磥?lái)谷歌是怕我們開(kāi)發(fā)者不會(huì)使用子線程,直接給我們寫(xiě)好了。

ListAdapter 又是個(gè)什么鬼?怎么越來(lái)越復(fù)雜了?

其實(shí)谷歌就喜歡把簡(jiǎn)單的東西復(fù)雜化,如果我們使用 AsyncListDiffer 去實(shí)現(xiàn)的話,雖然不用我們操心子線程了,但是還是需要我們定義對(duì)象、集合、添加數(shù)據(jù)的方法 ,如addNewData ,里面調(diào)用 mDiffer.submitList() 才能實(shí)現(xiàn)。

谷歌還是怕我們不會(huì)用吧!直接把 AsyncListDiffer 的使用都給簡(jiǎn)化了,直接提供了 ListAdapter 包裝類,內(nèi)部對(duì) AsyncListDiffer 的使用做了一系列的封裝,使用的時(shí)候我們的 RV-Adapter 直接繼承 ListAdapter 即可實(shí)現(xiàn) AsyncListDiffer 的功能,內(nèi)部連設(shè)置數(shù)據(jù)的方法都給我們提供好了。谷歌真的是我們的好爸爸!為我們開(kāi)發(fā)者操碎了心。

那它們都到底怎么使用呢?

不管什么方式,我們都需要定義好自己的 DiffUtil.CallBack,畢竟就算讓程序幫我們排序和差分,我們也得告訴程序排序的規(guī)則和diff的規(guī)則,是吧!

public class MyDiffUtilItemCallback extends DiffUtil.ItemCallback<TestBean> {
 	/**
     * 是否是同一個(gè)對(duì)象
     */  
    @Override
    public boolean areItemsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getId() == newItem.getId();
    }
 	/**
     * 是否是相同內(nèi)容
     */ 
    @Override
    public boolean areContentsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getName().equals(newItem.getName());
    }
	/**
     * areItemsTheSame()返回true而areContentsTheSame()返回false時(shí)調(diào)用,也就是說(shuō)兩個(gè)對(duì)象代表的數(shù)據(jù)是一條,
     * 但是內(nèi)容更新了。此方法為定向刷新使用,可選。
     */
    @Nullable
    @Override
    public Object getChangePayload(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        Bundle payload = new Bundle();
        if (!oldItem.getName().equals(newItem.getName())) {
            payload.putString("KEY_NAME", newItem.getName());
        }
        if (payload.size() == 0){
            //如果沒(méi)有變化 就傳空
            return null;
        }
        return payload;
    }
}

那個(gè)Diff的具體實(shí)現(xiàn)我們選用哪一種方案呢?其實(shí)三種方式都是可以實(shí)現(xiàn)的,這里我們先使用 AsyncListDiffer 的方式來(lái)實(shí)現(xiàn)。

上面關(guān)于 AsyncListDiffer 的介紹我們說(shuō)過(guò)了,雖然不需要我們實(shí)現(xiàn)異步操作了,但是我們還是需要實(shí)現(xiàn)對(duì)象、集合、添加數(shù)據(jù)的方法等。

示例如下:

public class AsyncListDifferAdapter extends RecyclerView.Adapter<AsyncListDifferAdapter.ViewHolder> {
    private LayoutInflater mInflater;
    // 數(shù)據(jù)的操作由AsyncListDiffer實(shí)現(xiàn)
    private AsyncListDiffer<TestBean> mDiffer;
    public AsyncListDifferAdapter(Context mContext) {
    	// 初始化AsyncListDiffe
        mDiffer = new AsyncListDiffer<>(this, new MyDiffUtilItemCallback());
        mInflater = LayoutInflater.from(mContext);
    }
    //添加數(shù)據(jù)傳對(duì)象和對(duì)象集合都可以
    public void addData(TestBean mData){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.add(mData);
        mDiffer.submitList(mList);
    }
    public void addData(List<TestBean> mData){
    	// 由于DiffUtil是對(duì)比新舊數(shù)據(jù),所以需要?jiǎng)?chuàng)建新的集合來(lái)存放新數(shù)據(jù)。
    	// 實(shí)際情況下,每次都是重新獲取的新數(shù)據(jù),所以無(wú)需這步。
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        mDiffer.submitList(mList);
    }
    //刪除數(shù)據(jù),要先獲取全部集合,再刪除指定的集合,再提交刪除之后的集合
    public void removeData(int index){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.remove(index);
        mDiffer.submitList(mList);
    }
    public void clear(){
        mDiffer.submitList(null);
    }
    @Override
    @NonNull
    public AsyncListDifferAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }
    @Override
    public void onBindViewHolder(@NonNull AsyncListDifferAdapter.ViewHolder holder, final int position) {
        TestBean bean = mDiffer.getCurrentList().get(position);
        holder.mTvName.setText(bean.getName());
    }
    @Override
    public int getItemCount() {
        return mDiffer.getCurrentList().size();
    }
    static class ViewHolder extends RecyclerView.ViewHolder {
       ......
    }
}

由于 Diff.Callback 我們已經(jīng)在 Adapter 內(nèi)部已經(jīng)初始化了,所以使用的時(shí)候我們直接像普通的 RV 設(shè)置 Adapter 一樣即可。

在更新數(shù)據(jù)的時(shí)候我們使用 Adapter 定義的 addDataremoveData 即可完成Diff刷新。

使用 ListAdapter 會(huì)怎樣?

如果覺(jué)得使用 AsyncListDiffer 都嫌棄麻煩的話,我們直接使用 ListAdapter 也能實(shí)現(xiàn)。

由于我們還是需要一個(gè)List集合去保存我們的數(shù)據(jù),我們就能對(duì) ListAdapter 再做一個(gè)簡(jiǎn)單的基類封裝。

public abstract class BaseRVDifferAdapter<T, VH extends RecyclerView.ViewHolder> extends ListAdapter<T, VH> {
    protected Context mContext;
    protected LayoutInflater mInflater;
    protected List<T> mDatas = new ArrayList<>();
    public BaseRVDifferAdapter(Context context, DiffUtil.ItemCallback<T> callback) {
        super(callback);
        mContext = context;
        mInflater = LayoutInflater.from(mContext);
    }
    //設(shè)置數(shù)據(jù)源
    protected void setData(List<T> list) {
        mDatas.clear();
        mDatas.addAll(list);
        List<T> mList = new ArrayList<>();
        mList.addAll(mDatas);
        submitList(mList);
    }
    //添加數(shù)據(jù)源
    protected void addData(List<T> list) {
        mDatas.addAll(list);
        List<T> mList = new ArrayList<>();
        mList.addAll(mDatas);
        submitList(mList);
    }
    //刪除制定索引數(shù)據(jù)源
    protected void removeData(int index) {
        mDatas.remove(index);
        List<T> mList = new ArrayList<>();
        mList.addAll(mDatas);
        submitList(mList);
    }
    //清除全部數(shù)據(jù)
    protected void clear() {
        mDatas.clear();
        submitList(null);
    }
    //獲取adapter維護(hù)的數(shù)據(jù)集合
    protected List<T> getAdapterData() {
        return mDatas;
    }
}

我們實(shí)現(xiàn)Adater的方法就如下:

public class MyListAdapter extends BaseRVDifferAdapter<TestBean, MyListAdapter.ViewHolder> {
    public MyListAdapter(Context context) {
        super(context, new MyDiffUtilItemCallback());
    }
    @Override
    @NonNull
    public MyListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }
    @Override
    public void onBindViewHolder(@NonNull MyListAdapter.ViewHolder holder, final int position) {
        TestBean bean = getItem(position);
        holder.mTvName.setText(bean.getName());
    }
    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView mTvName;
        ViewHolder(View itemView) {
            super(itemView);
            mTvName = itemView.findViewById(R.id.tv_name);
        }
    }
}

在我們自己的 Adpater 中,我們還是需要初始化我們自己的 Diff.Callback的。那么在使用的時(shí)候也就和普通的RV設(shè)置 Adapter 是一樣的。

如下:

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sorted_list);
        RecyclerView mRecyclerView = findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAsyncListDifferAdapter = new AsyncListDifferAdapter(this);
        mRecyclerView.setAdapter(mAsyncListDifferAdapter);
        initData();
    }
    private void addData() {
        List<TestBean> mList = new ArrayList();
        for (int i = 10; i < 20; i++){
            mList.add(new TestBean(i, "Item " + i));
        }
        mMyListAdapter.addData(mList);
    }
    private void initData() {
        List<TestBean> mList = new ArrayList();
        for (int i = 0; i < 10; i++){
            mList.add(new TestBean(i, "Item " + i));
        }
        mMyListAdapter.setData(mList);
    }
    private void updateData() {
        List<TestBean> mList = new ArrayList();
        for (int i = 20; i < 30; i++){
            mList.add(new TestBean(i, "Item " + i));
        }
        mMyListAdapter.addData(mList);
    }

是不是可以無(wú)腦使用Diff的方式呢?也不是,當(dāng)一些普通長(zhǎng)列表我們使用默認(rèn)的RV-Adapter就行了,加載更多,往源數(shù)據(jù)上面加數(shù)據(jù),并不涉及到很多數(shù)據(jù)源改變的也沒(méi)必要使用Diff。

那么哪里使用?比如IM的會(huì)話列表,長(zhǎng)期變動(dòng)的數(shù)據(jù),比如評(píng)論區(qū)域熱評(píng)區(qū)的數(shù)據(jù)切換時(shí),比如我們的Android掘金App:

直接暴力的切換數(shù)據(jù)源然后 notifyDataSetChanged ,這樣就會(huì)整個(gè)頁(yè)面閃爍。而對(duì)于這些特定的一些場(chǎng)景,我們使用 Diff 的功能,就會(huì)感覺(jué)更加的流暢,體驗(yàn)會(huì)好一點(diǎn)哦!

三、DiffUtil的封裝

既然 AsyncListDiffer 和 ListAdapter 都是快速實(shí)現(xiàn)的方式,那我們直接使用基本 DiffUtil 行不行?

當(dāng)然可以,我不想直接使用 ListAdapter ,我就想用 RV.Adapter ,因?yàn)槲矣衅渌姆庋b與擴(kuò)展,我自己會(huì)使用異步線程,我不需要你幫我管理,我不需要使用你的 AsyncListDiffer 幫我管理我的 List 對(duì)象 。

我們可以直接使用基本的 DiffUtil 。真實(shí)使用下來(lái)也不是很復(fù)雜,只需要做一點(diǎn)點(diǎn)的封裝也能很方便的實(shí)現(xiàn)邏輯。

先寫(xiě)一個(gè)Differ的配置文件,內(nèi)部可配置主線程,異步線程,和必備的DiffUtil.Callback:

class MyAsyncDifferConfig <T>(
    @SuppressLint("SupportAnnotationUsage")
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    val mainThreadExecutor: Executor?,
    val backgroundThreadExecutor: Executor,
    val diffCallback: DiffUtil.ItemCallback<T>) {
    class Builder<T>(private val mDiffCallback: DiffUtil.ItemCallback<T>) {
        companion object {
            private val sExecutorLock = Any()
            private var sDiffExecutor: Executor? = null
        }
        private var mMainThreadExecutor: Executor? = null
        private var mBackgroundThreadExecutor: Executor? = null
        fun setMainThreadExecutor(executor: Executor?): Builder<T> {
            mMainThreadExecutor = executor
            return this
        }
        fun setBackgroundThreadExecutor(executor: Executor?): Builder<T> {
            mBackgroundThreadExecutor = executor
            return this
        }
        fun build(): MyAsyncDifferConfig<T> {
            if (mBackgroundThreadExecutor == null) {
                synchronized(sExecutorLock) {
                    if (sDiffExecutor == null) {
                        sDiffExecutor = Executors.newSingleThreadExecutor()
                    }
                }
                mBackgroundThreadExecutor = sDiffExecutor
            }
            return MyAsyncDifferConfig(
                mMainThreadExecutor,
                mBackgroundThreadExecutor!!,
                mDiffCallback)
        }
    }
}

重點(diǎn)是定義一個(gè)自己的AsyncDiffer,內(nèi)部使用 DiffUtil 來(lái)計(jì)算差分。

class MyAsyncDiffer<T>(
    private val adapter: RecyclerView.Adapter<*>,
    private val config: MyAsyncDifferConfig<T>
) {
    private val mUpdateCallback: ListUpdateCallback = AdapterListUpdateCallback(adapter)
    private var mMainThreadExecutor: Executor = config.mainThreadExecutor ?: MainThreadExecutor()
    private val mListeners: MutableList<ListChangeListener<T>> = CopyOnWriteArrayList()
    private var mMaxScheduledGeneration = 0
    private var mList: List<T>? = null
    private var mReadOnlyList = emptyList<T>()
    private class MainThreadExecutor internal constructor() : Executor {
        val mHandler = Handler(Looper.getMainLooper())
        override fun execute(command: Runnable) {
            mHandler.post(command)
        }
    }
    fun getCurrentList(): List<T> {
        return mReadOnlyList
    }
    @JvmOverloads
    fun submitList(newList: MutableList<T>?, commitCallback: Runnable? = null) {
        val runGeneration: Int = ++mMaxScheduledGeneration
        if (newList == mList) {
            commitCallback?.run()
            return
        }
        val previousList = mReadOnlyList
        if (newList == null) {
            val countRemoved = mList?.size ?: 0
            mList = null
            mReadOnlyList = emptyList()
            mUpdateCallback.onRemoved(0, countRemoved)
            onCurrentListChanged(previousList, commitCallback)
            return
        }
        if (mList == null) {
            mList = newList
            mReadOnlyList = Collections.unmodifiableList(newList)
            mUpdateCallback.onInserted(0, newList.size)
            onCurrentListChanged(previousList, commitCallback)
            return
        }
        val oldList: List<T> = mList as List<T>
        config.backgroundThreadExecutor.execute {
            val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                override fun getOldListSize(): Int {
                    return oldList.size
                }
                override fun getNewListSize(): Int {
                    return newList.size
                }
                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldItem: T? = oldList[oldItemPosition]
                    val newItem: T? = newList[newItemPosition]
                    return if (oldItem != null && newItem != null) {
                        config.diffCallback.areItemsTheSame(oldItem, newItem)
                    } else oldItem == null && newItem == null
                }
                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    val oldItem: T? = oldList[oldItemPosition]
                    val newItem: T? = newList[newItemPosition]
                    if (oldItem != null && newItem != null) {
                        return config.diffCallback.areContentsTheSame(oldItem, newItem)
                    }
                    if (oldItem == null && newItem == null) {
                        return true
                    }
                    throw AssertionError()
                }
                override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
                    val oldItem: T? = oldList[oldItemPosition]
                    val newItem: T? = newList[newItemPosition]
                    if (oldItem != null && newItem != null) {
                        return config.diffCallback.getChangePayload(oldItem, newItem)
                    }
                    throw AssertionError()
                }
            })
            mMainThreadExecutor.execute {
                if (mMaxScheduledGeneration == runGeneration) {
                    latchList(newList, result, commitCallback)
                }
            }
        }
    }
    private fun latchList(
        newList: MutableList<T>,
        diffResult: DiffUtil.DiffResult,
        commitCallback: Runnable?
    ) {
        val previousList = mReadOnlyList
        mList = newList
        mReadOnlyList = Collections.unmodifiableList(newList)
        diffResult.dispatchUpdatesTo(mUpdateCallback)
        onCurrentListChanged(previousList, commitCallback)
    }
    private fun onCurrentListChanged(
        previousList: List<T>,
        commitCallback: Runnable?
    ) {
        for (listener in mListeners) {
            listener.onCurrentListChanged(previousList, mReadOnlyList)
        }
        commitCallback?.run()
    }
    //定義接口
    interface ListChangeListener<T> {
        fun onCurrentListChanged(previousList: List<T>, currentList: List<T>)
    }
    fun addListListener(listener: ListChangeListener<T>) {
        mListeners.add(listener)
    }
    fun removeListListener(listener: ListChangeListener<T>) {
        mListeners.remove(listener)
    }
    fun clearAllListListener() {
        mListeners.clear()
    }
}

然后我們可以用委托的方式配置,可以讓普通的 RecyclerView.Adapter 也能通過(guò)配置的方式選擇是否使用Differ。

實(shí)現(xiàn)我們的控制類接口

//設(shè)置別名簡(jiǎn)化
typealias IDiffer<T> = IMyDifferController<T>
fun <T> differ(): MyDifferController<T> = MyDifferController()
interface IMyDifferController<T> {
    fun RecyclerView.Adapter<*>.initDiffer(config: MyAsyncDifferConfig<T>): MyAsyncDiffer<T>
    fun getDiffer(): MyAsyncDiffer<T>?
    fun getCurrentList(): List<T>
    fun setDiffNewData(list: MutableList<T>, commitCallback: Runnable? = null)
    fun addDiffNewData(list: MutableList<T>, commitCallback: Runnable? = null)
    fun addDiffNewData(t: T, commitCallback: Runnable? = null)
    fun removeDiffData(index: Int)
    fun clearDiffData()
    fun RecyclerView.Adapter<*>.onCurrentListChanged(previousList: List<T>, currentList: List<T>)
}

在對(duì)控制類接口實(shí)例化,做一些具體的操作邏輯

class MyDifferController<T> : IMyDifferController<T> {
    private var mDiffer: MyAsyncDiffer<T>? = null
    override fun RecyclerView.Adapter<*>.initDiffer(config: MyAsyncDifferConfig<T>): MyAsyncDiffer<T> {
        mDiffer = MyAsyncDiffer(this, config)
        val mListener: MyAsyncDiffer.ListChangeListener<T> = object : MyAsyncDiffer.ListChangeListener<T> {
            override fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) {
                this@initDiffer.onCurrentListChanged(previousList, currentList)
            }
        }
        mDiffer?.addListListener(mListener)
        return mDiffer!!
    }
    override fun getDiffer(): MyAsyncDiffer<T>? {
        return mDiffer
    }
    override fun getCurrentList(): List<T> {
        return mDiffer?.getCurrentList() ?: emptyList()
    }
    override fun setDiffNewData(list: MutableList<T>, commitCallback: Runnable?) {
        mDiffer?.submitList(list, commitCallback)
    }
    override fun addDiffNewData(list: MutableList<T>, commitCallback: Runnable?) {
        val newList = mutableListOf<T>()
        newList.addAll(mDiffer?.getCurrentList() ?: emptyList())
        newList.addAll(list)
        mDiffer?.submitList(newList, commitCallback)
    }
    override fun addDiffNewData(t: T, commitCallback: Runnable?) {
        val newList = mutableListOf<T>()
        newList.addAll(mDiffer?.getCurrentList() ?: emptyList())
        newList.add(t)
        mDiffer?.submitList(newList, commitCallback)
    }
    override fun removeDiffData(index: Int) {
        val newList = mutableListOf<T>()
        newList.addAll(mDiffer?.getCurrentList() ?: emptyList())
        newList.removeAt(index)
        mDiffer?.submitList(newList)
    }
    override fun clearDiffData() {
        mDiffer?.submitList(null)
    }
    override fun RecyclerView.Adapter<*>.onCurrentListChanged(previousList: List<T>, currentList: List<T>) {
    }
}

到此我們就能封裝一個(gè)DiffUtil的工具類了,我們可以選擇是否啟用Diff,例如我們不使用Diff,我們使用Adapter就是一個(gè)普通的Adaper

class MyDiffAdapter() : RecyclerView.Adapter<BaseViewHolder>() {
    private val mDatas = arrayListOf<DemoDiffBean>()
    fun addData(list :List<DemoDiffBean>) {
        mDatas.addAll(list)
        notifyDataSetChanged()
    }
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int, payloads: MutableList<Any>) {
        if (!CheckUtil.isEmpty(payloads) && (payloads[0] as String) == "text") {
            YYLogUtils.w("差分刷新 -------- 文本更新")
            holder.setText(R.id.tv_job_text, mDatas[position].content)
        } else {
            onBindViewHolder(holder, position)
        }
    }
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
        YYLogUtils.w("默認(rèn)數(shù)據(jù)賦值 --------")
        holder.setText(R.id.tv_job_text, mDatas[position].content)
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
        return BaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_diff_jobs, parent, false))
    }
    override fun getItemCount(): Int {
        return mDatas.size
    }
}

如果我們想啟動(dòng)Diff的功能的時(shí)候,實(shí)現(xiàn)這個(gè)接口并委托實(shí)現(xiàn)即可啟用Diff。

class MyDiffAdapter() : RecyclerView.Adapter<BaseViewHolder>(), IDiffer<DemoDiffBean> by differ() {
    init {
        initDiffer(MyAsyncDifferConfig.Builder(DiffDemoCallback()).build())
    }
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int, payloads: MutableList<Any>) {
        if (!CheckUtil.isEmpty(payloads) && (payloads[0] as String) == "text") {
            YYLogUtils.w("差分刷新 -------- 文本更新")
            holder.setText(R.id.tv_job_text, getCurrentList()[position].content)
        } else {
            onBindViewHolder(holder, position)
        }
    }
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
        YYLogUtils.w("默認(rèn)數(shù)據(jù)賦值 --------")
        holder.setText(R.id.tv_job_text, getCurrentList()[position].content)
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
        return BaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_diff_jobs, parent, false))
    }
    override fun getItemCount(): Int {
        return getCurrentList().size
    }
}

使用的時(shí)候也是超方便的

    private fun initRV() {
        mAdapter = MyDiffAdapter()
        findViewById<RecyclerView>(R.id.recyclerView).vertical().apply {
            adapter = mAdapter
            divider(Color.BLACK)
        }
    }
    private fun initData() {
        mDatas.clear()
        for (i in 1..10) {
            mDatas.add(DemoDiffBean(i, "conetnt:$i"))
        }
        mAdapter.setDiffNewData(mDatas)
    }  
    private fun initListener() {
        findViewById<View>(R.id.diff_1).click {
            val list = mutableListOf<DemoDiffBean>()
            for (i in 1..10) {
                list.add(DemoDiffBean(i, "Diff1 conetnt:$i"))
            }
            mAdapter.setDiffNewData(list)
        }
        findViewById<View>(R.id.diff_2).click {
            val list = mutableListOf<DemoDiffBean>()
            for (i in 1..10) {
                list.add(DemoDiffBean(i, "Diff3 conetnt:$i"))
            }
            list.removeAt(0)
            list.removeAt(1)
            list.removeAt(2)
            list[3].content = "自定義亂改的數(shù)據(jù)"
            mAdapter.setDiffNewData(list)
        }
    }

運(yùn)行的效果如下:

是不是很方便呢?可能有人會(huì)問(wèn),這么封裝有什么好處?

其實(shí)這樣通過(guò)配置的方式,我們可以使用在任意的RV.Adapter上面,包括我們自己定義的BaseAdapter,LoadMoreAdapter等。相對(duì)比較靈活吧,方便在原有的效果上快速修改。

當(dāng)然了,其實(shí)關(guān)于 Diff 的實(shí)現(xiàn),有這么多種方式可以讓大家使用,每一種方案都能實(shí)現(xiàn)同樣的效果,只是看大家愿不愿意優(yōu)化而已,每一種方式使用起來(lái)都不算難。

小結(jié)

關(guān)于 paylpoad 和 Diff 的問(wèn)題,既然 Diff 是在 payload 的基礎(chǔ)上實(shí)現(xiàn)的,那是不是有 Diff 功能之后我們就不需要手動(dòng) payload 了呢?

也不是,上面的介紹中已經(jīng)講過(guò)了,如果數(shù)據(jù)頻繁的切換,最好是使用Diff,如果就是類似普通的評(píng)論列表點(diǎn)贊的效果,我們手動(dòng) payload 即可。他們有各自的使用場(chǎng)景。

需要注意的是,如果使用 Diff 要留意對(duì)象指針的問(wèn)題,DiffUtil 首先檢查新提交的 newList 與內(nèi)部持有的 mList 的引用是否相同, 如果相同, 就直接返回。如果不同的引用,才會(huì)對(duì) newList 和 mList 做 Diff 算法比較。

可以看的我的 Demo 都是直接另 new 一個(gè) List 來(lái)進(jìn)行操作的,正常開(kāi)發(fā)場(chǎng)景一般我們都是從服務(wù)器拿的不同的 List,也不會(huì)有問(wèn)題。而如果從本地拿的數(shù)據(jù)去比對(duì)的時(shí)候就需要注意,對(duì)比的對(duì)象和原有的對(duì)象是否是同一個(gè)對(duì)象,此時(shí)可以考慮對(duì)象的深拷貝來(lái)實(shí)現(xiàn)新對(duì)象,再拿去和原有對(duì)象進(jìn)行差分對(duì)比,關(guān)于淺拷貝與深拷貝的對(duì)比和如何深拷貝,可以看我之前的文章【傳送門(mén)】。

好了,本文的全部代碼與Demo都已經(jīng)開(kāi)源。有興趣可以看這里。項(xiàng)目會(huì)持續(xù)更新,大家可以關(guān)注一下。

以上就是Android的RV列表刷新詳解Payload與Diff方式異同的詳細(xì)內(nèi)容,更多關(guān)于Android RV列表刷新Payload Diff的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android 自定義View實(shí)現(xiàn)計(jì)時(shí)文字詳解

    Android 自定義View實(shí)現(xiàn)計(jì)時(shí)文字詳解

    這篇文章主要為大家介紹了Android 自定義View實(shí)現(xiàn)計(jì)時(shí)文字詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度

    Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度

    本篇文章主要介紹了Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Android內(nèi)置的OkHttp用法介紹

    Android內(nèi)置的OkHttp用法介紹

    okhttp是一個(gè)第三方類庫(kù),用于android中請(qǐng)求網(wǎng)絡(luò)。這是一個(gè)開(kāi)源項(xiàng)目,是安卓端最火熱的輕量級(jí)框架,由移動(dòng)支付Square公司貢獻(xiàn)(該公司還貢獻(xiàn)了Picasso和LeakCanary) 。用于替代HttpUrlConnection和Apache HttpClient
    2022-08-08
  • android編程之xml文件讀取和寫(xiě)入方法

    android編程之xml文件讀取和寫(xiě)入方法

    這篇文章主要介紹了android編程之xml文件讀取和寫(xiě)入方法,涉及Android針對(duì)XML文件的相關(guān)操作技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-04-04
  • Android開(kāi)發(fā)中的單例模式應(yīng)用詳解

    Android開(kāi)發(fā)中的單例模式應(yīng)用詳解

    這篇文章主要介紹了Android開(kāi)發(fā)中的單例模式應(yīng)用,結(jié)合實(shí)例形式詳細(xì)分析了Android開(kāi)發(fā)中常用單例模式的實(shí)現(xiàn)與使用方法,需要的朋友可以參考下
    2018-01-01
  • Android制作登錄頁(yè)面并且記住賬號(hào)密碼功能的實(shí)現(xiàn)代碼

    Android制作登錄頁(yè)面并且記住賬號(hào)密碼功能的實(shí)現(xiàn)代碼

    這篇文章主要介紹了Android制作登錄頁(yè)面并且記住賬號(hào)密碼功能的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 詳解Glide4.0集成及使用注意事項(xiàng)

    詳解Glide4.0集成及使用注意事項(xiàng)

    這篇文章主要介紹了詳解Glide4.0集成及使用注意事項(xiàng),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • Android學(xué)習(xí)之文件存儲(chǔ)讀取

    Android學(xué)習(xí)之文件存儲(chǔ)讀取

    本節(jié)給大家介紹的是Android數(shù)據(jù)存儲(chǔ)與訪問(wèn)方式中的一個(gè)——文件存儲(chǔ)與讀寫(xiě),當(dāng)然除了這種方式外,我們可以存到SharedPreference,數(shù)據(jù)庫(kù), 或者ContentProvider中,當(dāng)然這些后面都會(huì)講,嗯,開(kāi)始本文內(nèi)容~
    2016-07-07
  • Android?手寫(xiě)RecyclerView實(shí)現(xiàn)列表加載

    Android?手寫(xiě)RecyclerView實(shí)現(xiàn)列表加載

    這篇文章主要介紹了Android?手寫(xiě)RecyclerView實(shí)現(xiàn)列表加載,涉及到列表的需求,肯定第一時(shí)間想到RecyclerView,即便是自定義View,那么RecyclerView也會(huì)是首選,為什么會(huì)選擇RecyclerView而不是ListView,主要就是RecyclerView的內(nèi)存復(fù)用機(jī)制,這也是RecyclerView的核心?
    2022-08-08
  • Android 8.0安裝apk的實(shí)例代碼

    Android 8.0安裝apk的實(shí)例代碼

    本文給大家分享了Android 8.0安裝apk的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2018-03-03

最新評(píng)論