використання notifyItemRemoved або notifyDataSetChanged за допомогою RecyclerView в Android


83

Я створюю список карток для відображення за допомогою RecyclerView, де кожна картка має кнопку для видалення цієї картки зі списку.

Коли я використовую notifyItemRemoved () для видалення картки в RecyclerView, він видаляє елемент і чудово анімує, але дані у списку не оновлюються коректно.

Якщо замість цього я переключаюся на notifyDataSetChanged (), тоді елементи у списку видаляються та оновлюються правильно, але тоді картки не анімуються.

Хтось має досвід використання notifyItemRemoved () і знає, чому він поводиться інакше, ніж notifyDataSetChanged?

Ось декілька кодів, які я використовую:

private List<DetectedIssue> issues = new ArrayList<DetectedIssue>();

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    if(position >0){
        RiskViewHolder riskHolder = (RiskViewHolder)holder;
        final int index = position - 1;
        final DetectedIssue anIssue = issues.get(index);

        riskHolder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    int index = issues.indexOf(anIssue);
                    issues.remove(anIssue);
                    notifyItemRemoved(index);

                    //notifyDataSetChanged();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

@Override
public int getItemCount() {
    return (issues.size()+1);
}

3
спробуйте notifyItemRemoved (індекс + 1)
pskink

1
Індекс правильний. Як я вже казав, все працює нормально, якщо я замість цього використовую notifyDataSetChanged () .....
революційний

2
ви пробували notifyItemRemoved (індекс + 1)?
pskink

1
Ого, моя точна проблема! Дякуємо, що позбавили мене від спрощення спрощення мого коду, щоб зрозуміти питання.
SMBiggs

2
liist.remove (позиція); notifyItemRemoved (позиція); notifyItemRangeChanged (позиція, getItemCount ()); Для видалення кожного разу верхнього елемента.
Рохіт Банділ

Відповіді:


107

Використовуйте notifyItemRangeChanged (позиція, getItemCount ()); після notifyItemRemoved (позиція);
Вам не потрібно використовувати індекс, просто використовуйте позицію. Дивіться код нижче.

private List<DetectedIssue> issues = new ArrayList<DetectedIssue>();

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    if(position >0){
        RiskViewHolder riskHolder = (RiskViewHolder)holder;

        riskHolder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    issues.remove(position);
                    notifyItemRemoved(position);
                    //this line below gives you the animation and also updates the
                    //list items after the deleted item
                    notifyItemRangeChanged(position, getItemCount());

                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

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

4
З документації: "Ви повинні використовувати параметр позиції лише під час отримання відповідного елемента даних всередині цього методу, і не слід зберігати його копію. Якщо вам потрібна позиція елемента пізніше (наприклад, у прослуховувачі кліків), використовуйте RecyclerView. ViewHolder.getAdapterPosition (), яка матиме оновлену позицію адаптера. "
Хуан Крус Солер

1
@Akshay Mahajan Стара тема, вибачте, але notifyItemRemoved(position);чудово працює сама (з анімацією). Що notifyItemRangeChanged(position, getItemCount());робить? Я не бачу різниці. Дякую
Йохан Дахмані

Не повинно бути getItemCount () - позиція, оскільки itemCount означає кількість елементів після видаленого елемента?
Michał Ziobro

1
так, другий параметр - діапазон змінених елементів. використання підрахунку предметів означає, що кожен предмет після зміни позиції змінюється.
Тревіс Кастільо,

2
@YohanDahmani, notifyItemRemoved (позиція); не спрацює, якщо ви приміряєте останній елемент ... IndexOutOfBoundException, який ви отримаєте.
Bajrang Hudda

32

Пробували

public void removeItem(int position) {
    this.taskLists.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position, getItemCount() - position);
}

і працює як оберіг.


6
Не могли б ви пояснити, чому ви використовуєте getItemCount() - positionзамість просто getItemCount()?
hamena314

@ hamena314 насправді notifyItemRangeChanged (int position, int itemCount) мента, що в елементах "position" змінилися, а з "position", "itemCount" змінилися елементи, тому розумно передавати лише елементи після "positon" замість того, щоб передавати список усіх пунктів. Або замість передачі "getItemCount () - position" ми можемо передати getItemCount ().
Джей

17

моя помилка, notifyItemChanged (position) безпорадний, елемент позиції можна видалити, і елемент позиції + 1 добре, але елементи починаються з позиції + 2, ви отримаєте виняток, будь ласка, використовуйте notifyItemRangeChanged (position, getItemCount ()); після notifyItemRemoved (позиція);

подобається це:

public void removeData(int position) {
    yourdatalist.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position,getItemCount());
}

