Android에서 RecyclerView와 함께 notifyItemRemoved 또는 notifyDataSetChanged 사용


83

RecyclerView를 사용하여 표시 할 카드 목록을 만들고 있습니다. 여기서 각 카드에는 목록에서 해당 카드를 제거하는 버튼이 있습니다.

RecyclerView에서 카드를 제거하기 위해 notifyItemRemoved () 를 사용 하면 항목이 제거되고 잘 애니메이션되지만 목록의 데이터가 올바르게 업데이트되지 않습니다.

그 대신 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 (index + 1) 시도
pskink

1
색인이 정확합니다. 내가 대신 notifyDataSetChanged ()를 사용하면 모든 것이 잘 작동한다고 말했듯이 .....
혁명적 인

2
notifyItemRemoved (index + 1)을 시도 했습니까?
pskink

1
와, 내 정확한 문제! 질문을 명확히하기 위해 코드를 단순화하는 수고를 덜어 주셔서 감사합니다.
SMBiggs

2
liist.remove (위치); notifyItemRemoved (위치); notifyItemRangeChanged (위치, getItemCount ()); 항상 최상위 요소를 제거합니다.
Rohit Bandil

답변:


107

사용 notifyItemRangeChanged (위치에서 getItemCount ()); notifyItemRemoved (position); 후
인덱스를 사용할 필요가없고 위치 만 사용하면됩니다. 아래 코드를 참조하십시오.

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 (). "
Juan Cruz Soler

1
@Akshay Mahajan 오래된 스레드가 죄송하지만 notifyItemRemoved(position);(애니메이션과 함께) 혼자서 잘 작동 합니다 . 무엇을 notifyItemRangeChanged(position, getItemCount());하고있다? 차이가 보이지 않습니다. 감사합니다
Yohan Dahmani 2017 년

itemCount는 항목을 제거한 후 항목 수를 의미하므로 getItemCount ()-위치가 아니어야합니까?
Michał Ziobro

1
예, 두 번째 매개 변수는 변경된 항목의 범위입니다. 항목 수를 사용한다는 것은 위치 이후의 모든 항목이 변경됨을 의미합니다.
Travis Castillo

2
@YohanDahmani, notifyItemRemoved (위치); 당신이 얻을 마지막 item..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 실제로는 "position"항목에서 notifyItemRangeChanged (int position, int itemCount) 항목이 변경되고 "position"에서 "itemCount"항목이 변경되었으므로 모든 목록을 전달하는 대신 "positon"이후 항목 만 전달하는 것이 좋습니다. 항목. 또는 "getItemCount ()-position"을 전달하는 대신 getItemCount ()를 전달할 수 있습니다.
Jay

17

내 실수, notifyItemChanged (position) 는 무력하며, 위치 항목은 제거 될 수 있으며, 위치 +1 항목은 괜찮지 만 항목은 위치 +2에서 시작됩니다. 예외가 발생합니다. notifyItemRangeChanged (position, getItemCount) 를 사용하십시오. ()); notifyItemRemoved (position);

이렇게 :

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

2
이렇게하면 변경되는 사항과 문제 해결에 어떻게 도움이되는지에 대한 세부 정보를 제공하십시오.
AndroidMechanic-Viral 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

제 경우에는 Cursor와 함께 Content Provider와 Custom RecyclerView Adapter를 사용합니다. 이 코드 줄은 알림을 보내는 곳입니다.

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

recyclerView 어댑터 (삭제 버튼)에서 가정 :

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);
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.