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

리사이클러뷰 list add 질문

0 추천
새 프로젝트로 해서 해봤는데 안돼서 다시 질문드립니다!
enerigpy (2,110 포인트) 님이 2023년 2월 17일 질문
public class MainActivity extends AppCompatActivity implements MyRecyclerAdapter.MyRecyclerViewClickListener {
    private List<ItemData> dataList = new ArrayList<>();

    private MyRecyclerAdapter adapter;

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

        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        List<ItemData> items = getItems();
        dataList.add(items.get(0));
        dataList.add(items.get(1));
        adapter = new MyRecyclerAdapter(dataList);
        recyclerView.setAdapter(adapter);
        adapter.setOnClickListener(this);
    }

    private List<ItemData> getItems() {
        return Arrays.asList(new ItemData(R.drawable.ic_launcher_background), new ItemData(R.drawable.ic_launcher_foreground));
    }

    public void removeItem(int position) {
        dataList.remove(position);
        adapter.notifyItemRemoved(position);
    }

    @Override
    public void onItemClicked(int position) {
        dataList.get(position).toggleChecked();
        adapter.notifyItemChanged(position);
    }

    @Override
    public void onItemLongClicked(int position) {

    }

    @Override
    public void onImageViewClicked(int position) {

    }
}
public class ItemData {
    private int image;
    private boolean checked;

    public ItemData(int image) {
        this(image, false);
    }

    public ItemData(int image, boolean checked) {
        this.image = image;
        this.checked = checked;
    }

    public int getImage() {
        return image;
    }

    public void setImage(int image) {
        this.image = image;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }

    public void toggleChecked() {
        this.checked = !this.checked;
    }

    public int getDrawableId() {
        return checked ? R.drawable.ic_sc_btn1 : R.drawable.ic_sc_btn;
    }
}
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {

    private final List<ItemData> itemData;

    public MyRecyclerAdapter(List<ItemData> itemData) {
        this.itemData = itemData;
    }

    public interface MyRecyclerViewClickListener {
        void onItemClicked(int position);

        void onItemLongClicked(int position);

        void onImageViewClicked(int position);
    }

    private MyRecyclerViewClickListener mListener;

    public void setOnClickListener(MyRecyclerViewClickListener listener) {
        this.mListener = listener;
    }

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

    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
        final ItemData item = getItem(position);
        holder.bind(item);

        holder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) mListener.onItemClicked(holder.getAdapterPosition());
            }
        });

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) mListener.onItemClicked(holder.getAdapterPosition());
            }
        });

        holder.image.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) mListener.onImageViewClicked(holder.getAdapterPosition());
            }
        });

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (mListener != null) {
                    mListener.onItemLongClicked(holder.getAdapterPosition());
                    return true;
                }
                return false;
            }
        });
    }

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

    private ItemData getItem(int position) {
        return itemData.get(position);
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        Button button;
        ImageView image;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            image = itemView.findViewById(R.id.image);
            button = itemView.findViewById(R.id.rebtn);

        }

        public void bind(ItemData item) {
            image.setImageResource(item.getImage());
            button.setBackgroundResource(item.getDrawableId());
        }
    }
}
정확하게 어떤 문제인가요? 빌드는 되는데, 화면에 아무 것도 안나온다는 건가요. 아니면 컴파일이 안된다는 건가요? 첫번째 문제라면  item_list.xml을 올려보세요.
item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <Button
        android:id="@+id/rebtn"
        android:layout_width="47dp"
        android:layout_height="42.54dp"
        android:layout_marginTop="26dp"
        android:layout_marginEnd="30dp"
        android:background="@drawable/ic_sc_btn"
        app:layout_constraintEnd_toEndOf="@+id/image"
        app:layout_constraintTop_toTopOf="@+id/image" />

    <ImageView
        android:id="@+id/image"
        android:layout_width="286dp"
        android:layout_height="119dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

실행은 되는데 이미지가 추가가 안되어서 화면에 출력안되네요
혹시 RecyclerView속성에 Layoutmanager는 설정
하셨나요? 저는 xml안에 설정이 돠어 있어요.
오 드디어 해결했어요!! xml에서는 어떻게 하셨나요??
java class에 코드로 넣으니 되네요!
android:layoutManager="자동완성"
자동완성 부분에서 LinearLayoutManager를 선택하거나 입력(직접 입력시는 패키지명 포함)하시면 됩니다.
https://developer.android.com/reference/androidx/recyclerview/widget/LinearLayoutManager
감사합니다. 추가로 질문이 있는데 이제 이미지와 버튼이 두개씩 생성되었면 각 버튼을 클릭했을 때의 상태를 객체? 변수에 저장하려고 하는데 어떻게 구현해야 할까요!?

