如何更新RecyclerView适配器数据?


273

试图弄清楚update RecyclerView的Adapter 是什么问题。

获得新的产品列表后,我尝试:

  1. ArrayListrecyclerView创建的片段更新,将新数据设置为adapter,然后调用adapter.notifyDataSetChanged(); 它不起作用。

  2. 像其他人一样创建一个新适配器,它对他们有用,但是对我来说没有任何变化: recyclerView.setAdapter(new RecyclerViewAdapter(newArrayList))

  3. 创建一种Adapter如下更新数据的方法:

    public void updateData(ArrayList<ViewModel> viewModels) {
       items.clear();
       items.addAll(viewModels);
       notifyDataSetChanged();
    }

    然后,无论何时要更新数据列表,我都调用此方法。它不起作用。

  4. 要检查我是否可以以任何方式修改recyclerView,并尝试至少删除一个项目:

     public void removeItem(int position) {
        items.remove(position);
        notifyItemRemoved(position);
    }

    一切都保持原样。

这是我的适配器:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> implements View.OnClickListener {

    private ArrayList<ViewModel> items;
    private OnItemClickListener onItemClickListener;

    public RecyclerViewAdapter(ArrayList<ViewModel> items) {
        this.items = items;
    }


    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);
        v.setOnClickListener(this);
        return new ViewHolder(v);
    }

    public void updateData(ArrayList<ViewModel> viewModels) {
        items.clear();
        items.addAll(viewModels);
        notifyDataSetChanged();
    }
    public void addItem(int position, ViewModel viewModel) {
        items.add(position, viewModel);
        notifyItemInserted(position);
    }

    public void removeItem(int position) {
        items.remove(position);
        notifyItemRemoved(position);
    }



    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ViewModel item = items.get(position);
        holder.title.setText(item.getTitle());
        Picasso.with(holder.image.getContext()).load(item.getImage()).into(holder.image);
        holder.price.setText(item.getPrice());
        holder.credit.setText(item.getCredit());
        holder.description.setText(item.getDescription());

        holder.itemView.setTag(item);
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    @Override
    public void onClick(final View v) {
        // Give some time to the ripple to finish the effect
        if (onItemClickListener != null) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    onItemClickListener.onItemClick(v, (ViewModel) v.getTag());
                }
            }, 0);
        }
    }

    protected static class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView image;
        public TextView price, credit, title, description;

        public ViewHolder(View itemView) {
            super(itemView);
            image = (ImageView) itemView.findViewById(R.id.image);
            price = (TextView) itemView.findViewById(R.id.price);
            credit = (TextView) itemView.findViewById(R.id.credit);
            title = (TextView) itemView.findViewById(R.id.title);
            description = (TextView) itemView.findViewById(R.id.description);
        }
    }

    public interface OnItemClickListener {

        void onItemClick(View view, ViewModel viewModel);

    }
}

我开始RecyclerView如下:

recyclerView = (RecyclerView) view.findViewById(R.id.recycler);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 5));
adapter = new RecyclerViewAdapter(items);
adapter.setOnItemClickListener(this);
recyclerView.setAdapter(adapter);

那么,如何实际更新适配器数据以显示新接收到的项目?


更新:问题是gridView的布局如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:tag="catalog_fragment"
    android:layout_height="match_parent">

    <FrameLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"/>

        <ImageButton
            android:id="@+id/fab"
            android:layout_gravity="top|end"
            style="@style/FabStyle"/>

    </FrameLayout>
</LinearLayout>

然后我只是删除LinearLayoutFrameLayout作为父布局。


items.clear(); items.addAll(newItems);是一个丑陋的模式。如果您确实需要在此处进行防御性复制,items = new ArrayList(newItems);则将不再那么难看。
Miha_x64 '18 -10-18

2
@ miha-x64你是对的-它将不那么难看。问题在于这根本行不通。适配器没有引用该引用。它获取引用本身。因此,如果您构建一个新的数据集,则它具有一个新的引用,而适配器仍然只知道旧的引用。
令人难以置信的

Answers:


325

我正在使用RecyclerView,删除和更新都可以正常工作。

1)删除:有4个步骤从RecyclerView中删除项目

list.remove(position);
recycler.removeViewAt(position);
mAdapter.notifyItemRemoved(position);                 
mAdapter.notifyItemRangeChanged(position, list.size());

这些代码行对我有用。

2)更新数据:我唯一要做的就是

mAdapter.notifyDataSetChanged();

您必须使用“活动/片段”代码而不是RecyclerView适配器代码来完成所有这些操作。


2
谢谢。请检查更新。不知何故,线性布局不会让RecylerView更新列表。
Filip Luchianenco

