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 당신 말이 맞아요. 문제는 이것이 작동하지 않는다는 것입니다. 어댑터가 참조를 참조하지 않습니다. 참조 자체를 가져옵니다. 따라서 새 데이터 세트를 빌드하면 새로운 참조가 있지만 어댑터는 여전히 이전 데이터 만 알고 있습니다.
놀라운 1

답변:


326

RecyclerView를 사용하고 있으며 제거 및 업데이트가 모두 잘 작동합니다.

1) 제거 : RecyclerView에서 항목을 제거하는 4 단계가 있습니다.

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 (위치);
feisal

3
저에게는 선 recycler.removeViewAt(position);(또는 오히려 recycler.removeAllViews())이 결정적인 선 이었습니다.
MSpeed

2
삭제하는 동안 성가신 글리치를 피하기 위해 고려해야 할 중요한 사항 :이 라인을 recycler.removeViewAt (position);
Angelo Nodari

7
NotifyDataSetChanged는 특히 매우 긴 목록을 처리하는 경우 과잉입니다. 몇 가지 항목에 대해 전체를 다시로드 하시겠습니까? 고맙지 만 사양 할게. 또한 recycler.removeViewAt (position)? 필요하지 않습니다. 또한 "mAdapter.notifyItemRemoved (position)"및 "mAdapter.notifyItemRangeChanged (position, list.size ())"를 모두 호출 할 필요는 없습니다. 일부를 제거하거나 제거하십시오. ONCE에 알리십시오. 그들 중 한 명에게 전화하십시오.
Vitor Hugo Schwaab

328

이것은 미래 방문자를위한 일반적인 답변입니다. 어댑터 데이터를 업데이트하는 다양한 방법이 설명되어 있습니다. 이 프로세스에는 매번 두 가지 주요 단계가 포함됩니다.

  1. 데이터 세트 업데이트
  2. 변경 사항을 어댑터에 통지

단일 항목 삽입

index에 "Pig"를 추가하십시오 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());

단일 항목 제거

목록에서 "Pig"를 제거하십시오.

단일 항목 제거

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

여러 항목 제거

목록에서 "Camel"및 "Sheep"를 제거하십시오.

여러 항목 제거

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

단일 항목 이동

"Sheep"을 한 위치에서 다른 위치 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 또는 결합 제거 다음에 삽입이 있습니까?
Semaphor

최소한의 예제에서는 notifyItemChanged로 충분합니다. 변경된 항목에 대해 onCreateViewHolder 및 onBindViewHolder를 호출하고 다른보기가 표시됩니다. 응용 프로그램에서 항목을 업데이트하는 데 문제가있어 다른보기가 생길 수 있지만 다른 문제가있는 것 같습니다.
Semaphor

@Suragch 내 질문에 대한 아이디어 : stackoverflow.com/questions/57332222/… 이 스레드에 나열된 모든 것을 시도했지만 아무 소용이 없습니다 :(

DiffUtils를 사용하여 변경하는 경우는 어떻습니까? 수동으로 .notify <Action> ()을 호출하는 것처럼 유동적이지 않습니다.
GuilhE

1
정말 볼에서 떨어졌다 Update single item, 있었어야 거북이처럼 I를
ardnew

46

이것이 나를 위해 일한 것입니다.

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

업데이트 된 목록 (내 경우에는 ArrayList로 변환 된 데이터베이스)이 포함 된 새 어댑터를 만들고 어댑터로 설정 한 후 시도 recyclerView.invalidate()하고 작동했습니다.


12
변경된 항목을 업데이트하는 대신 전체보기를 새로 고치지 않습니까?
TWilly

13
매번 새 어댑터를 만드는 대신 사용자 정의 어댑터 클래스에서 새 목록을 설정하기 위해 setter 메소드를 작성했습니다. 그 후, 그냥 전화를 걸면 YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();OP가 먼저 시도한 것처럼 들립니다 (이 경우 주석으로 추가하여 다른 사람에게 도움이 될 수 있습니다).
Kevin Lee

2
독자, 이것을 해결하지 마십시오. 이 답변은 효과가 있지만 더 효율적인 방법이 있습니다. 모든 데이터를 다시 로드 하는 대신 가장 효율적인 방법은 새 데이터를 어댑터에 추가하는 것입니다.
Sebastialonso

예, @TWilly에 동의합니다. UI에서 완전한 View를 다시 그립니다.
Rahul

18

이를 수행하는 두 가지 옵션이 있습니다. 어댑터에서 UI를 새로 고치십시오.

mAdapter.notifyDataSetChanged();

또는 recyclerView 자체에서 새로 고치십시오.

recyclerView.invalidate();

15

또 다른 옵션은 diffutil 을 사용하는 입니다. 원본 목록을 새 목록과 비교하고 변경 사항이있을 경우 새 목록을 업데이트로 사용합니다.

기본적으로 DiffUtil을 사용하여 이전 데이터와 새 데이터를 비교하고 notifyItemRangeRemoved, notifyItemRangeChanged 및 notifyItemRangeInserted를 호출하도록 할 수 있습니다.

notifyDataSetChanged 대신 diffUtil을 사용하는 간단한 예 :

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)

계산 목록이 큰 경우 계산 스레드가 기본 스레드에서 작동합니다.


1
이것이 정확하지만 어댑터 내부의 데이터를 어떻게 업데이트합니까? 어댑터에 List <Object>를 표시하고 List <Object>로 새 업데이트를 받았다고 가정 해 봅시다. DiffUtil은 차이를 계산하여 어댑터에서 전달하지만 원래 또는 새 목록과는 아무런 관련이 없습니다 (귀하의 경우 getItems(). 원본 목록에서 어떤 데이터를 제거 / 추가 / 업데이트
해야하는지

1
이 기사가 도움이 될 수 있습니다. .. medium.com/@nullthemall/…
j2emanue

@vanomart 일반적으로하는 것처럼 로컬 목록 필드를 새 목록 값으로 직접 바꾸면됩니다.이 접근법의 유일한 차이점은 notifyDataSetChanged () 대신 DiffUtil을 사용하여보기를 업데이트한다는 것입니다.
carrizo

내 생각은? : 나는 모든 것을 여기에 나열하지만 운 시도 stackoverflow.com/questions/57332222/...

답변 주셔서 감사합니다! dispatchUpdatesTo ?? 뒤에 "clear"& "addAll"을 넣어야하는 이유를 설명해 주시겠습니까? Tnx!
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"메서드를 사용하여 지금 구현하고있었습니다.
Vitor Hugo Schwaab

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()위의 답변에서 언급 한 함수에 전달했습니다 .

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