그리고 비슷한 개념으로 누를 때 각기 다른 링크도 걸고 싶은데 이건 어떻게 해야할까요? 방법이 영 다르니 어렵네요
버튼 두개의 상태를 저장하는 부분은 ItemData class 에 두번째 버튼에 대한 필드를 추가하고 첫번째 버튼 상태를 처리할 때와 동일하게 하시면 됩니다.
이벤트의 처리는 MyRecyclerViewClickListener를 왜 사용하는지 생각을 해보시면 금방 답이 나올겁니다.MainActivity와 RecyclerView 사이의 커뮤니케이션은 MyRecyclerViewClickListener을 통해 이루어집니다. MyRecyclerViewClickListener에서 발생하는 이벤트가 외부인 MainActivity에서 처리되어야 MyRecyclerViewAdapter는 재활용과 추후 변경에 유연해 집니다. MyRecyclerViewAdapter는 뷰홀더에 데이터를 받아서 표시하게 하는 역할에 집중하면 됩니다. 따라서 MyRecyclerViewClickListener를 구현하고 있는 MainActivity에서 원하시는 동작을 처리하시면 됩니다. 필요하다면 MyRecyclerViewClickListener에 메소드를 하나 더 추가하고 구현을 하면 되겠죠. 리스너를 사용하는 목적만 이해하시면 어려울게 전혀 없으므로, 천천히 구현해 보세요.
holder.button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse("https://youtu.be/"));
                context.startActivity(intent);
            }
        });
네 이런식으로 버튼 하나더 추가하여 구현했는데요

그럼 재활용을 할때 각각 다른 링크를 걸려고 하는데 어떻게 해야할까요!?
위에서 말씀 드린대로 액티비티를 호출하는 코드를 인터페이스를 구현하는 쪽에서 호출하도록 변경하세요.

public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {

    public interface MyRecyclerViewClickListener {
        ...
        void onOpenLink();
    }

   ...

   @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
        ...

        holder.button2.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (mListener != null) {
                    mListener.onOpenLink();
                    return true;
                }
                return false;
            }
        });
    }
});


public class MainActivity ... {

  ...

  @Override
  public void onOpenLink() {
         // 유투브 여는 코드
  }
}

다시 말씀드리지만 어댑터와 액티비티 간에 어떻게 이벤트를 처리하는지를 이해하려고 해보세요. 버튼클릭이벤트를 설정하는 것과 많이 다르지 않습니다. 버튼클릭은 안드로드이드가 정의한 인터페이스를 사용하는 것이고 어댑터에서는 내가 정의한 인터페이스를 사용하는 거예요.
네 감사합니다. onOpenLink로 이제 접근되어 가는중인데
dataList.get(0), dataList.get(1)에 대해 조건문을 쓰려고합니다.
if(dataList.get(0).toggleChecked() == checked) 이렇게하고 {} 안에 url을 넣으면 될거 같은데 dataList.get(0).toggleChecked() 이부분이 어떤거와 비교해야 하는지 감이 안오네요! true나 != null 넣으면 void와 안맞아서 안된다고 나오네요

public void onOpenLink(int position) {
        dataList.get(0).toggleChecked();
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("https://youtu.be/"));
        startActivity(intent);
    }
코드를 먼저 생각하지 마시고 로직을 먼저 생각하시면 쉽게.해갤될 문제입니다.아래가 로직에 해당하는 문장이죠.
현재 아이템의 버튼이 체크된 상태라면 유투브를 연다.
이제 이걸 작성하신 클래스들과 연결하시면 됩니다. 한글을 영어로 번역하는거랑 비슷해요.
If the current item is checked, then open YouTube
나머지는 관계된 클래스들만 확인하시먄 답이 나올겁니다.
네 어찌저찌 참거짓 조건문으로 구현은 했는데
이게 check를 구현하는게 공유가 되어서
각각 버튼 누르면 두개다 링크연결이 되는데 또 뭐가 문제일까요

public void onOpenLink(int position) {
        if(dataList.get(0).toggleChecked(checked) == true) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("https://youtu.be/"));
            startActivity(intent);
        }
    } // main

// itemData.java
public boolean toggleChecked(boolean checked) {
        this.checked = !this.checked;
        return !checked;
    }
버튼2에 대한 처리를 별도로 하셔야죠. 버튼1에서 사용하는 메소드를 그대로 사용하니 공유가 되는 거겠죠.
if(dataList.get(0).getButton2Checked()) {
   openYoutube();
}
두번째 버튼도 토클이 필요하다면 ItemData 클래스에 두번째 버튼에 대한 속성을 추가하시면 되겠죠.
아네네 당연히 버튼은 두개로 했어요!

