如何使用 DiffUtils 更新 RecyclerView 中的列表?

Posted

技术标签:

【中文标题】如何使用 DiffUtils 更新 RecyclerView 中的列表?【英文标题】:How to update list in RecyclerView with DiffUtils? 【发布时间】:2020-01-16 21:34:08 【问题描述】:

在使用 DiffUtil ItemCallback 时,如何使用 DiffCallback 在 RecyclerView 中加载 newList。

当用户选择我希望 RecyclerView 更新的不同尺寸时,我想为用户提供从数据库返回不同尺寸列表的选项。

RecyclerViewAdatper

RecyclerViewAdapter extends ListAdapter<WordEntity, RecyclerViewAdapter.ViewHolder> 
private RecyclerViewAdapter() 
    super(DIFF_CALLBACK);


private static final DiffUtil.ItemCallback<WordEntity> DIFF_CALLBACK = new DiffUtil.ItemCallback<WordEntiti>() 

    @Override
    public boolean areItemsTheSame...

    @Override
    public boolean areContentsTheSame...
;

@Override
public viewHolder onCreateViewHolder...

@Override
public void onVindViewHolder ...

class ViewHolder extends RecyclerView.ViewHolder ...

public void updateWordList(List<WordEntity> words) 
    final WordDiffCallBack diffCallBack = new WordDiffCallBack(list???, words);
    final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallBack);

    this.list???.clear();
    this.addAll(words);
    diffResult.dispatcheUpdatesTo(this);

WordsDiffCallBack

private final List<WordEntity> mOldList;
private final List<WordEntity> mNewList;

public WordsDiffCallBack(List<WordEntity> oldList, List<WordEntity> newList) 
    this.mOldList = oldList;
    this.mNewList = newList;


@Override
public int getOldListSize() 
    return mOldList.size();


@Override
public int getNewListSize() 
    return mNewList.size();


@Override
public boolean areItemsTheSame(int OldItemPostion, int newItemPosition) ...

@Override boolean areContentsTheSame(int oldItemPosition, int newItemPosition)...

@Override getChangePayload(int oldItemPosition, int newItemPosition) ...


我希望 RecycelView 在用户更改列表大小时自动更新。如何从 ListAdapter 调用旧列表,甚至会更新 RecyclerView

【问题讨论】:

你在上面的代码中遇到了什么问题? 如何从 ArrayListAdapter 调用列表? 您不必自己计算差异。只需将新列表传递给适配器adapter.submitList(newList),适配器就会接受它,在后台线程上计算差异并使用动画更新列表 @sonnet 我也在想同样的事情,但我收到以下错误 androidx.room.RoomTrackingLiveData 无法转换为 java.util.List。当我尝试从我的 ViewModel 调用 getList() 时。它是一个 liveData>. 这是一个单独的问题。将您的实时数据从 dao 返回到 viewmodel,然后返回到片段/活动。在您的片段/活动中观察此实时数据 【参考方案1】:

您可以在 youtube 中创建一个模板,就像此视频中显示的那样:

https://www.youtube.com/watch?v=y31fzLe2Ajw

这是一个适配器的例子。

public class CartFragAdapter extends RecyclerView.Adapter<CartFragAdapter.CartFragViewHolder> 

    private static final String TAG = "debinf PurchaseAdap";

    private static final DiffUtil.ItemCallback<ProductsObject> DIFF_CALLBACK = new DiffUtil.ItemCallback<ProductsObject>() 
        @Override
        public boolean areItemsTheSame(@NonNull ProductsObject oldProduct, @NonNull ProductsObject newProduct) 
            Log.i(TAG, "areItemsTheSame: old is "+oldProduct.getCode()+" ; new is "+newProduct.getCode());
            return oldProduct.getCode().equals(newProduct.getCode());
        

        @Override
        public boolean areContentsTheSame(@NonNull ProductsObject oldProduct, @NonNull ProductsObject newProduct) 
            Log.i(TAG, "areContentsTheSame: old is "+oldProduct.getPrice()+" ; new is "+newProduct.getPrice());
            return oldProduct.getPrice() == newProduct.getPrice();
        
    ;

    private AsyncListDiffer<ProductsObject> differ = new AsyncListDiffer<ProductsObject>(this, DIFF_CALLBACK);

    @NonNull
    @Override
    public CartFragViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) 
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_purchase, parent, false);
        return new CartFragViewHolder(view);
    

    @Override
    public void onBindViewHolder(@NonNull CartFragViewHolder holder, int position) 
        final ProductsObject purchaseList = differ.getCurrentList().get(position);

        holder.mCode.setText(purchaseList.getCode());
        holder.mPrice.setText(String.valueOf(purchaseList.getPrice()));
        holder.mDescription.setText(purchaseList.getDescription());


    

    @Override
    public int getItemCount() 
        Log.i(TAG, "getItemCount");
        return differ.getCurrentList().size();
    

    public void submitList(List<ProductsObject> products)
        Log.i(TAG, "submitList: products.size is "+products.size());
        differ.submitList(products);
    

    public class CartFragViewHolder extends RecyclerView.ViewHolder 

        public TextView mCode, mPrice, mDescription;

        public CartFragViewHolder(@NonNull View itemView) 
            super(itemView);

            mCode = (TextView) itemView.findViewById(R.id.item_productCode);
            mPrice = (TextView) itemView.findViewById(R.id.item_productPrice);
            mDescription = (TextView) itemView.findViewById(R.id.item_productDescription);
        
    

MainActivity 你这样调用适配器:

CartFragAdapter adapter = new CartFragAdapter();
adapter.submitList(inputData);

希望对你有帮助!

【讨论】:

【参考方案2】:

观察这段代码

