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

동적 중첩 리사이클러뷰 아이템 추가시 랜덤변경(?) 문제

0 추천

RoutineModel.java (부모)

public class RoutineModel {
    private List<RoutineDetailModel> routineDetailsList;
    private String routine;

    public RoutineModel(String routine) {
        this.routine = routine;
    }

    public List<RoutineDetailModel> getRoutineDetailsModel() {
        return routineDetailsList;
    }

    public void addDetails(RoutineDetailModel item) {
        if(routineDetailsList == null) {
            routineDetailsList = new ArrayList<>();
        }
        this.routineDetailsList.add(item);
    }
    public boolean removeDetails(int index) {
        if(routineDetailsList == null || index >= routineDetailsList.size() || index < 0) return false;
        this.routineDetailsList.remove(index);
        return true;
    }

    public String getRoutine() {
        return routine;
    }


    public int getDetailsSize() {
        if(routineDetailsList == null) return 0;
        return routineDetailsList.size();
    }
}

 

MultipleAdapter.java ( 어댑터)

public class MultipleViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    private final Context context;
    private List<Object> items;
    private OnItemClickListener onItemClickListener;

    public MultipleViewAdapter(Context context, List<Object> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if(viewType == 1){
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
            return new RoutineViewHolder(itemView);
        }
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_detail_item, parent, false);
        return new RoutineDetailsViewHolder(itemView);
        
    }


    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object object = items.get(position); //Object or Generic class or abstarct base Model class parent of Routine and RoutineDetails>
        
        if(object instanceof RoutineModel) {
            updateRoutineViews((RoutineViewHolder) holder, (RoutineModel) object, position);
        } else if(object instanceof RoutineDetailModel) {
            updateRoutineDetailsViewHolder((RoutineDetailsViewHolder) holder, (RoutineDetailModel) object, position);
        }
    }

    private void updateRoutineViews(RoutineViewHolder holder, RoutineModel routineItem, int position){
        holder.routine.setText("Routine " + routineItem.getRoutine());
        
        holder.addSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(onItemClickListener != null) onItemClickListener.onClick(v, position);
            }
        });

        holder.deleteSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(onItemClickListener != null) onItemClickListener.onClick(v, position);
            }
        });
    }
    @Override
    public int getItemViewType(int position) {
        Object object = items.get(position);
        if(object instanceof RoutineModel){
            return 1;
        }
        //else if instanceOf RoutineDetailModel return 0
        return 0;
    }

    @Override
    public int getItemCount() {
        if(items == null) return 0;
        return items.size();
    }

    public Object getItem(int position) {
        if(this.items == null || position < 0 || position >= this.items.size())
            return null;
        return this.items.get(position);
    }

    public void swapData(List<Object> newItems) {
        if (newItems != null) {
            this.items = newItems;
            notifyDataSetChanged();
        }
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public interface OnItemClickListener{
        void onClick(View view, int position);
        void onLongClick(View view, int position);
    }


    public static class RoutineViewHolder extends RecyclerView.ViewHolder  {
        public TextView routine;
        public Button addSet;
        public Button deleteSet;

        public RoutineViewHolder(@NonNull View itemView) {
            super(itemView);
            //initViews(); in constructor
            routine = itemView.findViewById(R.id.routine);
            addSet = itemView.findViewById(R.id.add_set);
            deleteSet = itemView.findViewById(R.id.delete_set);
        }
    }

    public static class RoutineDetailsViewHolder extends RecyclerView.ViewHolder {
        
    }
}

 

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private MultipleViewAdapter adapter;
    private List<RoutineModel> routineList;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        contentList.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MultipleViewAdapter(this, new ArrayList<>());
        contentList.setAdapter(adapter);

        adapter.setOnItemClickListener(new MultipleViewAdapter.OnItemClickListener() {
            @Override
            public void onClick(View view, int position) {
                Object item = (Object) adapter.getItem(position);
                if(item instanceof RoutineModel) {
                    RoutineModel routineModel = (RoutineModel) item;
                    if (view.getId() == R.id.add_set) {
                        int weight = randomInt(99);
                        routineModel.addDetails(new RoutineDetailModel(routineModel.getDetailsSize() + 1, weight));
                        adapter.swapData(getMixedList()); // OR add item to adapter and notify item inserted

                    } else if (view.getId() == R.id.delete_set) {

                        boolean deleted = routineModel.removeDetails(routineModel.getDetailsSize() - 1); // -1 !!! to delete last item
                        adapter.swapData(getMixedList()); // OR remove item from adapter and notify item removed

                    }

                }
            }

            @Override
            public void onLongClick(View view, int position) {
                //empty
            }
        });

        initFakeData();
        adapter.swapData(getMixedList());
    }

    private List<Object> getMixedList() {
        List<Object> mixedList = new ArrayList<>();
        for(RoutineModel rm: routineList){
            mixedList.add(rm);
            if(rm.getRoutineDetailsModel() != null && rm.getRoutineDetailsModel().size() > 0){
                for(RoutineDetailModel rmdetilas: rm.getRoutineDetailsModel()){
                    mixedList.add(rmdetilas);
                }
            }
        }
        return mixedList;
    }


    private void initFakeData() {
        routineList = new ArrayList<>();
        for(int i = 0; i < 5; i++){
            RoutineModel routineModel = new RoutineModel(String.valueOf(i + 1));
            for(int j = 0; j < 4; j++){
                routineModel.addDetails(new RoutineDetailModel(j+1, randomInt(99)));
            }
            routineList.add(routineModel);
        }
    }

    private int randomInt(int max) {
        return (int) Math.floor(Math.random() * max);
    }
}
codeslave (3,940 포인트) 님이 2021년 1월 26일 질문
codeslave님이 2021년 1월 28일 수정

