Як оновити дані адаптера 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. Щоб перевірити, чи можу я яким-небудь чином змінити переробникView, і спробував видалити принаймні елемент:

     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) ВИДАЛЕННЯ: Для видалення елемента з RecyclerView є 4 кроки

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

Цей рядок кодів працює для мене.

2) ОНОВЛЕННЯ ДАНИХ: Єдине, що мені довелося зробити - це

mAdapter.notifyDataSetChanged();

Вам довелося це робити в коді Actvity / Fragment, а не в коді адаптера RecyclerView.


2
Дякую. Перевірте оновлення. Так чи інакше, лінійний макет не дозволяє списку оновлень RecylerView.
Філіп Лукіаненко

40
вам потрібні лише ці два рядки: list.remove (позиція); mAdapter.notifyItemRemoved (положення);
фейсал

3
Для мене лінії recycler.removeViewAt(position);(а точніше recycler.removeAllViews()) були вирішальними.
MSpeed

2
Важливе враження, щоб уникнути набридливих збоїв під час видалення: Вам не потрібно викликати цю лінію reciler.removeViewAt (позиція);
Анджело Нодарі

7
NotifyDataSetChanged є надмірним, особливо якщо ви маєте ДУЖЕ довгі списки. Перезавантажити всю справу на кілька предметів? Ні, дякую. Також reciler.removeViewAt (позиція)? Не потрібно. І не потрібно дзвонити як "mAdapter.notifyItemRemoved (позиція)", так і "mAdapter.notifyItemRangeChanged (позиція, 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 або комбіноване видалення, а потім вставка?
Семафор

З вашого мінімального прикладу, здається, що notifyItemChanged достатньо. Він буде викликати onCreateViewHolder та onBindViewHolder для зміненого елемента, а також відображається інший вигляд. У мене виникла проблема з оновленням програми, що призвело б до іншого погляду, але, здається, є інша проблема.
Семафор

@Suragch будь-яке уявлення про моє запитання: stackoverflow.com/questions/57332222/… Я спробував усе, що перераховано в цій темі, але безрезультатно :(

Як щодо того, коли ми використовуємо DiffUtils для внесення змін? Вони не настільки текучі, як дзвінки вручну. Повідомте <Action> ().
GuilhE

1
Дійсно кинув м'яч під Update single item, повинні були мені подобатися черепахи
ardnew

46

Це для мене працювало:

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

Після створення нового адаптера, який містить оновлений список (в моєму випадку це була база даних, перетворена в ArrayList), і встановив, що як адаптер, я спробував, recyclerView.invalidate()і він працював.


12
Чи не оновило б це повне уявлення замість того, щоб просто оновити змінили елементи?
ДВАЯТИ

13
Замість того, щоб робити новий адаптер щоразу, я створив метод встановлення в моєму власному класі адаптера для встановлення нового списку. Після цього просто зателефонуйте. YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();Як сказано, це звучить як те, що ОП спробував спочатку (просто додавши це як коментар, щоб воно могло допомогти комусь іншому, як це працювало в моєму випадку).
Кевін Лі

2
Читачу, не погоджуйся на цьому. Ця відповідь працює, але є більш ефективний спосіб. Замість того щоб перезавантажувати всі дані заново , найефективнішим способом є просто додавання нових даних у адаптер.
Себастіалонсо

Так, я погоджуюся з @TWilly, це перемальовує повний Вид на інтерфейс користувача.
Рахул

18

у вас є два варіанти зробити це: оновіть інтерфейс користувача з адаптера:

mAdapter.notifyDataSetChanged();

або оновіть його від вторинного перегляду:

recyclerView.invalidate();

15

Інший варіант - використовувати дифузію . Він порівняє вихідний список з новим списком і використає новий список як оновлення, якщо є зміни.

В основному, ми можемо використовувати DiffUtil для порівняння старих даних з новими даними і дозволяти йому викликати notifyItemRangeRemoved, а також сповіщатиItemRangeChanged та 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)

Я роблю CalcuDiff відробіток основної теми, якщо це великий список.


1
Хоча це правильно, як оновлювати дані всередині адаптера? Скажімо, я відображаю Список <Object> в адаптері, і я отримую нове оновлення зі списком <Object>. DiffUtil обчислить різницю і відправить її в адаптер, але він не робить нічого з оригінальним чи новим списком (у вашому випадку getItems(). Як ви знаєте, які дані з оригінального списку потрібно видалити / додати / оновити?
vanomart

1
Можливо, ця стаття може допомогти. .. medium.com/@nullthemall/…
j2emanue

@vanomart вам потрібно лише безпосередньо замінити поле вашого локального списку новим значенням списку, як це було б зазвичай, єдиною різницею в цьому підході є те, що замість notifyDataSetChanged () ви використовуєте DiffUtil для оновлення подання.
carrizo

якась ідея про мою? я спробував всі перераховані тут , але не пощастило: stackoverflow.com/questions/57332222 / ...

Спасибі за вашу відповідь! Чи можете ви поясніть, чому я повинен поставити "clear" & "addAll" після відправкиUpdatesTo ?? Tnx!
Аді Азарія

10

Оновлення даних перегляду списків, перегляду сітки та рециркулятора

mAdapter.notifyDataSetChanged();

або

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

Схоже, це не оновлює мої дані, поки користувач не почне прокручувати. Я роздивився всюди і не можу зрозуміти, чому ..
SudoPlz

@SudoPlz ви можете використовувати користувальницькі прокрутки перехоплювач для виявлення прокрутки і виконати вашу роботу , як сповістити stackoverflow.com/a/31174967/4401692
Панкай 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

Я вирішив ту саму проблему по-іншому. У мене немає даних, я чекаю їх з фонового потоку, тому почніть зі списку emty.

        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();
}

Ви можете реалізувати їх всередині класу адаптерів або у вашому фрагменті чи активності, але в такому випадку вам потрібно створити адаптер для виклику методів сповіщення. У моєму випадку я зазвичай реалізую його в адаптері.


2

Я дізнався, що дійсно простий спосіб перезавантажити RecyclerView - це просто зателефонувати

recyclerView.removeAllViews();

Це спочатку видалить увесь вміст RecyclerView, а потім знову додасть його до оновлених значень.


2
Будь ласка, не використовуйте це. Ви попросите неприємностей.
dell116

1

я отримав відповідь через довгий час

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

0

Якщо нічого згаданого у вищезазначених коментарях не працює для вас. Це може означати, що проблема полягає десь в іншому місці.

Я знайшов рішення в тому, як я встановлював список адаптеру. У моїй діяльності список був змінною екземпляра, і я змінював його безпосередньо, коли змінювалися будь-які дані. Через те, що вона була еталонною змінною, відбувалося щось дивне. Тож я змінив змінну посилання на локальну та використав іншу змінну для оновлення даних, а потім перейшов до addAll()функції, згаданої вище у відповідях.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.