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

리사이클러뷰 스크롤시 랜덤 데이터 변경 및 생성문제.

0 추천

운동 일지 앱을 만들고 있습니다.

일지앱이라 동적 아이템추가기능이 필요해서 구현을 했습니다.

버튼을 누르면 루틴이 추가되고, 추가된 루틴에서도 버튼을 누르면 상세아이템(세트, 무게, 횟수)이 추가됩니다.

따라서 루틴, 루틴상세 타입 두가지입니다.(정확히는 Footer까지 넣어서 세가지입니다.) 

어댑터는 하나를 사용했고 List<Object>를 사용해서 모든 타입을 여기서 관리하기때문에

하나의 리스트만으로도 여러타입 관리가 가능했고 표현이 가능했습니다.

 

그런데 상세아이템을 추가후 데이터를 입력후에 계속해서 추가하다보면 아이템이 많아져

스크롤이 될만한 지점에서 저 입력한 데이터의 아이템이 그대로 입력된채 아이템이 생성됩니다.

그러다가 또 계속 아이템을 생성하다가 그런 현상이생기고 여기서 스크롤을 위아래로하면

이 데이터가 랜덤으로 다른 아이템에 옮겨?갑니다...

스크롤시 데이터가 옮겨가는거보면 뷰홀더문제인것같은데요.. 구글링해보니 이러한 문제들이 있긴한것같은데

해결방법은 아니었습니다.. (getItemId를 이용한다던데 getItemViewType을 이용한다던지..)

다만, 홀더에서 holder.setIsRecyclerView(false) 가 유일하게 해결책(추가코드와 함께)이 되었는데요..

이 메소드가 보니 뷰홀드의 재활용을 막는거더라구요..그런데 뷰홀더의 재활용을 막는거면

의미가 리사이클러뷰의 의미가 없기때문에 되도록 사용을 하고싶지않은데 방법이 없을까요?

 

public class RoutineAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    final static int TYPE_ROUTINE = 1;
    final static int TYPE_ROUTINE_DETAIL = 2;
    final static int TYPE_ROUTINE_FOOTER = 3;

    private Context context;
    private List<Object> mItems = new ArrayList<>();
    OnRoutineItemClickListener routinelistener;
    OnRoutineAddClickListener routineAddListener;

    public void updateRoutineList(List<Object> newRoutineList) {
        final RoutineDiffUtil diffCallback = new RoutineDiffUtil(this.mItems, newRoutineList);
        final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);

        this.mItems.clear();
        this.mItems.addAll(newRoutineList);
        diffResult.dispatchUpdatesTo(this);
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        View itemView;
        if(viewType == TYPE_ROUTINE){
            itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
            return new RoutineViewHolder(itemView);
        }
        else if(viewType == TYPE_ROUTINE_DETAIL){
            itemView = LayoutInflater.from(context).inflate(R.layout.routine_detail_item, parent, false);
            return new RoutineDetailViewHolder(itemView);
        }
        else {
            itemView = LayoutInflater.from(context).inflate(R.layout.add_routine_item, parent, false);
            return new RoutineAddFooterViewHolder(itemView);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object obj;
//        holder.setIsRecyclable(false); //이게 맞는지 잘모르겠다
        switch (getItemViewType(position)) {
            case TYPE_ROUTINE:
                obj = mItems.get(position);
                setRoutineData((RoutineViewHolder) holder, (RoutineModel) obj);
                break;
            case TYPE_ROUTINE_DETAIL:
                obj = mItems.get(position);
                RoutineDetailModel item = (RoutineDetailModel) obj;
                ((RoutineDetailViewHolder) holder).setDetailItem(item);
                break;
            case TYPE_ROUTINE_FOOTER:
                break;
        }
    }

    private void setRoutineData(RoutineViewHolder holder, RoutineModel routineItem){
        holder.routine.setText(routineItem.getRoutine());
    }

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

    @Override
    public int getItemCount() {
        if(mItems == null)
            return -1;
        return mItems.size() + 1; // footer 때문에 +1
    }

    @Override
    public int getItemViewType(int position) {
        if(position == mItems.size()) { // footer를 마지막에 위치시키기 위함
            return TYPE_ROUTINE_FOOTER;
        }
        else {
            Object obj = mItems.get(position); // 커스텀 LinearlayoutManager의 IOOE 에러가나서 안보이던거였음.
            if(obj instanceof RoutineModel) {
                return TYPE_ROUTINE;
            }
            else {
                // obj instanceof RoutineDetailModel
                return TYPE_ROUTINE_DETAIL;
            }
        }
    }
    
    // 루틴 추가인터페이스
    public interface OnRoutineAddClickListener {
        public void onAddRoutineClick();
    }

    public void setOnAddRoutineClickListener(OnRoutineAddClickListener listener) {
        this.routineAddListener = listener;
    }
    
    // 상세 추가/삭제 인터페이스
    public interface OnRoutineItemClickListener {
        public void onAddBtnClicked(int curRoutinePos);
        public void onDeleteBtnClicked(int curRoutinePos);
        public void onWritingCommentBtnClicked(int curRoutinePos);
    }

    public void setOnRoutineClickListener(OnRoutineItemClickListener listener) {
        this.routinelistener = listener;
    }

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

        public RoutineViewHolder(@NonNull View itemView) {
            super(itemView);

            initViews();

            addSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onAddBtnClicked(getAdapterPosition());
                }
            });

            deleteSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onDeleteBtnClicked(getAdapterPosition());
                }
            });

            comment.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onWritingCommentBtnClicked(getAdapterPosition());
                }
            });
        }

        private void initViews() {
            routine = itemView.findViewById(R.id.routine);
            addSet = itemView.findViewById(R.id.add_set);
            deleteSet = itemView.findViewById(R.id.delete_set);
            comment = itemView.findViewById(R.id.write_comment);
        }
    }

    private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
        private TextView set;
        private EditText weight;


        public RoutineDetailViewHolder(@NonNull View itemView) {
            super(itemView);
            initViews();
        }

        private void initViews() {
            set = itemView.findViewById(R.id.set);
            weight = itemView.findViewById(R.id.weight);
        }

        private void setDetailItem(RoutineDetailModel item) {
            set.setText(item.getSet().toString() + "세트");
            weight.setText(item.getWeight());
        }
    }

    private class RoutineAddFooterViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public RoutineAddFooterViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.add_text);
            ConstraintLayout regionForClick = itemView.findViewById(R.id.clickable_layout);
            regionForClick.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (routineAddListener != null) {
                        routineAddListener.onAddRoutineClick();
                    }
                }
            });
        }
    }
}

 

 

codeslave (3,940 포인트) 님이 2021년 3월 16일 질문

답변 달기

· 글에 소스 코드 보기 좋게 넣는 법
· 질문에 대해 추가적인 질문이나 의견이 있으면 답변이 아니라 댓글로 달아주시기 바랍니다.
표시할 이름 (옵션):
개인정보: 당신의 이메일은 이 알림을 보내는데만 사용됩니다.
스팸 차단 검사:
스팸 검사를 다시 받지 않으려면 로그인하거나 혹은 가입 하세요.
...