40
您只需要这两行:list.remove(position); mAdapter.notifyItemRemoved(position);
2013年

3
对我而言,线条recycler.removeViewAt(position);(或更确切地说是recycler.removeAllViews())是至关重要的。
MSpeed

2
避免在删除过程中产生令人讨厌的故障的重要注意事项:无需调用此行recycler.removeViewAt(position);。
Angelo Nodari '16

7
NotifyDataSetChanged是过大的,特别是在处理非常长的列表时。重新装入整个物品,以便放几个物品?不用了,谢谢。另外,recycler.removeViewAt(position)吗?没有必要。并且不需要同时调用“ mAdapter.notifyItemRemoved(position)”和“ mAdapter.notifyItemRangeChanged(position,list.size())”。删除一些,或删除一个。通知一次。打电话给他们之一。
维托·雨果·施瓦布

328

这是将来访客的一般答案。说明了更新适配器数据的各种方法。该过程每次包括两个主要步骤:

  1. 更新数据集
  2. 通知适配器更改

插入单项

在索引处添加“猪” 2

插入单项

String item = "Pig";
int insertIndex = 2;
data.add(insertIndex, item);
adapter.notifyItemInserted(insertIndex);

插入多个项目

在索引处再插入三只动物2

插入多个项目

ArrayList<String> items = new ArrayList<>();
items.add("Pig");
items.add("Chicken");
items.add("Dog");
int insertIndex = 2;
data.addAll(insertIndex, items);
adapter.notifyItemRangeInserted(insertIndex, items.size());

删除单项

从列表中删除“猪”。

删除单项

int removeIndex = 2;
data.remove(removeIndex);
adapter.notifyItemRemoved(removeIndex);

删除多个项目

从列表中删除“骆驼”和“绵羊”。

删除多个项目

int startIndex = 2; // inclusive
int endIndex = 4;   // exclusive
int count = endIndex - startIndex; // 2 items will be removed
data.subList(startIndex, endIndex).clear();
adapter.notifyItemRangeRemoved(startIndex, count);

删除所有项目

清除整个列表。

删除所有项目

data.clear();
adapter.notifyDataSetChanged();

用新列表替换旧列表

清除旧列表,然后添加一个新列表。

用新列表替换旧列表

// clear old list
data.clear();

// add new list
ArrayList<String> newList = new ArrayList<>();
newList.add("Lion");
newList.add("Wolf");
newList.add("Bear");
data.addAll(newList);

// notify adapter
adapter.notifyDataSetChanged();

adapter有一个参考data,所以重要的是,我没有设定data一个新的目标。相反,我从中清除了旧项目data,然后添加了新项目。

更新单个项目

更改“绵羊”项,使其显示为“我喜欢绵羊”。

更新单个项目

String newValue = "I like sheep.";
int updateIndex = 3;
data.set(updateIndex, newValue);
adapter.notifyItemChanged(updateIndex);

移动单个项目

将“绵羊”从一个位置移动3到另一个位置1

移动单个项目

int fromPosition = 3;
int toPosition = 1;

// update data array
String item = data.get(fromPosition);
data.remove(fromPosition);
data.add(toPosition, item);

// notify adapter
adapter.notifyItemMoved(fromPosition, toPosition);

这是项目代码供您参考。在此答案中可以找到RecyclerView适配器代码。

MainActivity.java

public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener {

    List<String> data;
    MyRecyclerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // data to populate the RecyclerView with
        data = new ArrayList<>();
        data.add("Horse");
        data.add("Cow");
        data.add("Camel");
        data.add("Sheep");
        data.add("Goat");

        // set up the RecyclerView
        RecyclerView recyclerView = findViewById(R.id.rvAnimals);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
                layoutManager.getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);
        adapter = new MyRecyclerViewAdapter(this, data);
        adapter.setClickListener(this);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(this, "You clicked " + adapter.getItem(position) + " on row number " + position, Toast.LENGTH_SHORT).show();
    }

    public void onButtonClick(View view) {
        insertSingleItem();
    }

    private void insertSingleItem() {
        String item = "Pig";
        int insertIndex = 2;
        data.add(insertIndex, item);
        adapter.notifyItemInserted(insertIndex);
    }

    private void insertMultipleItems() {
        ArrayList<String> items = new ArrayList<>();
        items.add("Pig");
        items.add("Chicken");
        items.add("Dog");
        int insertIndex = 2;
        data.addAll(insertIndex, items);
        adapter.notifyItemRangeInserted(insertIndex, items.size());
    }

    private void removeSingleItem() {
        int removeIndex = 2;
        data.remove(removeIndex);
        adapter.notifyItemRemoved(removeIndex);
    }

    private void removeMultipleItems() {
        int startIndex = 2; // inclusive
        int endIndex = 4;   // exclusive
        int count = endIndex - startIndex; // 2 items will be removed
        data.subList(startIndex, endIndex).clear();
        adapter.notifyItemRangeRemoved(startIndex, count);
    }

    private void removeAllItems() {
        data.clear();
        adapter.notifyDataSetChanged();
    }

    private void replaceOldListWithNewList() {
        // clear old list
        data.clear();

        // add new list
        ArrayList<String> newList = new ArrayList<>();
        newList.add("Lion");
        newList.add("Wolf");
        newList.add("Bear");
        data.addAll(newList);

        // notify adapter
        adapter.notifyDataSetChanged();
    }

    private void updateSingleItem() {
        String newValue = "I like sheep.";
        int updateIndex = 3;
        data.set(updateIndex, newValue);
        adapter.notifyItemChanged(updateIndex);
    }

    private void moveSingleItem() {
        int fromPosition = 3;
        int toPosition = 1;

        // update data array
        String item = data.get(fromPosition);
        data.remove(fromPosition);
        data.add(toPosition, item);

        // notify adapter
        adapter.notifyItemMoved(fromPosition, toPosition);
    }
}

