Как обновить данные адаптера RecyclerView?

275

Попытка выяснить, в чем проблема с обновлением RecyclerViewадаптера.

После того, как я получил новый Список продуктов, я попытался:

  1. Обновите ArrayListфрагмент из того места, где recyclerViewон создан, установите новые данные для адаптера, затем вызовите 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>

Тогда я просто удалил LinearLayoutи сделал FrameLayoutкак родительский макет.

Филипп Лучианенко
источник
items.clear(); items.addAll(newItems);это уродливый шаблон. Если вам действительно нужно защитное копирование здесь, items = new ArrayList(newItems);это будет менее уродливо.
Miha_x64
2
@ miha-x64 Ты прав - это было бы менее уродливо. Проблема в том, что это просто не работает. Адаптер не получает ссылку на ссылку. Он получает ссылку на себя. Поэтому, если вы создаете новый набор данных, у него есть новая ссылка, а адаптер все еще знает только старый.
Невероятный Янв

Ответы:

326

Я работаю с RecyclerView, и удаление, и обновление работают хорошо.

1) УДАЛИТЬ: есть 4 шага, чтобы удалить элемент из RecyclerView

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

Эти строки кодов работают для меня.

2) ОБНОВЛЕНИЕ ДАННЫХ: единственное, что мне нужно было сделать, это

mAdapter.notifyDataSetChanged();

Вы должны были сделать все это в коде Actvity / Fragment, а не в коде RecyclerView Adapter.

Никколо Пассолунги
источник
2
Спасибо. Пожалуйста, проверьте обновление. Каким-то образом линейный макет не позволяет обновлять список RecylerView.
Филипп Лучиенко
40
вам нужны только эти две строки: list.remove (position); mAdapter.notifyItemRemoved (положение);
16:00
3
Для меня линии recycler.removeViewAt(position);(точнее recycler.removeAllViews()) были решающими.
MSpeed
2
Важное соображение, чтобы избежать раздражающего сбоя при удалении: вам не нужно вызывать эту строку recycler.removeViewAt (position);
Анджело Нодари
7
NotifyDataSetChanged является излишним, особенно если вы имеете дело с ОЧЕНЬ длинными списками. Перезагрузить все это для нескольких предметов? Нет, спасибо. Кроме того, recycler.removeViewAt (позиция)? Не нужно. И не нужно вызывать оба «mAdapter.notifyItemRemoved (position)» и «mAdapter.notifyItemRangeChanged (position, list.size ())». Удалить некоторые или удалить один. Уведомить раз. Позвони одному из них.
Витор Хьюго Швааб
329

Это общий ответ для будущих посетителей. Описаны различные способы обновления данных адаптера. Процесс включает в себя два основных шага каждый раз:

  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, если вы делаете большие или сложные изменения в списке.

Дальнейшее обучение

Suragch
источник
5
Отличный ответ! Какие будут шаги при замене элемента in, что приведет к другому типу представления? Это просто notifyItemChanged или комбинированное удаление с последующей вставкой?
Семафора
С вашим минимальным примером, похоже, что notifyItemChanged достаточно. Он вызовет onCreateViewHolder и onBindViewHolder для измененного элемента, и будет показано другое представление. У меня была проблема с обновлением элемента в приложении, что привело бы к другому представлению, но, похоже, есть другая проблема.
Семафора
@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()и это сработало.

martinpkmn
источник
12
Разве это не обновит весь вид, а не просто обновит элементы, которые изменились?
TWilly
13
Вместо того, чтобы каждый раз создавать новый адаптер, я создал метод setter в своем собственном классе адаптеров, чтобы задать новый список. После этого просто назовите « YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();Это, как говорится», это звучит как то, что сначала попробовал ОП (просто добавив это в качестве комментария, чтобы это могло помочь кому-то еще, как это сработало в моем случае).
Кевин Ли
2
Читатель, не соглашайтесь на это. Этот ответ работает, но есть более эффективный способ. Вместо повторной загрузки всех данных наиболее эффективным способом является добавление новых данных в адаптер.
Себастьянсо
Да, я согласен с @TWilly, он перерисовает полный просмотр интерфейса пользователя.
Рахул
18

у вас есть 2 варианта: обновить интерфейс от адаптера:

mAdapter.notifyDataSetChanged();

или обновите его из себя в recyclerView:

recyclerView.invalidate();
ediBersh
источник
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 от основного потока на случай, если это большой список.

j2emanue
источник
1
хотя это правильно, как обновить данные внутри вашего адаптера? Допустим, я отображаю List <Object> в адаптере и получаю новое обновление с List <Object>. DiffUtil вычислит разницу и отправит ее в адаптер, но она ничего не делает с исходным или новым списком (в вашем случае getItems(). Как вы узнаете, какие данные из исходного списка необходимо удалить / добавить / обновить?
vanomart
1
Может быть, эта статья может помочь. .. medium.com/@nullthemall/...
j2emanue
@vanomart, вам нужно только напрямую заменить локальное поле списка новым значением списка, как вы это обычно делаете, единственное отличие в этом подходе состоит в том, что вместо notifyDataSetChanged () вы используете DiffUtil для обновления представления.
Carrizo
есть идеи о моем? я перепробовал все перечисленное здесь, но не повезло: stackoverflow.com/questions/57332222/…
Спасибо за ваш ответ! Не могли бы вы объяснить, почему я должен поставить «очистить» и «добавить все» после отправки обновлений ?? Tnx!
Ади Азарья
10

Обновление данных списка, вида сетки и повторного просмотра

mAdapter.notifyDataSetChanged();

или

mAdapter.notifyItemRangeChanged(0, itemList.size());
Панкадж Талавия
источник
Это не обновляет мои данные, пока пользователь не начнет прокрутку. Я посмотрел повсюду и не могу понять, почему ..
SudoPlz
@SudoPlz вы можете использовать пользовательский прослушиватель прокрутки для обнаружения прокрутки и выполнения операции уведомления, например stackoverflow.com/a/31174967/4401692
Pankaj Talaviya
Спасибо Панкадж, но это именно то, чего я пытаюсь избежать. Я хочу обновить все без того, чтобы пользователь касался экрана.
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();
}

Вы можете реализовать их внутри класса адаптера или в вашем фрагменте или действии, но в этом случае вам нужно создать экземпляр адаптера для вызова методов уведомления. В моем случае я обычно реализую это в адаптере.

Teocci
источник
2

Я обнаружил, что действительно простой способ перезагрузить RecyclerView - это просто вызвать

recyclerView.removeAllViews();

Это сначала удалит все содержимое RecyclerView, а затем добавит его снова с обновленными значениями.

Джонатан Персгарден
источник
2
Пожалуйста, не используйте это. Вы будете просить неприятностей.
dell116
1

я получил ответ через долгое время

  SELECTEDROW.add(dt);
                notifyItemInserted(position);
                SELECTEDROW.remove(position);
                notifyItemRemoved(position);
roufal
источник
0

Если ничего не упомянуто в вышеприведенных комментариях, это работает для вас. Это может означать, что проблема лежит где-то еще.

Единственное место, где я нашел решение, было в том, как я настраивал список для адаптера. В моей деятельности список представлял собой переменную экземпляра, и я менял ее напрямую, когда менялись какие-либо данные. Из-за того, что это ссылочная переменная, происходило что-то странное. Поэтому я изменил ссылочную переменную на локальную и использовал другую переменную для обновления данных, а затем перешел к addAll()функции, упомянутой в ответах выше.

bitSmith
источник