这是我的 diffutil 回调

public class MyDiffUtilCallback extends DiffUtil.Callback 

List<String> oldlist;
List<String > newlist;

public MyDiffUtilCallback(List<String> oldlist, List<String> newlist) 
    this.oldlist = oldlist;
    this.newlist = newlist;

@Override
public int getOldListSize() 
    return oldlist.size();

@Override
public int getNewListSize() 
    return newlist.size();

@Override
public boolean areItemsTheSame(int olditempostion, int newitemPostion) 
    return olditempostion==newitemPostion;

@Override
public boolean areContentsTheSame(int olditempostion, int newitemPostion) 
    return olditempostion==newitemPostion;

@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) 
    return super.getChangePayload(oldItemPosition, newItemPosition);

这是使用 diffutilcallback 的 recyclerview adpater

public class recyclerviewAdapter extends RecyclerView.Adapter<recyclerviewAdapter.Viewholder> 

List<String> datasource;

public recyclerviewAdapter(List<String> datasource) 
    this.datasource = datasource;


@Override
public Viewholder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) 
    View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.demorow,viewGroup,false);
    return new Viewholder(itemView);


@Override
public void onBindViewHolder(@NonNull Viewholder viewholder, int i) 
    viewholder.tv_demo_data_row.setText(datasource.get(i));


@Override
public int getItemCount() 
    return datasource.size();


public static class Viewholder extends RecyclerView.ViewHolder 

    TextView tv_demo_data_row;

    public Viewholder(@NonNull View itemView) 
        super(itemView);

        tv_demo_data_row=itemView.findViewById(R.id.tv_demo_data_row);
    


//DIFF CALLBACK STATE
public void insertdata(List<String> insertList)
    /**
     * Insert list insert data to list
     */
    MyDiffUtilCallback diffUtilCallback = new MyDiffUtilCallback(datasource,insertList);
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffUtilCallback);
    datasource.addAll(insertList);
    diffResult.dispatchUpdatesTo(this);


public void updateList(List<String> newList)
    /**
     * update list clear old data and update new data
     */
    MyDiffUtilCallback diffUtilCallback = new MyDiffUtilCallback(datasource,newList);
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffUtilCallback);
    datasource.clear();
    datasource.addAll(newList);
    diffResult.dispatchUpdatesTo(this);

在这里我可以更新按钮点击事件中的活动列表

insert_data.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            //inserting the data
            List<String> insertlist = new ArrayList<>(); //asign old list
            for(int i=0;i<2;i++)
                insertlist.add(UUID.randomUUID().toString()); // insert new list
                adapter.insertdata(insertlist);
                faster_recyclerview.smoothScrollToPosition(adapter.getItemCount()-1); //auto scroll to last postion
            
        
    );

用你的模型类替换字符串列表

【讨论】:

【参考方案3】:

并且除了上述(使用DiffUtils)之外,如果你使用Clicks来改变RecyclerView的内容,那么

ViewHolder implements View.OnClickListener

并使用它在 onBindViewHolder 中设置 OnClickListener

.setOnClickListener(holder);

得到你当前的位置

this.getAdapterPosition()
private class HistoryRecyclerAdapter extends RecyclerView.Adapter<HistoryRecyclerAdapter.ViewHolder> 
        private List<HistoryEntry> entries;

        HistoryRecyclerAdapter(List<HistoryEntry> entries) 
            this.entries = entries;
        

        public void updateContainer(List<HistoryEntry> list)
            DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() 
                @Override
                public int getOldListSize()  return entries.size(); 

                @Override
                public int getNewListSize()  return list.size(); 

                @Override
                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) 
                    return entries.get(oldItemPosition).getId()==list.get(newItemPosition).getId();
                

                @Override
                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) 
                    HistoryEntry old = entries.get(oldItemPosition);
                    HistoryEntry neww = list.get(newItemPosition);
                    return  old.getExpression().equals(neww.getExpression())
                            && old.getResult().equals(neww.getResult());
                
            , false);//detectMoves=true потом позволит вызвать анимацию если строка просто была перемещена
            entries = list;
            diffResult.dispatchUpdatesTo(this);
        

        class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
            //some Views
            final Button delBtn;

            ViewHolder(View view)
                super(view);
                //some Views
                delBtn = (Button) view.findViewById(R.id.delete_btn);
            

            @Override
            public void onClick(View v) 
                switch (v.getId())
                    //some cases
                    case R.id.delete_btn:
                        historyManager.delete(entries.get(this.getAdapterPosition()));
                        break;
                
            
        

        @NonNull
        @Override
        public HistoryRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) 
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.history_row, parent, false);
            return new HistoryRecyclerAdapter.ViewHolder(view);
        

        @Override
        public void onBindViewHolder(HistoryRecyclerAdapter.ViewHolder holder, final int position) 
            //some code
            holder.delBtn.setOnClickListener(holder);
        

        @Override
        public int getItemCount() 
            return entries.size();
        

    

早期我在 onBindViewHolder 中使用了 final int 位置,不明白出了什么问题...)))

【讨论】:

以上是关于如何使用 DiffUtils 更新 RecyclerView 中的列表?的主要内容,如果未能解决你的问题,请参考以下文章

Android开发Diffutils打造不一样的recyclerview

如何在 Recyclerview 中正确使用 DiffUtil 更新数组列表?

DiffUtils 遇到 Kotlin,榨干视图局部刷新的最后一滴性能

在适配器 kotlin 中更新视图时如何正确使用 Diff utils

Linux From Scratch(LFS11.0)构建 LFS 系统 - Diffutils-3.8

Linux From Scratch(LFS11.0)交叉编译临时工具 - Diffutils-3.8