2개의 답변

0 추천
제가 보기에는 Nested RecyclerView를 쓰지 않아도 될 것처럼 보입니다만.  RecylcerView한 개에 Routine별로 헤더를 달면 되는 걸로 보이네요. 변경된 아이템만 갱신하기 위해서 DiffUtil이나 DiffUtil이 구현된 ListAdapter를 쓰시면 될 것 같구요. 동적으로 RecyclerView를 추가하는 경우는 본 적이 없는 것 같아요. 그렇게 하려면 고려해야할 요소들이 많아져서 너무 복잡해 지거든요. 예를 들면 View pool의 갯수 조정, RecyclerView 간에 뷰공유 등등, 좀 어려운 영역을 건드려야 하기 때문에, 저라면 순수 공부하는 목적이 아니라면 그냥 RecyclerView  하나로 구현을 할 것 같습니다. Adapter에 화면에 필요한 데이터만 제공해 주면 될 것 같아요.
spark (227,830 포인트) 님이 2021년 1월 26일 답변
흠 너무 어려운 방법 or 비효율적인 방법으로 시도하고있었던건가요..
딱 보자마자 루틴리스트 안에 또다른 싱세 리스트가 있기에 리사이클러뷰의 리사이클러뷰를 사용해야된다고 생각했었는데..
DiffUtill이라는것은 찾아봤는데 대강 제가 원하는 딱 변경된 아이템만 변경해주고
notifydatasetchanged를 사용안해 비효율을 막는다? 라는 것 같네요..

그런데 헤더라는것은 정확하게 무엇인지 감이안잡히는데 검색해봐도 header / footer
정도만 나오는것같은데 이거 말씀 맞으실까요..?
0 추천

음.. 제가 보기엔 개념이 헷갈리는 상태입니다.

아래 동영상을 보시면, (제가 만들라다가 시간이 없어서)
https://www.youtube.com/watch?v=EyUjw6b5gXE

그래서 강좌를 보면 이분은 소스공개를 해 놨는데요, 소스를 보면

