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

RecyclerView에서 2가지 문제가 생겼습니다.

0 추천

제가 지금 이중 리사이클러뷰를 이용하여 운동 로그를 추가할 수 있는 기능을 넣고 있는데요

 

문제가 만약 제가 인클라인 벤치프레스 추가하기 버튼을 누르면 인클라인 부분에만 추가하는 부분이 생겨야 하는데 사진처럼 디클라인 벤치프레스 부분에도 로그가 생깁니다..

 

또 다른 문제는 추가하기 버튼을 2번 이상누르면 그냥 빈 공간만 생기네요...

 

피드백 좀만 주시면 감사하겠습니다

[상위 리사이클러뷰]

public class RegisteredHealthLogAdapter extends RecyclerView.Adapter<RegisteredHealthLogAdapter.ViewHolder>{
    ArrayList<String> registeredLogs = new ArrayList<>();
    ArrayList<setList> setLists = new ArrayList<>();
    int set = 1;

    public class ViewHolder extends RecyclerView.ViewHolder {
        private RecyclerView recyclerView;
        private TextView textView;
        private Button button;

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

            textView = itemView.findViewById(R.id.registeredTextView);
            button = itemView.findViewById(R.id.addWeight);
            recyclerView = itemView.findViewById(R.id.todayLogRecyclerView);
        }

        public TextView getTextView() {
            return textView;
        }

        public Button getButton() { return button; }

        public RecyclerView getRecyclerView() { return recyclerView; }
    }

    public RegisteredHealthLogAdapter(ArrayList<String> list) {
        registeredLogs = list;
    }

    public void removeItem(int position) {
        registeredLogs.remove(position);
        notifyItemRemoved(position);
        notifyDataSetChanged(); // 갱신처리
    }

    @NonNull
    @Override
    public RegisteredHealthLogAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = null;

        v = LayoutInflater.from(parent.getContext()).inflate(R.layout.health_registered_add, parent, false);

        return new RegisteredHealthLogAdapter.ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull RegisteredHealthLogAdapter.ViewHolder holder, int position) {
        String item = registeredLogs.get(position);
        holder.getTextView().setText(item);

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(holder.itemView.getContext());
        holder.getRecyclerView().setLayoutManager(linearLayoutManager);

        HealthLogSetAdapter adapter = new HealthLogSetAdapter(setLists);
        holder.getRecyclerView().setAdapter(adapter);

        holder.getButton().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 추가하기 버튼이 클릭되면 아이템 추가
                setList list = new setList(String.valueOf(set++), "0kg", "0 reps");
                HealthLogSetAdapter.addItem(list);
                notifyDataSetChanged();
            }
        });
    }

    @Override
    public int getItemCount() { return registeredLogs.size(); }

    public void addItem(String data) {
        registeredLogs.add(data);
    }
}

 

[하위 리사이클러뷰]

public class HealthLogSetAdapter extends RecyclerView.Adapter<HealthLogSetAdapter.ViewHolder>{
    static ArrayList<com.mango.wellbeingdiary.main.health.list.setList> setList = new ArrayList<>();
    TextView setTextView, weightTextView, repsTextView;

    public class ViewHolder extends RecyclerView.ViewHolder{
        public ViewHolder(@NonNull View itemView) {

            super(itemView);

            setTextView = itemView.findViewById(R.id.set);
            weightTextView = itemView.findViewById(R.id.healthWeight);
            repsTextView = itemView.findViewById(R.id.reps);
        }

        public TextView getSetTextView() { return setTextView; }

        public TextView getWeightTextView() { return weightTextView; }

        public TextView getRepsTextView() { return repsTextView; }
    }

    public HealthLogSetAdapter(ArrayList<setList> list) {
        setList = list;
    }

    @NonNull
    @Override
    public HealthLogSetAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = null;

        v = LayoutInflater.from(parent.getContext()).inflate(R.layout.health_log_add_set, parent, false);