2
будь-ласка, надайте детальну інформацію про те, що зміниться цим шляхом, і як це допоможе вирішити проблему.
AndroidMechanic - Virus Patel

це спрацювало. позиція об'єкта рядка також буде оновлена.
ralphgabb

1

Як запропонував @pskink, це могло бути (index + 1) у моєму випадку з notifyItemRemoved(index+1), можливо, тому, що я резервую верхній індекс, тобто position=0для заголовка.


0

Ви можете використовувати getLayoutPosition()зRecyclerView.ViewHolder

getLayoutPosition() надає точну позицію елемента в макеті і код

holder.removeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Position for remove
                int modPosition= holder.getLayoutPosition();
                //remove item from dataset
                numbers.remove(modPosition);
                //remove item from recycler view
                notifyItemRemoved(modPosition);
            }
        });

1
Ви повинні використовувати getAdapterPosition, як зазначено в документації , ви ризикуєте створити невідповідності.
marcos E.

0
**my solution looks like this**

this way is unnecessary to use the heavy method:
 //notifyItemRangeChanged(xx,xx)

/**
 * 
 * recyclerView的item中的某一个view,获取其最外层的viewParent,也就是item对应的layout在adapter中的position
 *
 * @param recyclerView
 * @param view:can be the deep one inside the item,or the item itself .
 * @return
 */
public static int getParentAdapterPosition(RecyclerView recyclerView, View view, int parentId) {
    if (view.getId() == parentId)
        return recyclerView.getChildAdapterPosition(view);
    View viewGroup = (View) view.getParent();
    if (viewGroup != null && viewGroup.getId() == parentId) {
        return recyclerView.getChildAdapterPosition(viewGroup);
    }
    //recursion
    return getParentAdapterPosition(recyclerView, viewGroup, parentId);
}




//wherever you set the clickListener .
holder.setOnClickListener(R.id.rLayout_device_item, deviceItemClickListener);
holder.setOnLongClickListener(R.id.rLayout_device_item, deviceItemLongClickListener);


@Override
public boolean onLongClick(View v) {
    final int position = ViewUtils.getParentAdapterPosition(rVDevicesList, v, R.id.rLayout_device_item);
    return true;
}

0

У моєму випадку я використовую постачальника вмісту та спеціальний адаптер RecyclerView із курсором. У цьому рядку коду ви повідомляєте:

getContext().getContentResolver().notifyChange(uri, null);

Припустимо, що у вашому адаптері reciclerView (кнопка Видалити):

Uri currentUri = ContentUris.withAppendedId(DatabaseContract.ToDoEntry.CONTENT_URI_TODO, id);
int rowsDeleted = mContext.getContentResolver().delete(currentUri, null, null);
if (rowsDeleted == 0) {
    Log.d(TAG, "onClick: Delete failed");
} else {
    Log.d(TAG, "onClick: Delete Successful");
}

А у вашому постачальнику баз даних:

case TODO_ID:
selection = DatabaseContract.ToDoEntry._ID + "=?";
selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};
rowsDeleted = database.delete(DatabaseContract.ToDoEntry.TODO_TABLE_NAME, selection, selectionArgs);
if (rowsDeleted != 0){
    getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;

-2

Вам слід додати слухач видалення в клас ViewHolder

 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                   onCancel(getAdapterPosition());

            }
        });

  private void onCancel(int position) {
        if (position >= issues.size())
            return;
        issues.remove(position);
        notifyItemRemoved(position);
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.