public class Item {
    private String itemTitle;
    private List<SubItem> subItemList;
...

위에처럼, 데이터 구조를 클래스로 정의했죠.
클래스 Item의 목록은 부모 recyclerview로
SubItem 클래스의 목록은 부모 하나마다 존재하니까,
N개가 존재하는 겁니다.
당연히 child recyclerview도 N개가 생성되고요.

그래서 강좌의 소스를 좀 더 자세히 디버깅까지 해가면서 이해하시기 바랍니다.

결론적으로 리사이클러뷰에서 additem을 만들어서 외부에서 제어하긴 힘들어요.
(불가능하진 않지만, 구현한다고해도 성능이 떨어집니다.)

데이터를 바꾸어서 다시 그리는 개념이라고 생각하면 됩니다.

즉 원본 데이터를 바꾸어 다시 던지면 그 바뀐 원본 데이터에 맞게
그리는 역할만 하는 것이죠.


아래는 부모 리사이클러뷰의 onBindViewHolder입니다.
SubItemAdapter를 생성해서 매칭하죠?
부모가 N개면, N개의 리사이클러뷰가 생성이되고,
데이터에서 item 하나하나마다 다르게 있는 subitem을 각각
리사이클러뷰에 던지는 겁니다.

@Override
    public void onBindViewHolder(@NonNull ItemViewHolder itemViewHolder, int i) {
        Item item = itemList.get(i);
        itemViewHolder.tvItemTitle.setText(item.getItemTitle());

        // Create layout manager with initial prefetch item count
        LinearLayoutManager layoutManager = new LinearLayoutManager(
                itemViewHolder.rvSubItem.getContext(),
                LinearLayoutManager.VERTICAL,
                false
        );
        layoutManager.setInitialPrefetchItemCount(item.getSubItemList().size());

        // Create sub item view adapter
        SubItemAdapter subItemAdapter = new SubItemAdapter(item.getSubItemList());


        itemViewHolder.rvSubItem.setLayoutManager(layoutManager);
        itemViewHolder.rvSubItem.setAdapter(subItemAdapter);
        itemViewHolder.rvSubItem.setRecycledViewPool(viewPool);
    }

바깥에서 Add버튼을 눌렀을 때,
item - subitem 데이터 구조를 바꾸어서 던지면 다시 그리는 겁니다.

Will Kim (43,170 포인트) 님이 2021년 1월 27일 답변
즉 부모 아이템 B에 추가를 해야 한다면,
List<Item> items 를 하나하나 루프를 돌아서
items.get(i).getName().equals("B") 일때
items.getSubItems().add(subItem) 한 뒤에,
그 데이터 구조를 부모 리사이클러뷰로 던져서 다시 그리는 겁니다.

리사이클러뷰안에서 서치 뷰 연계하는 것도
다 루프를 돌아서 원본 데이터 구조를
한카피 복사한뒤에
해당 글자가 들어가는 거 빼고
다 remove 한 뒤에,
혹은 빈 리스트 만들고 있는 것만 추가해서
다시 그리는 겁니다.

게다가
childRecyclerview가 부모가 3개면
완전히 서로 다른 3개가 생겨야 하는데
어댑터가 전역으로 선언되어 있으면 꼬이는 거죠.
차일드 끼리 서로 연결시켜 놓았기 때문입니다.

왜 그렇게 했는지는 이해가 됩니다.
어떻게든 외부에서 추가하려고 한 것이겠죠.
그렇게 하려면, 추가하려는 데이터가
내건지 확인하는 코드를 작성해서
모든 자식 리사이클러뷰가 확인해야 하고,
그렇게 해도 자식의 어댑터를 부모안에서
전역으로 선언해서는 안됩니다.

올바른 방법은 데이터를 재구성해서 매번 다시 그리는 로직입니다.
감사합니다..이해가 정확하게 되지는 않지만...
저같은경우에는 onCreateViewHolder에서 부모리사이클러뷰당 딱 하나의 자식 리사이클러뷰를 만들어주어 계속해서 쓰는? 형태인데.. 이렇게 되면 스크롤 되기전에는  onBindViewHolder가 재활용을 위해 호출이 안되기때문에 문제가 없지만 제가 기존 올렸던 사진처럼 데이터를 계속 추가하게 됐을때, 스크롤이 되고 그때 onBindViewHolder가 호출되면 기존 가지고 잇던 자식 어댑터때문에 문제가 된다 이말 씀인가요..? (정확히 뭐가 어떻게 문제되는지는 이해를 못했습니다..ㅜ)
그래서 onBinViewHolder가 재활용을 위해 호출될때마다 새로 어댑터를 만들어서 새로 데이터를 바인딩해야한다 이거같은데.. 맞나 모르겠습니다..
그리고 질문 몇가지만 하겠습니다. 제가 다른곳에서 질문을하고 조언을 받아 코드를 짰습니다. 본문 코드는 새로짠 코드입니다. (길어서 크게 상관없는 코드는 지웠습니다.)
그 사람은 어댑터를 1개만 씁니다. 뷰홀더 2개는 동일하구요.
게다가 List<> 타입도 기존 데이터 클래스가 아니라 Object 클래스를 사용해서 타입을 검사해서 하더라구요. 결과적으로만 보면 해봤는데 잘 작동하더라구요.
그런데 찝찝한게 한가지가 있는데 소스를 보시면 아시겠지만..어댑터를 한개만 사용하다보니
List<Object> 아이템에 Routine 클래스(부모) RoutineDetail 클래스(중첩,서브)가 두개 다 들어갑니다. Routine 클래스는 또 따로 List<RoutineDetail> 아이템을 가지고 있습니다..
 List<Object> 아이템에 부모아이템을 저장하고 그 부모가 가지고 있는 서브 아이템을 또 따로 추출해서 이 아이템에 저장하는것이죠...
그렇게해서 어댑터에서 한번에 데이터를 뿌리는 방식을 사용하던데..괜찮은 방법인가요?
 저는 결과가 나오니 다행이라고 생각하지만 좀 찝찝한 점은
위에 말씀드린대로 List<Object> 클래스에 부모 데이터만이 아니라 부모데이터에서 추출한 서브 데이터까지 저장하는게 좀 마음에 안들다고 해야하나..
깔끔하지 못한것같습니다.
기존 리사이클러뷰를 사용할때처럼 List<RoutineModel> 이렇게 하나만 사용해서 데이터를 뿌리고 싶었는데.. 이렇게하니 어댑터 하나만 사용해서는 안의 서브아이템까지는 뿌리지 못하더라구요..

그래서 결국 지저분하더라도 저렇게 써야하나 하는데 어떤가요..?
두번째 질문은 선생님께서 적어주신 코드를 읽어봤는데 (영상)
보시면 onBindViewHolder에서 서브 어댑터를 새로 생성하고 setAdapter해주잖아요?
그런데 제가 듣기로는 setAdapter는 최초 1회만 진행해주는게 좋다고 들었습니다. 계속 호출하면 추후 문제가 될 수 있다고 어디선가 봤는데요..
그래서 여태 onBindViewHolder에서 setAdapter를 진행을 안했었는데..
문제가 없나요?
중첩 리사이클러뷰는
한개의 부모 리사이클러뷰 하단에 N개의 리사이클러뷰를 표시하는 구조이고요.
당연히 N개의 자식 어댑터가 각자 생성되는 것입니다.

두번째 질문에 대한 답변으로는 전혀 문제가 안된다는 것입니다.
여태껏 수없이 많이 그렇게 코딩했어요.

전에 올리신 animated gif 같은 이미지가 너무 빨라서 제대로 이해가 되지 않지만,
제가 이해한 바로는 각 서브 항목들이 따로 데이터가 추가가 되야 하는데 차일드끼리 서로 같이 연동이 되어서 발생하는 문제입니다.

onBinViewHolder가 재활용을 위해 호출될때마다 새로 어댑터를 만들어서 새로 데이터를 바인딩해야한다 --> 맞습니다. (그냥 그려주는 용도라고 보시면 됩니다)

List<Object> 클래스에 부모 데이터만이 아니라 부모데이터에서 추출한 서브 데이터까지 저장하는게 좀 마음에 안들다고 해야하나..
깔끔하지 못한것같습니다
--> 객체구조로 프로그래밍하는 것이 처음에는 불편하실 수도 있어요.
     그러나 그렇게 가야 합니다. 객체 지향 프로그래밍을 우리가 하고 있는 겁니다.
     익숙해진다면 그게 더 깔끔하다고 생각될 겁니다. (저는 제가 드린 강좌 링크가 매우 깔끔합니다)

좀 더 개발하다보면, 객체 구조로 json 통신을 해야 하고,
그 데이터를 다층 구조의 객체로 통신을 해야 합니다.
즉, Object 안에 List<Object2>가 있고,
Object2 안에 List<Object3>  가 있는 것은 흔한 구조입니다.

그런 다층 구조를 표현하려면 중첩 리사이클러뷰 혹은 3중첩 리사이클러뷰가 적절합니다.

질문자께서 만든 로직의 첫번째 아이템의 차일드 목록과 두번째 아이템의 차일드목록은 독립적인 것이라고 이해했는데,
그러면 독립적이므로 독립적으로 new 로 생성하는 겁니다.

다 만드신 후에 소스를 다시 한번 보세요. 아니면 제가 소개한 동영상의 소스를 깊이 있게 들여다 보세요. 100% 이해를 한 뒤에 코딩을 하셔야 훨씬 진도가 빨리 나갑니다. 벌써 수주가 지났을 텐데요, 절반만 이해하고 코딩한 결과입니다.

외부에서 목록 안의 목록에 데이터를 추가하는 것은 리사이클러뷰의특성상 어렵고 비효율적입니다.
따라서 데이터를 다루고, 그것을 표시하는 역할만 하는 게 맞습니다.

또한가지 방식은 목록 안의 목록에 버튼을 넣는 것입니다.
그러면 그 버튼은 차일드 OnBindViewHolder에서 처리하면 됩니다.
마지막 차일드인 경우 하단에 버튼을 Visible하게 만드는 방법을 사용하면,
맨 마지막 차일드 하단에 버튼이 나오고, 그 버튼을 누르면
팝업을 표시하고, 추가하는 것이죠.
그때는 모든 목록을 보여 줄 필요가 없이 해당 목록만 보여줄 수 있겠죠.
그러나 UI적인 요구사항하고 맞지 않으면 그 방법은 어렵고요.
감사합니다..처음에는 저도 setAdapter를 onBindViewHolder에 넣어서 진행해줬는데 setAdpater를 최초 1회만 해줘야 좋다는것을 보고 계속 돌다돌다보니 이렇게까지왔네요 감사합니다..
선생님 그러면 외부에서 목록안의 목록에 추가한느것은 비효율적이라고 끝에말씀하셨는데,, 그럼 부모 어댑터내부에서 클릭이벤트처리를 하라는 말씀인가요
선생님이 걸어주신 링크의 소스를 깃에서 받아 돌려보고 코도를 천천히 읽어봤는데요. 소스 코드 자체는 이해가 갑니다만 선생님께서 외부에서 목록안의 목록을 추가하는 것에 대한것이 잘 이해가 안갑니다. 이 자체가 이해가 안간다기보다는 영상의 소스에서도 어댑터에서 메인액티비티는 외부니까.. 메인액티비티에서 서브 아이템의 목록을 만들고 그걸 이제 다시 부모 아이템에 넣고 이제 어댑터를 만들어주던데요.. 여기서도 그러면 이 서브 아이템을 추가하는 방식자체는 방식은 별로 좋지 못한방식인건가요?

그외에 뭐 setInitialPrefetchItemCount 라던지, RecyclerView Pool 이라던지 이런것 빼고는 코드가 이해는 갑니다..
표시하고자 하는 데이터를 외부에서 던져서 그리는 방법이 가장 심플하고요.

부모 밑에 N개의 차일드어댑터가 있다면, 어떤 차일드 어댑터를 다시 그릴 것인지 루프를 돌아서 그 차일드 어댑터를 다시 생성해야 하니까 비효율적이라고 보는 겁니다.
훔,,그럼 onBindViewHolder에서도 setAdapter를 하는것은 계속해주는것은 좋지못한 방식은 맞다 이건가여..?
onBindViewHolder에서 계속 setAdapter를 하는 방법외에 다른 방법은 없습니다.
setAdapter는 그릴때 한번 하는 것입니다.
그러나 지금 하려는 것은 그린 뒤에도
Adapter의 데이터를 변경하려는 것이잖아요.

[일반적인 방식]
초기 데이터로 어댑터를 그린다.
추가된 정보를 데이터에 반영한다.
변경된 데이터로 어댑터를 또 그린다.
(반복)

[지금 하시려는 것]
초기 데이터로 어댑터를 그린다.
추가된 데이터만 리사이클러뷰로 던진다.
수정된 데이터를 적용하는 차일드 리사이클러뷰를 찾는다
그 해당하는 차일드 리사이클러뷰의 어댑터터에 추가된 데이터를 전달한다.
전달받은 데이터로 행을 추가한다.
(반복)

제가 보내드린 예시가 좋은 방법입니다.
[지금 하시려는 방법]이 좋지 않은 방법이라고 이야기 하는 겁니다.
지금하시려는 방법으로 해도, 계속 setAdapter를 해야 하는 것이고,
변경된 차일드 리사이클러뷰가 뭔지 찾아서 그것만 수정해야 하는 것입니다.
그런 방식이 일반적인 방식도 아니고,
리사이클러뷰가 그렇게 동작하는 라이브러리가 아니라,
위에 일반적인 방식으로 동작하도록 구현된 라이브러리입니다.

이야기가 계속 길어지는데,
이중 리사이클러 뷰를 그리는 방법을 샘플로 확인 했다면,
버튼을 눌러서 추가하는 것을 리사이클러뷰에서 하지말고
데이터에 반영하고, 리사이클러뷰에 또 던지면 되는 것입니다.

질문자의 또 다른 문제점은
좋은 샘플을 찾아서 이해한 뒤에
그것을 따라서 잘 구현 하고,
테스트를 통해서 문제를 보완하면 되는데,

누군가가 어떤 문제가 있다는 내용을 인터넷에 공지하면 그것에 너무 신경을 쓴다는 것입니다.
그 사람이 그 글을 언제 썼고, 그 사람이 한 이야기가 맞다는 보장도 없습니다.

그런 문제를 미연에 방지하는 방법은
리사이클러뷰 샘플 중에서 최신, 그리고 가장 좋은 평점과
스택오버플로우 같은 곳에서 선택된 답변에 있는 것들을 찾아서
여러개의 샘플을 실행해보고
가장 좋아 보이는 샘플을 선정해서
그걸로 구현하는 방법을 제안합니다.

개념이 제대로 정립되지 않은 상태에서
잘못된 정보로 구현한 소스 코드를 가지고
계속 스스로 문제를 해결하려고 한다면,
시간 소모가 매우 많습니다.

그럴바에는 그 시간을 좋은 샘플을 찾는 것에 집중하고,
내가 구현하고자 하는 것과 가장 유사하고, 활용이 가능한 샘플을 찾는 데
하루 정도만 할애해도,
무수하게 많은 샘플을 컴파일하고 실행해 볼 수 있습니다.

거기서 출발하는 게 초급자들에게는 가장 빠른 방법이고,
제대로 감을 잡을 수 있는 방법입니다.

가끔씩 좋지 않은 샘플로 시작해서,
그걸 스스로 해결하려고 하다가 개발을 포기하시는 분들을 여럿 봤습니다.
그 레벨에서 해결할 수 없는 것을 시도하는 것보다는
올바른 개념을 배운다는 느낌으로 접근하시기를 바랍니다.
그것이 가장 Shortest Path입니다.
아.... 그렇군요ㅠ따끔한 말씀 감사합니다. 안그래도 느끼고 있던 참이었습니다...
예를들면 이번 setAdapter()에서 제가 어디선가 본 글에서는 `setAdapter()는 최초 1회만 호출되어야한다` 때문에 다른 중첩 리사이클러뷰의 여러 샘플 코드를 봤을때 onBindViewHolder() 에서 중첩 리사이클러뷰의 데이터를 바인딩할때 setAdapter를 해주는데 이건.. 스크롤..재활용 될때마다 onBindBiewHolder는 여러번 호출되니까 setAdaper()도 자동으로 여러번 호출될테니 이건 `틀린 코드`다 하고 너무 빙빙 돌리다 이코드 저코드 작성하다 꼬이고 꼬이고 개념이 이해가 안가고 그렇게 된것같네요..

따끔한 말씀 감사합니다ㅜ

위의 일반적인 중첩 리사이클러뷰의 코드를 보고 다시한번 정리해보도록하겠습니다.
...