        return new HealthLogSetAdapter.ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull HealthLogSetAdapter.ViewHolder holder, int position) {
        setList item = setList.get(position);
        holder.getSetTextView().setText(item.getSetsText());
        holder.getWeightTextView().setText(item.getWeightText());
        holder.getRepsTextView().setText(item.getRepsText());
    }

    @Override
    public int getItemCount() {
        return setList.size();
    }

    public static void addItem(setList data) { setList.add(data); }

}

 

매력적인수박 (670 포인트) 님이 2021년 7월 24일 질문

1개의 답변

0 추천

왜 HealthSetLogAdapter의 item을 static 으로 선언하셨나요? 아이템이 자식 뷰홀더간에 공유되면 님이 보시는 증상이 생길 수 있겠네요.

그리고 제가 테스트를 해보니, 다시 생각해보셔야할  코드 디자인의 문제점이 있습니다.

        holder.getButton().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 추가하기 버튼이 클릭되면 아이템 추가
                setList list = new setList(String.valueOf(set++), "0kg", "0 reps");
                HealthLogSetAdapter.addItem(list);
                notifyDataSetChanged();
            }
        });

부모어댑터와 자식어댑터가 공유하는 데이터타입이 일치하지 않는 다는 것입니다. 님의 경우는 부모어댑터에는

ArrayList<String> registeredLogs  자식 어댑터 에는 String + List<String> 을 사용하고 있습니다. 그리고 자식어댑터의 아이템에 대한 컨트롤을 부모어댑터 안에서 하려고 하고 있습니다. 그럼, 다음 질문에 답을 해보세요.

님이 사용하는 데이터가 예를 들어 로컬데이터베이스 또는 서버에서 오는 거라고 생각해 보죠. 그리고 님이 자식어댑터에 뭔가를 추가하면 서버에 저장이 되고 이것이 화면에 반영이 되어야 한다면 위의 구조에서는 어떻게 처리를 하실 수 있나요?

우선, 부모어댑터에서 사용해야 하는 데이터 타입은 자식어댑터의 데이터타입을 포함해야 합니다.
 

public class Routine {
    private final String equipmentName;
    private List<SetsItem> setsItems;
    ...

    public static Routine newEmptyRoutine(String equipmentName) {
        return new Routine(equipmentName, new ArrayList<>());
    }
}

public class SetsItem {
    private final Sets sets;
    private final Weight weight;
    private final Reps reps;
  ...

    public static SetsItem newEmptySetsItem() {
        return new SetsItem(new Sets(1), new Weight(0), new Reps(0));
    }
}

public enum SetsUnit {
    NONE(""),
    KG("kg"),
    REPS("reps");

    private final String text;

    SetsUnit(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

public abstract class SetsField {
    protected final int value;
    protected SetsUnit unit;
  ...
   public String getDisplayText() {
        return value + " " + unit.getText();
    }
}

public class Reps extends SetsField {
    public Reps(int value) {
        super(value, SetsUnit.REPS);
    }
}

public class Sets extends SetsField {
    public Sets(int value) {
        super(value, SetsUnit.NONE);
    }
}

public class Weight extends SetsField {
    public Weight(int value) {
        super(value, SetsUnit.KG);
    }
}

 

위와 비슷한 구조가 되어야 어댑터 안에서 다음과 같이 처리가 가능합니다.

public class RoutineAdapter extends RecyclerView.Adapter<RoutineViewHolder> {

    private List<Routine> routines = new ArrayList<>();
    private RoutineViewHolder.Listener routineListener;

    public RoutineAdapter(List<Routine> routines) {
        this.routines = routines;
    }

    public void setItems(List<Routine> routines) {
        this.routines = routines;
        notifyDataSetChanged();
    }

    public void updateRoutine(Routine routine) {
        int index = this.routines.indexOf(routine);
        if (index < 0) throw new NoSuchElementException("Cannot find routine " + routine);
        this.routines.set(index, routine);
        notifyItemChanged(index);
    }

    public void setRoutineListener(RoutineViewHolder.Listener routineListener) {
        this.routineListener = routineListener;
    }

    @NonNull
    @Override
    public RoutineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_routine, parent, false);
        return new RoutineViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RoutineViewHolder holder, int position) {
        Routine routine = routines.get(position);
        holder.bind(routine, routineListener);
    }