笔记

  • 如果使用notifyDataSetChanged(),则将不执行动画。这也可能是一项昂贵的操作,因此,notifyDataSetChanged()如果您仅更新单个项目或一系列项目,则不建议使用。
  • 如果您要对列表进行大型或复杂更改,请DiffUtil

进一步研究


5
很棒的答案!替换入项会导致不同的视图类型的步骤是什么?它是单单notifyItemChanged还是组合后跟插入的remove?
Semaphor

用您的最小示例,看起来notifyItemChanged就足够了。它将为更改的项目调用onCreateViewHolder和onBindViewHolder,并显示不同的视图。我在更新项目的应用程序中遇到问题,这会导致出现不同的视图,但是似乎还有另一个问题。
Semaphor

@Suragch关于我的问题的任何想法:stackoverflow.com/questions/57332222/…我尝试了此线程中列出的所有内容,但无济于事:(

当我们使用DiffUtils进行更改时呢?它们不像手动调用.notify <Action>()那样流畅。
GuilhE

1
真正放弃了球下Update single item,本来应该是我喜欢乌龟
ardnew

46

这对我有用:

recyclerView.setAdapter(new RecyclerViewAdapter(newList));
recyclerView.invalidate();

在创建一个包含更新列表的新适配器(在我的情况下,这是一个数据库转换为ArrayList)并将其设置为适配器之后,我尝试recyclerView.invalidate()并成功了。


12
这不会刷新整个视图,而不仅仅是更新已更改的项目吗?
TWilly '16

13
我没有每次都制作一个新的适配器,而是在自定义适配器类中创建了一个setter方法来设置新列表。之后,只需调用话YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();虽如此,这听起来像是OP首先尝试的操作(只是将其添加为注释,以便它可以对其他人有所帮助,就像我的情况一样)。
凯文·李

2
读者,请不要为此感到满足。这个答案有效,但是有一种更有效的方法。最有效的方法是将新数据添加到适配器,而不是再次重新加载所有数据
Sebastialonso

是的,我同意@TWilly,它将重绘完整的UI视图。
拉胡尔

18

您有2种方法可以执行此操作:从适配器刷新UI:

mAdapter.notifyDataSetChanged();

或从recyclerView本身刷新它:

recyclerView.invalidate();

15

另一种选择是使用diffutil。它将原始列表与新列表进行比较,如果有更改,则将新列表用作更新。

基本上,我们可以使用DiffUtil比较旧数据与新数据,并让它代表您调用notifyItemRangeRemoved,notifyItemRangeChanged和notifyItemRangeInserted。

使用diffUtil代替notifyDataSetChanged的简单示例:

DiffResult diffResult = DiffUtil
                .calculateDiff(new MyDiffUtilCB(getItems(), items));

//any clear up on memory here and then
diffResult.dispatchUpdatesTo(this);

//and then, if necessary
items.clear()
items.addAll(newItems)

万一这是一个很大的清单,我会在主线程上进行calculateDiff工作。


1
尽管这是正确的,但如何更新适配器内部的数据?假设我在适配器中显示List <Object>,并且得到了List <Object>的新更新。DiffUtil将计算差异并将其分配到适配器中,但是它对原始列表或新列表不起作用(在您的情况下getItems()。如何得知原始列表中的哪些数据需要删除/添加/更新?
vanomart

1
也许这篇文章可以有所帮助。.. medium.com/@nullthemall/...
j2emanue

@vanomart只需像通常那样直接用新的列表值替换本地列表字段,这种方法的唯一区别是,您可以使用DiffUtil来更新视图,而不是notifyDataSetChanged()。
carrizo

关于我的想法吗?我想这里的一切都列出,但没有运气:stackoverflow.com/questions/57332222/...

谢谢您的回答!您能解释一下为什么我应该在dispatchUpdatesTo之后加上“ clear”和“ addAll”吗?天哪!
Adi Azarya

10

更新ListView,GridView和RecyclerView的数据

mAdapter.notifyDataSetChanged();

要么

mAdapter.notifyItemRangeChanged(0, itemList.size());

直到用户开始滚动,这似乎才更新我的数据。我看遍了整个地方,不知道为什么..
SudoPlz

@SudoPlz,您可以使用自定义滚动侦听器来检测滚动并执行您的通知操作,例如stackoverflow.com/a/31174967/4401692
Pankaj Talaviya

感谢Pankaj,但这正是我要避免的事情。我想更新所有内容,而无需用户触摸屏幕。
SudoPlz

5

将新数据添加到当前数据的最佳和最酷的方法是

 ArrayList<String> newItems = new ArrayList<String>();
 newItems = getList();
 int oldListItemscount = alcontainerDetails.size();
 alcontainerDetails.addAll(newItems);           
 recyclerview.getAdapter().notifyItemChanged(oldListItemscount+1, al_containerDetails);

2
我看到人们对所有内容都使用“ notifyDataSetChanged”,这不是过分杀伤力吗?我的意思是,重新加载整个列表,因为有一些更改或添加了...我喜欢这种方法,现在正在使用“ notifyItemRangeInserted”方法实现。
维托·雨果·施瓦布

5

我以不同的方式解决了相同的问题。我没有从后台线程等待的数据,因此从一个空列表开始。

        mAdapter = new ModelAdapter(getContext(),new ArrayList<Model>());
   // then when i get data

        mAdapter.update(response.getModelList());
  //  and update is in my adapter

        public void update(ArrayList<Model> modelList){
            adapterModelList.clear(); 
            for (Product model: modelList) {
                adapterModelList.add(model); 
            }
           mAdapter.notifyDataSetChanged();
        }

而已。


2

这些方法是有效的,并且很好地开始使用基本方法RecyclerView

private List<YourItem> items;

public void setItems(List<YourItem> newItems)
{
    clearItems();
    addItems(newItems);
}

public void addItem(YourItem item, int position)
{
    if (position > items.size()) return;

    items.add(item);
    notifyItemInserted(position);
}

public void addMoreItems(List<YourItem> newItems)
{
    int position = items.size() + 1;
    newItems.addAll(newItems);
    notifyItemChanged(position, newItems);
}

public void addItems(List<YourItem> newItems)
{
    items.addAll(newItems);
    notifyDataSetChanged();
}

public void clearItems()
{
    items.clear();
    notifyDataSetChanged();
}

public void addLoader()
{
    items.add(null);
    notifyItemInserted(items.size() - 1);
}

public void removeLoader()
{
    items.remove(items.size() - 1);
    notifyItemRemoved(items.size());
}

public void removeItem(int position)
{
    if (position >= items.size()) return;

    items.remove(position);
    notifyItemRemoved(position);
}

public void swapItems(int positionA, int positionB)
{
    if (positionA > items.size()) return;
    if (positionB > items.size()) return;

    YourItem firstItem = items.get(positionA);

    videoList.set(positionA, items.get(positionB));
    videoList.set(positionB, firstItem);

    notifyDataSetChanged();
}

您可以在Adapter类内部或在Fragment或Activity中实现它们,但在这种情况下,您必须实例化Adapter才能调用通知方法。就我而言,我通常在适配器中实现它。


2

我发现重新加载RecyclerView的一种非常简单的方法是

recyclerView.removeAllViews();

这将首先删除RecyclerView的所有内容,然后使用更新后的值再次添加它。


2
请不要使用它。您会自找麻烦。
dell116 '19

1

很久以后我得到了答案

  SELECTEDROW.add(dt);
                notifyItemInserted(position);
                SELECTEDROW.remove(position);
                notifyItemRemoved(position);

0

如果上述评论中没有提及任何内容,那么对您有用。这可能意味着问题出在其他地方。

我发现解决方案的一个地方就是将列表设置为适配器的方式。在我的活动中,列表是一个实例变量,当任何数据更改时,我都在直接更改它。由于它是一个参考变量,因此发生了一些奇怪的事情。因此,我将引用变量更改为本地变量,并使用另一个变量更新数据,然后将其传递给addAll()上述答案中提到的函数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.