public ItemData(int image, boolean checked, boolean checked2) {
        this.image = image;
        this.checked = checked;
        this.checked2 = checked2;
    }

rebtn2에서 링크가 걸리도록 했는데 리사이클 1번째 아이템 2번째 아이템에서 1번째 rebtn2에만 링크연결되어야하는데 그래서 dataList.get(0).toggleChecked(checked)
dataList.get(1).toggleChecked(checked) 의 조건문을 쓰려고 했는데 안되네요
이상하게 같은 링크로 타지네요.

if(dataList.get(0).toggleChecked2(checked2) == true) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("https://youtu.be/"));
            startActivity(intent);
        }

        if(dataList.get(1).toggleChecked2(checked2) == true) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("https://www.youtube.com/watch?v=a_k-dCgubFo"));
            startActivity(intent);
        }

public class ViewHolder extends RecyclerView.ViewHolder {
        Button button, button2;
        ImageView image;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            image = itemView.findViewById(R.id.image);
            button = itemView.findViewById(R.id.rebtn);
            button2 = itemView.findViewById(R.id.rebtn2);
        }

        public void bind(ItemData item) {
            image.setImageResource(item.getImage());
            button.setBackgroundResource(item.getDrawableId());
        }
    }
아이템마다 열어야 될 링크가 다른건가요?
그럼 열어야될 링크를 ItemData의 속성으로 만들면 굳이 아이템이 몇번째인지 체크할 필요가 없을 것 같은데요.

public class ItemData {
    private int image;
    private boolean checked;
    private boolean checked2;
    private String link;
}

ItemData selectedItem = dataList.get(position);
if(selectedItem.toggleChecked2(checked2)) {
     openUrl(selectedItem.getLink());       
}

private void openLink(String url) {
   Intent intent = new Intent(Intent.ACTION_VIEW);
   intent.setData(Uri.parse(link));
   startActivity(intent);
}

안되는 부분이 있다면 브레이크포인트를 걸고 디버깅을 하시면 어디서 문제가 생겼는지 파악하기가 쉬우므로 문제해결도 쉬워집니다.
ItemData selectedItem = dataList.get(position);
if(selectedItem.toggleChecked2(checked2)) {
     openUrl(selectedItem.getLink());       
}

이 부분이 어디에 추가해야하는지 이해가 안되네요 ㅜㅜ

holder.button2.setOnClickListener(new View.OnClickListener() 이 부분에서는 건들게 없는건가요?
혹시 디버깅은 해보셨나요?
디버깅을 해보셔서 한스텝씩 보셔야 어디가 문제인지 알기가 쉬우실 거예요. 버그 잡을 때는 기본적으로 해보셔야는 단계예요.
holder.button2.setOnClickListener 최종코드는 제가 본 적이 없어서 정확하지는 않지만,
제 추측이라면 현재 문제는 어댑터에서 이벤트를 할당할 때 있을 거라고 보여져요.
@Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
        ...

       holder.button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 아래처럼 원하는 인터페이스 메소드를 호출해야 함.
                if (mListener != null)
                    mListener.onButton2Clicked(holder.getAdapterPosition());
            }
        });
    }

// Activity

@Override
public void onButton2Clicked(int position) {
   ItemData selectedItem = dataList.get(position);
    selectedItem.toggleChecked2();
    adapter.notifyItemChanged(position);

    if(ItemData.getChcked2()) {
        openLink(selectedItem.getLink());
    }
}

public class ItemData {
    ....
    private boolean checked2;

    ...

    public boolean getChecked2() {
         return this.checked2;
    }   

    public void toggleChecked2() {
        this.checked2 = !this.checked2;
    }
}
네네 근데

ItemData selectedItem = dataList.get(position);
if(selectedItem.toggleChecked2(checked2)) {
     openUrl(selectedItem.getLink());       
}

이 부분은 어디 클래스에 넣어야 하는건가요?
이미 윗 댓글에 있는데요. MyRecyclerViewClickListener에 onButton2Clicked를 추가하고 구현했는데요. @Override 어노테이션을 보시면 이해가 가실거예요.
네 답변 잘받았습니다.

근데 계속해서 코드가 꼬이는거 같아서요

holder.button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) {
                    mListener.onOpenLink(holder.getAdapterPosition());
                }
            }
        });

이부분에서 string이 아니여서                      mListener.onOpenLink(String.valueOf(holder.getAdapterPosition()));
이렇게 바꿔줘야 하는거같고

@Override
    public void onButton2Clicked(int position) {
        ItemData selectedItem = dataList.get(position);
        selectedItem.toggleChecked2(checked2);
        adapter.notifyItemChanged(position);

        if(selectedItem.getChecked2(checked2)) {
            onOpenLink(selectedItem.getLink());
        }
    }