    @Override
    public int getItemCount() {
        return routines.size();
    }
}

public class RoutineViewHolder extends RecyclerView.ViewHolder {

    public interface Listener {
        void onAddItem(Routine routine);
    }

    private final TextView equipmentTxt;
    private final Button addBtn;
    private final RecyclerView setsRcv;

    private final SetsAdapter adapter;

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

        equipmentTxt = itemView.findViewById(R.id.equipmentTxt);
        addBtn = itemView.findViewById(R.id.addBtn);
        setsRcv = itemView.findViewById(R.id.setsRcv);

        adapter = new SetsAdapter(Collections.emptyList());
        setsRcv.setAdapter(adapter);
    }

    public void bind(Routine routine, Listener listener) {
        equipmentTxt.setText(routine.getEquipmentName());
        adapter.setItems(routine.getSetsItems());
        addBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onAddItem(routine);
            }
        });
    }
}

 

자식어댑터에서의 처리도 간결해 집니다.

public class SetsAdapter extends RecyclerView.Adapter<SetsViewHolder> {

    private List<SetsItem> items = new ArrayList<>();

    public SetsAdapter(List<SetsItem> items) {
        this.items = items;
    }

    public void setItems(List<SetsItem> items) {
        this.items = items;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public SetsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_sets, parent, false);
        return new SetsViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull SetsViewHolder holder, int position) {
        holder.bind(items.get(position));
    }

    @Override
    public int getItemCount() {
        return items.size();
    }
}

public class SetsViewHolder extends RecyclerView.ViewHolder {
    TextView setsTxt, weightTxt, repsTxt;

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

        setsTxt = itemView.findViewById(R.id.setsTxt);
        weightTxt = itemView.findViewById(R.id.weightTxt);
        repsTxt = itemView.findViewById(R.id.repsTxt);
    }

    public void bind(SetsItem item) {
        setsTxt.setText(item.getSets().getDisplayText());
        weightTxt.setText(item.getWeight().getDisplayText());
        repsTxt.setText(item.getReps().getDisplayText());
    }

 

그리고 바뀐 데이터 구조를 이용하면 외부에서 아이템을 추가하거나 삭제하는 부분도 컨트롤이 더 수월해집니다.

public class MainActivity extends AppCompatActivity {

    private RoutineAdapter routineAdapter;

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

        bindViews();
    }

    private final List<Routine> routines = new ArrayList(Arrays.asList(
        Routine.newEmptyRoutine("Decline bench press"),
        Routine.newEmptyRoutine("Incline bench press")
    ));

    private void bindViews() {
        RecyclerView routineRcv = findViewById(R.id.routineRcv);
        routineRcv.addItemDecoration(new DividerItemDecoration(routineRcv.getContext(), RecyclerView.VERTICAL));

        routineAdapter = new RoutineAdapter(routines);
        routineAdapter.setRoutineListener(new RoutineViewHolder.Listener() {
            @Override
            public void onAddItem(Routine routine) {
                addSets(routine);
            }
        });
        routineRcv.setAdapter(routineAdapter);

    }

    private void addSets(Routine routine) {
        List<SetsItem> setsItems = routine.getSetsItems();
        setsItems.add(SetsItem.newEmptySetsItem());
        routineAdapter.updateRoutine(routine);
    }

}

 

데이터 핸들링 관련된 로직, 리스너 관련된 로직은 거의 대부분의 경우에 액티비티나 프레그먼트에서 컨트롤 하는게 일반적입니다. 왜냐하면 데이터의 흐름은 어댑터에서 관여해서는 안되는 사항이기 때문입니다. 어댑터는 주어진 데이터를 받아서 뷰홀더에게 그려달라고 요청하는 역할만 하면 됩니다. 그리고 데이터를 가져오고 아이템을 추가, 저장하는 일은 별도의 클래스가 백그라운드 스레드에서 해주는게 좋습니다.

spark (227,530 포인트) 님이 2021년 7월 24일 답변
spark님이 2021년 7월 24일 수정
감사합니다. 많이 배웠습니다. spark님 코드 보고 변수명부터 가독성있게 조금씩 수정해보겠습니다.
...