마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

리사이클러뷰 첫번째 포지션을 클릭해서 데이터를 수정 했는데 맨 마지막 포지션이 바뀝니다

0 추천
@Override
public void onBindViewHolder(@NonNull AddRepairItemAdapterViewHolder holder, int position) {
    if(list.size()>position){
        setText(holder,position);
        setViewClickEvent(holder,position);

    }
}


@SuppressLint("HandlerLeak")
private void setText(AddRepairItemAdapterViewHolder holder, int position) {

    handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg){
            if(msg.arg1 == 1){
                try {
                    holder.tv_repairEdit_itemTitle.setText(consumable_dataBridge.getConsumableArrayList("0").get((Integer) msg.obj).consumableTitle);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }else if(msg.arg1 == 2){
                try {
                    holder.et_repairEdit_itemMemo.setText((CharSequence) msg.obj);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };

}
jay_choi (530 포인트) 님이 2022년 12월 1일 질문
list.size()>position 는 항상 true 아닌가요? 굳이 if문을 왜 집어넣으셨는지 궁금하네요.
그리고 setText 안에서 하는일이 단순히 텍스트를 설정해 주는일인데 왜 Handler가 사용되고 있는지 코드만으로는 명확하지가 않네요.
consumable_dataBridge.getConsumableArrayList("0")
위의 코드가 비동기로 처리되는 것 같다는 추측은 하겠는데, 별도의 설명없이는 모르겠네요.
어쨋든 Handler 내부가 문제의 주범으로 보이긴 합니다.

2개의 답변

0 추천

님의 증상이 발생할 수 있는 경우를 말씀드리면, 리사이클러뷰 어댑터(어댑터라고 부를게요)는 뷰홀더를 화면에 보이는 갯수 + 알파만큼만 생성을 하고 스크롤시에 재사용을 합니다. 따라서 스크롤이 발생할 때는 기존에 이미 생성되었던 뷰홀더가 사용되므로, 이전에 뷰홀더에 설정했던 값들이 남아 있게 됩니다. 님의 경우는 Handler까지 사용했기 때문에, 원하지 않은 포지션에 있는 뷰홀더의 내용이 갱신될 수 있습니다.

따라서 외부에서 어댑터에 필요한 데이터를 먼저 다 가져오고 난 다음에 어댑터를 갱신하세요.

어댑터에 사용하는 데이터는 flag를 하나 두어서 Handler사용했던 msg.arg를 대체하시던가, 아니면 클래스를 두개 나누어서 멀티뷰타입을 사용하셔도 됩니다.

 

flag를 사용할 경우,

public class ListItem {
    private final int itemType;
    private final title;
    private fianl memo;
   
   // Constructor, Getters 생략
}

 

멀티뷰타입을 사용할 경우, (ViewHolder가 두개 필요해서 코드는 많아집니다만, 상황에 맞게 사용하면 코드가 좀 더 깔끔해 질 수 있습니다.)

public abstract class ListItem {
    public abstract int getViewType();
    
   // Constructor, Getters 생략
}

public class ConsumableItem extends ListItem {
    
    private final title;

     // Constructor, Getters 생략
 
    @Override
    public int getViewType() {
       return R.layout.item_consumable;
    }
}

public class MemoItem extends ListItem {
    
    private final memo;

     // Constructor, Getters 생략
 
    @Override
    public int getViewType() {
       return R.layout.item_memo;
    }
}

 

spark (226,720 포인트) 님이 2022년 12월 2일 답변
spark님이 2022년 12월 2일 수정
필요한 데이터를 비동기적으로 한꺼번에 가져오는 코드는 Java는 RxJava, Kotlin은 Coroutine을 사용하면 큰 문제없이 해결이 가능합니다. 그렇지 않으면, 아무래도 제약사항이 많게 됩니다. 이게 전세계의 대부분의 개발자들이 해당 라이브러리를 광범위하게 사용되는 여러가지 이유 중의 하나입니다.
그리고 혹시 제가 말씀드린 구조를 사용할 수 없다면,
뷰홀더 내에 있는 뷰들을 값을 설정하기 전에 초기화를 해주시는 것이 좋을 것 같구요. 그리고
같은 뷰홀더에 두개이상의 Handler가 접근하고 있는 경우에 대한 처리를 하셔야 할 것 같아요. 먼저 실행 중인 Handler를 취소하고 최근에 것만 실행이 되게 하는 구조가 필요할 수 있습니다. 그래야 메모리 leak같은 걸 최소화 할 수 있지 않을까 하는 생각이 듭니다.
참고로 Glide같은 라이브러리 어떻게 백그라운드 처리를 하는지 확인하시면 도움이 될 겁니다.
0 추천
각 뷰홀더들이 바인딩 될 때마다 setTexr 를 호출하며 handler 를 초기화 하는 것으로 보이는데요,

setViewClickEvent 에서 해당 handler 를 통한 이벤트 처리를 한다면
마지막 뷰홀더 바인딩 시 초기화된 handler 가 동작하지 않을까요?

어떤 포지션의 아이템을 클릭하더라도 마지막 포지션이 반응할 것처럼 보입니다.

handler 초기화는 adapter 초기화시 1회만 하시고,
handler 를 통해 이벤트 처리를 하시려면 holder나 position 을 arg 에 담아서 메시지 처리하도록 수정해보세요,
익명 님이 2022년 12월 5일 답변
아 초기화 1회랑 position도 담아서 처리하는 방식으로 하면 될 것 같네요! 감사합니다~
...