여기에서 getLink에 대한 link를 리턴하는걸 구현했는데 position에 대해 그거에 맞는 배열로 불러들어야 하는 문제가 있네요.

public String[] getLink() {
        String[] link = {"https://www.youtube.com/watch?v=a_k-dCgubFo",
        "https://youtu.be/"};

        return link;
    }
자바 생성자만 사용할 줄 알면 해결될 것 같은데요. 생성자에 link를 전달하세요.

public class ItemData {
     private int image;  
     private boolean checked1;
     private boolean checked2;
     private String link;

     public ItemData(int image, String link) {
         this.image = image;
         this.link = link;
     }
   
}

new ItemData(R.drawable.ic_1, "https://link1");
new ItemData(R.drawable.ic_2, "https://link2");

ItemData를 화면에 보여질 데이터로 취급하세요.
//main
dataList.add(new ItemData(R.drawable.ic_sc_btn1, "https://www.youtube.com/watch?v=a_k-dCgubFo"));
dataList.add(new ItemData(R.drawable.ic_sc_btn,  "https://youtu.be/"));

private List<ItemData> getItems() {
        return Arrays.asList(new ItemData(R.drawable.ic_launcher_background, "https://youtu.be/"), new ItemData(R.drawable.ic_launcher_foreground, "https://www.youtube.com/watch?v=a_k-dCgubFo"));
    }

// item class
public ItemData(int image, String link) {
        this(image, false, false, link);
    }

    public ItemData(int image, boolean checked, boolean checked2, String link) {
        this.image = image;
        this.checked = checked;
        this.checked2 = checked2;
        this.link = link;
    }

public String getLink() {
        return link;
    }

// adapter
holder.button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) {
                    mListener.onOpenLink(String.valueOf(holder.getAdapterPosition()));
                }
            }
        });

url누를때 디버깅 해보니 link에 null이 반환되는데 뭐가 문제일까요?

1개의 답변

+1 추천
 
채택된 답변

onOpenLink에 필요한 파라미터는 URL string입니다. 그런데 님의 코드를 보면

holder.button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mListener != null) {
                    String url = String.valueOf(holder.getAdapterPosition()); // 버그. 이건 URL이 아님.
                    mListener.onOpenLink(url);
                }
            }
        });

holder.getAdapterPosition()을 String으로 변환해서 넘기고 있습니다. 이건 URL이 아니고, 아이템의 위치예요. onOpenLink를 사용하려면 getLink()를 가져와서 넘기셔야 하고,  holder.getAdapterPosition()을 사용하려면, 제가 했던 것처럼 onButton2Checked 왁 같은 형태로 아이템의 위치를 넘기셔야 겠죠.

코드를 작성하실 때 클래스 이름이나 메소드 이름, 파라미터 이름 등을 잘 만드세요. 그리고 코드를 읽으실 때는 영어 문장이라고 생각하시고 읽도록 해보세요. 자연스러운 영어문장 처럼 될 수록 좋고 깔끔한 코드가 나옵니다.

spark (227,830 포인트) 님이 2023년 2월 19일 답변
enerigpy님이 2023년 2월 19일 채택됨
네네 처음에 저거대로 하려고 했는데 long클릭으로 하지않고 setonclick으로 하려고 해서요 근데 longclick으로 했을 때는

holder.button2.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (mListener != null) {
                    mListener.onOpenLink(link);
                    return true;
                }
                return false;
            }
        });

했는데 link에 null만 넘어가네요??
link라는 변수를 어떻게 가져왔는지 모르겠네요. 님처럼 하고 싶으면 아래와 같은 코드가 되어야 할 것 같은데요.

if (mListener == null) return false;

int pos = holder.getAdapterPosition();
String url = getItem(pos).getLink();
mListener.onOpenLink(link);
return true;
오 드디어 해결한거 같네요 감사해요!

url말고 link로 다 통일시켜서 했어요 그래도 상관없겠죠?
아 그리고 만약에 link 연결 막아두려면 방법이 있을까요? "" 이렇게 하면 당연히 안되고 있어서요!
버튼이 체크된 상태에서만 링크를 열고 싶은거죠?

f (mListener == null) return false;

int pos = holder.getAdapterPosition();
ItemData item = getItem(pos).
boolean canOpenLink = !item.getChecked2();

String url = canOpenLink ? item.getLink() : null;
mListener.onOpenLink(link);
return true;

// Activity
@Override
public void onOpenLink(@Nullable String link) {
     ....
     if (link == null) return;
     openLink(link);
}
추가 질문이 있으시면 새 질문으로 해주시면 읽기 편할 것 같네요.
...