리사이클러뷰 아이템 클릭 이벤트 처리. (RecyclerView Item Click Event)

2019. 6. 11. 11:26


1. 리사이클러뷰(RecyclerView) 아이템 클릭.

지난 글 [안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)]와 [안드로이드 리사이클러뷰 사용 예제. (Android RecyclerView Example)]에서 리사이클러뷰의 기본 사용법과 예제를 살펴봤는데요, 이제, 리사이클러뷰 아이템 클릭 이벤트를 처리하는 방법에 대해 알아보도록 하겠습니다.


리스트뷰를 사용해 본 경험이 있다면, 리스트뷰의 아이템 클릭 이벤트를 OnItemClickListner 리스너를 통해 처리할 수 있다는 것을 알고 있을 것입니다. [안드로이드 리스트뷰 기본 사용법. (Android ListView) - 2.5 ListView 클릭 이벤트 처리]에서도 설명했듯이, ListViewsetOnItemClickListener()를 사용해 이벤트 리스너를 지정할 수 있죠.


그래서 리사이클러뷰에서도 setOnItemClickListener() 사용과 유사한 방법을 통해 아이템 클릭 이벤트를 처리할 수 있을 것 같은데요. 하지만, [안드로이드 개발 참조문서. RecyclerView] 문서를 아무리 찾아봐도, setOnItemClickListener() 또는 그와 유사한 기능을 제공하는 메서드를 찾을 수가 없습니다.


그렇다면 리사이클러뷰에서는 아이템을 표시만 할 수 있고 아이템 클릭 이벤트는 사용할 수 없는 걸까요? 아니면 아이템 클릭 이벤트를 처리하는, 리스트뷰에서의 아이템 클릭 이벤트 처리 방법과는 다른 방법이 있는 걸까요?

2. 아이템 클릭 이벤트 처리의 주체.

기본적으로 리스트뷰는 유사한 형태와 크기를 가진 아이템뷰를 세로 방향 한 줄로 나열합니다. 이러한 표시 형태의 단순함으로 인해, 화면에 표시된 아이템을 클릭했을 때, 몇 번째 아이템이 클릭되었는지 계산하는 과정이 비교적 간단합니다. 그래서 아이템 클릭 이벤트 처리 기능을 리스트뷰가 직접 제공합니다.


이에 반해 리사이클러뷰는, 리스트뷰에 비해 훨씬 유연하고 다양한 형태로 아이템을 표시하게 만들어 줍니다. [안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)]에서도 설명했듯이, 레이아웃매니저(LayoutManager)를 통해 아이템을 배치하는 형태를 다양하게 구성할 수 있고, 아직 언급하지는 않았지만, 애니메이션(animation) 효과 등을 손 쉽게 적용해 다이나믹한 화면을 구성할 수 있게 만들어줍니다. 하지만 이러한 장점들이, 아이템 클릭 이벤트 처리를 복잡하게 만드는 요인이 됩니다.


그래서 리사이클러뷰는 아이템 클릭 이벤트 리스너를 자신이 직접 다루지 않고, 아이템 뷰에서 OnClickListener를 통해 처리하게 만들어놓았습니다.

3. 리사이클러뷰 뷰홀더(ViewHolder)에서 아이템 클릭 이벤트 처리하기.

"아이템 뷰에서 OnClickListener를 통해 아이템 클릭 이벤트를 처리한다"는 문장이 어떻게 코드로 매핑되는지 바로 떠오르나요?


잠시, 이전 글 [안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)]에서 설명했던 내용을 다시 한번 상기시켜 보겠습니다.


어댑터를 통해 만들어진 각 아이템 뷰는 "뷰홀더(ViewHolder)"객체에 저장되어 화면에 표시되고, 필요에 따라 생성 또는 재활용(Recycle)됩니다.


그럼 이제 머리 속에 코드의 형태가 그려지나요?


  • 아이템 뷰에서 클릭 이벤트를 직접 처리하고,
  • 아이템 뷰는 뷰홀더 객체가 가지고 있으니,
  • 아이템 클릭 이벤트는 뷰홀더에서 작성.


아래 코드처럼, 뷰 홀더가 만들어지는 시점에 클릭 이벤트를 처리하면 됩니다.


    public class ViewHolder extends RecyclerView.ViewHolder {

        ViewHolder(View itemView) {
            super(itemView) ;

            // 아이템 클릭 이벤트 처리.
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO : process click event.
                }
            });
        }
    }

4. 아이템 위치(position) 알아내기.

자, 이제 아이템 클릭 이벤트를 처리할 수 있게 되었습니다. onClick() 메서드에서 로그 메시지를 출력하는 코드를 넣어보면, 아이템 클릭 시 메시지가 표시되는 것을 확인할 수 있는데요. 그런데 아직 해야 할 일이 남아 있습니다. 바로, 현재 클릭 이벤트가 발생된 아이템 위치(position)를 알아내는 것이죠.


보통, 앱에서 리사이클러뷰 아이템 클릭 이벤트를 사용할 때는, 단순히 로그 메시지 확인이나 하려고 이벤트 처리 코드를 작성하지 않습니다. 현재 선택된 아이템에 따라 다른 액션을 실행하거나, 아이템과 연결된 데이터를 확인, 수정, 삭제하는 등의 기능을 실행하기 위해 클릭 이벤트를 구현하죠. 그래서 아이템 클릭 이벤트에서 가장 처음으로 해야 할 일은, 현재 클릭 이벤트가 발생한 아이템의 위치를 알아내는 것입니다.


자, 어떻게 자신의 위치를 알아낼 수 있을까요? onClick() 메서드에는 단지 View 객체에 대한 참조만 전달될 뿐, 위치(position)에 대한 정보는 없네요. 그럼 View 객체의 태그(tag)를 통해 위치(position)를 직접 저장하고 관리해야 할까요?


그럴 필요는 없습니다. 이런 경우를 위해, 리사이클러뷰의 뷰홀더(android.support.v7.widget.RecyclerView.ViewHolder)에는 현재 자신의 위치(position)를 확인할 수 있는 getAdapterPosition() 이라는 메서드가 제공되고 있습니다.

뷰홀더의 getAdapterPosition()


    public class ViewHolder extends RecyclerView.ViewHolder {

        ViewHolder(View itemView) {
            super(itemView) ;

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = getAdapterPosition() ;
                    if (pos != RecyclerView.NO_POSITION) {
                        // TODO : use pos.
                    }
                }
            });
        }
    }

ViewHolder.getAdatperPosition() 메서드가 리턴하는 값은 어댑터 내 아이템의 위치(position)이지만, 리턴 값이 NO_POSITION인지에 대한 검사는 해줘야 합니다. notifyDataSetChanged()에 의해 리사이클러뷰가 아이템뷰를 갱신하는 과정에서, 뷰홀더가 참조하는 아이템이 어댑터에서 삭제되면 getAdapterPosition() 메서드는 NO_POSITION을 리턴하기 때문입니다.

5. 아이템 위치(position)로 데이터 리스트 접근하기.

자, 클릭 이벤트가 발생한 아이템 위치(position)를 알아냈으니, 어댑터가 참조하고 있는 데이터 리스트로부터 데이터를 가져오는 것은 간단하죠.


    public class ViewHolder extends RecyclerView.ViewHolder {

        ViewHolder(View itemView) {
            super(itemView) ;

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = getAdapterPosition() ;
                    if (pos != RecyclerView.NO_POSITION) {
                        // 데이터 리스트로부터 아이템 데이터 참조.
                        RecyclerItem item = mData.get(pos) ;

                        // TODO : use item.
                    }
                }
            });
        }
    }

6. 리사이클러뷰 외부(액티비티, 프래그먼트, ...)에서 아이템 클릭 이벤트 처리하기.

위에서 리사이클러뷰 아이템 이벤트 처리 코드를 작성했지만, 이는 어댑터 범주 안에서만 유효한 방법입니다. 그런데 어떤 경우에는 어댑터의 외부, 즉, 액티비티 또는 프래그먼트에서 아이템 클릭 이벤트를 처리하고 싶은 경우가 있습니다.


이런 경우 가장 쉽게 구현할 수 있는 방법은, 어댑터에 직접 리스너 인터페이스를 정의한 다음, 액티비티 또는 프래그먼트에서 해당 리스너 객체를 생성하고 어댑터에 전달하여 호출되도록 만드는 것입니다. 보통 "커스텀 리스너(Custom Listener)"라고 부르는데, 자식(여기서는 어댑터가 해당)이 부모(액티비티)의 이벤트 핸들러를 호출해야 할 필요가 있을 때 사용하는 방법입니다.


아래와 같은 절차를 통해 커스텀 리스너(Custom Listener)를 작성할 수 있습니다.

커스텀 리스너 작성 절차


6.1 커스텀 리스너(Custom Listener) 인터페이스 정의.

가장 먼저 해야 할 일은 자식 요소 안에서 새로운 리스너(Listener) 인터페이스를 정의하는 것입니다. 리스너에서 선언되는 메서드의 이름과 파라미터의 형식은 필요에 따라 정하면 됩니다.


[STEP-1] "XxxAdapter.java" - 어댑터 내에서 커스텀 리스너 인터페이스 정의.
public class XxxAdapter extends RecyclerView.Adapter<XxxAdapter.ViewHolder> {

    public interface OnItemClickListener {
        void onItemClick(View v, int position) ;
    }

    /// ... 코드 계속.
}

6.2 리스너 객체를 전달하는 메서드와 전달된 객체를 저장할 변수 추가.

다음, 어댑터의 외부(액티비티 또는 프래그먼트)에서 리스너 객체 참조를 어댑터에 전달하는 메서드를 추가합니다. 그리고 해당 메서드를 통해 전달된 리스너 객체 참조를 저장하는 변수도 추가합니다.


[STEP-2] "XxxAdapter.java" - 리스너 객체 전달 메서드와 변수 추가.
public class XxxAdapter extends RecyclerView.Adapter<XxxAdapter.ViewHolder> {

    /// 코드 계속 ...

    // 리스너 객체 참조를 저장하는 변수
    private OnItemClickListener mListener = null ;

    // OnItemClickListener 리스너 객체 참조를 어댑터에 전달하는 메서드
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.mListener = listener ;
    }

    /// ... 코드 계속.
}

6.3 아이템 클릭 이벤트 핸들러 메서드에서 리스너 객체 메서드 호출.

이제 어댑터 내 뷰홀더에서 아이템 클릭 시, 커스텀 이벤트 메서드를 호출하는 코드를 작성합니다.


[STEP-3] "XxxAdapter.java" - 리스너 객체 전달 메서드와 변수 추가.
public class XxxAdapter extends RecyclerView.Adapter<XxxAdapter.ViewHolder> {

    public class ViewHolder extends RecyclerView.ViewHolder {
 
        ViewHolder(View itemView) {
            super(itemView) ;

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = getAdapterPosition() ;
                    if (pos != RecyclerView.NO_POSITION) {
                        // 리스너 객체의 메서드 호출.
                        if (mListener != null) {
                            mListener.onItemClick(v, pos) ;
                        }
                    }
                }
            });
        }
    }
}

6.4 액티비티(또는 프래그먼트)에서 커스텀 리스너 객체 생성 및 전달.

자, 이제 마지막으로, 액티비티 또는 프래그먼트에서 커스텀 이벤트 리스너 객체를 생성하여 어댑터에 전달합니다.


[STEP-4] "MainActivity.java" - 리스너 객체 전달 메서드와 변수 추가.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        /// 코드 계속 ...

        XxxAdapter adapter = new XxxAdapter(list) ;

        adapter.setOnItemClickListener(new XxxAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View v, int position) {
                // TODO : 아이템 클릭 이벤트를 MainActivity에서 처리.
            }
        }) ;
    }

7. 리사이클러뷰 아이템 클릭 이벤트 처리 예제

그럼 이제 간단한 예제를 통해, 리사이클러뷰 아이템 클릭 이벤트를 어떻게 처리하는지 확인해보겠습니다.


예제 동작은 정말 단순합니다. [안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)]의 예제를 그대로 가져와서, 아이템 클릭 시 아이템의 텍스트를 바꾸는 기능만 추가합니다.

7.1 메인액티비티에 리사이클러뷰 추가.

메인액티비티에 리사이클러뷰를 추가합니다.


[STEP-1] "content_main.xml" - 메인액티비티 레이아웃 리소스 XML에 리사이클러뷰 추가.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler1"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.constraint.ConstraintLayout>

7.2 리사이클러뷰 아이템 뷰 레이아웃 추가.

리사이클러뷰 아이템에 표시될 아이템 뷰 레이아웃을 작성합니다.


[STEP-2] "recyclerview_item.xml" - 아이템 뷰를 위한 리소스 레이아웃 XML 작성.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/text1"
        android:textSize="32sp"/>

</android.support.constraint.ConstraintLayout>

7.3 리사이클러뷰 어댑터 구현.

리사이클러뷰 어댑터 구현에 관한 자세한 설명은 [안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)]을 참고하세요.


[STEP-3] "SimpleTextAdapter.java" - 리사이클러뷰 어댑터 작성.
public class SimpleTextAdapter extends RecyclerView.Adapter<SimpleTextAdapter.ViewHolder> {

    private ArrayList<String> mData = null ;

    // 아이템 뷰를 저장하는 뷰홀더 클래스.
    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView1 ;

        ViewHolder(View itemView) {
            super(itemView) ;

            // 뷰 객체에 대한 참조. (hold strong reference)
            textView1 = itemView.findViewById(R.id.text1) ;
        }
    }

    // 생성자에서 데이터 리스트 객체를 전달받음.
    SimpleTextAdapter(ArrayList<String> list) {
        mData = list ;
    }

    // onCreateViewHolder() - 아이템 뷰를 위한 뷰홀더 객체 생성하여 리턴.
    @Override
    public SimpleTextAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext() ;
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ;

        View view = inflater.inflate(R.layout.recyclerview_item, parent, false) ;
        SimpleTextAdapter.ViewHolder vh = new SimpleTextAdapter.ViewHolder(view) ;

        return vh ;
    }

    // onBindViewHolder() - position에 해당하는 데이터를 뷰홀더의 아이템뷰에 표시.
    @Override
    public void onBindViewHolder(SimpleTextAdapter.ViewHolder holder, int position) {
        String text = mData.get(position) ;
        holder.textView1.setText(text) ;
    }

    // getItemCount() - 전체 데이터 갯수 리턴.
    @Override
    public int getItemCount() {
        return mData.size() ;
    }
}

7.4 리사이클러뷰 아이템 클릭 이벤트 처리.

이제 본문에서 설명한, 리사이클러뷰 아이템 클릭 이벤트를 처리하는 코드를 작성합니다. 앞서 작성한 어댑터의 내용 중, 뷰홀더의 생성자에 관련 코드를 작성합니다.


[STEP-4] "SimpleTextAdapter.java" - 아이템 클릭 이벤트 처리.
public class SimpleTextAdapter extends RecyclerView.Adapter<SimpleTextAdapter.ViewHolder> {

    /// 코드 계속...

    // 아이템 뷰를 저장하는 뷰홀더 클래스.
    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView1 ;

        ViewHolder(View itemView) {
            super(itemView) ;

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = getAdapterPosition() ;
                    if (pos != RecyclerView.NO_POSITION) {
                        mData.set(pos, "item clicked. pos=" + pos) ;

                        notifyItemChanged(pos) ;
                    }
                }
            });

            // 뷰 객체에 대한 참조. (hold strong reference)
            textView1 = itemView.findViewById(R.id.text1) ;
        }
    }

    /// ... 코드 계속.
}

7.5 리사이클러뷰에 어댑터와 레이아웃매니저 지정하기.

마지막으로 어댑터와 레이아웃매니저 객체를 만들고, 각각 리사이클러뷰에 지정합니다.


[STEP-5] "MainActivity.java" - 리사이클러뷰에 어댑터와 레이아웃매니저 지정.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... 코드 계속

        // 리사이클러뷰에 표시할 데이터 리스트 생성.
        ArrayList<String> list = new ArrayList<>();
        for (int i=0; i<100; i++) {
            list.add(String.format("TEXT %d", i)) ;
        }

        // 리사이클러뷰에 LinearLayoutManager 객체 지정.
        RecyclerView recyclerView = findViewById(R.id.recycler1) ;
        recyclerView.setLayoutManager(new LinearLayoutManager(this)) ;

        // 리사이클러뷰에 SimpleTextAdapter 객체 지정.
        SimpleTextAdapter adapter = new SimpleTextAdapter(list) ;
        recyclerView.setAdapter(adapter) ;
    }

7.6 실행 화면.

위의 예제를 작성하고 실행하면, 아래와 같은 화면이 표시됩니다.

리사이클러뷰 아이템 클릭 예제 실행 화면 1


각 아이템을 클릭하면, 아이템에 표시되는 문자열이 변경되는 것을 확인할 수 있습니다.

리사이클러뷰 아이템 클릭 예제 실행 화면 2


8. 참고.

.END.


ANDROID 프로그래밍/RECYCLERVIEW , , , , , , , , , , , ,

  1. 이전 댓글 더보기
  2. Blog Icon
    ㄱㄱ

    아이고 고맙습니다 이 글 아니었으면 레퍼런스랑 스택오버플로우 엄청 뒤지고 다녔을 것 같네요 ㅋㅋㅋ

  3. 하지만 만약, 이 글이 아니었다면, 레퍼런스와 스택오버플로우를 통해 좀 더 좋은 내용들을 접하셨을 수도.... ㅜㅜ

    그래도 조금이나마 도움이 되어 다행입니다.

    방문해주셔서 감사합니다.

  4. 좋은 글 감사합니다.^^
    작성해주신 글을 보고 interface를 이용해서 longclick시 recyclerview 항목을 삭제하고 새로고침되는 기능을 만들었습니다.

    그런데 새로고침을 하고 나서 다시 longclick을 하면 작동이 안 됩니다. longclick 뿐만 아니라 모든 click 이벤트가 작동이 안 되는데 왜 그런지 아시나요?ㅜㅜ

  5. 음.. 작성하신 코드를 확인할 수가 없어서, 문제 원인 분석에 대한 조언을 드리기가 어렵네요.

    "새로고침" 기능을 어떻게 작성하셨는지는 모르겠지만, 아마 리사이클러뷰 아이템 뷰를 표시하는 과정에서 이벤트 핸들러 설정이 잘못된 것 같습니다.

    관련 코드를 질문글에 다시 올려주시면, 도움드리기가 더 용이할 것 같습니다.

    감사합니다.

  6. 덕분에 정말 잘 배워가고있습니다 선생님...정말 감사합니다 ㅜ_ㅜ
    들숨에 재력을 얻으시고 날숨에 건강을 얻으시길 바랍니다.
    사는동안 많이 버시고 앞으로도 좋은글 부탁드립니다!!

  7. 아.. 이번 생은 안될거에요. 아마...

    들숨에 업무 스트레스가 얻어지고, 날숨에 대출 이자가 나가는 이 상황...

    그래도.. 좋은 날이 오겠지요? 하..하..

    격려의 댓글 남겨주셔서 감사합니다!!

  8. Blog Icon
    hukhuk

    아이템을 클릭한 이후 해당 아이템 배경을 바꾸는 예제를 실험중인데
    적용은 잘 되나, 스크롤다운하여 해당 아이템이 화면에서 사라지면 클릭에 대한 백그라운드도 다시 처음으로 돌아갑니다..
    아마 안보이게 된 항목들이 리사이클되면서 다시 화면으로 나올 때 onCreateViewHolder 가 재실행되고 그에 따라 변화된 배경이 아닌 기본 배경으로 만들어지는 것 같습니다..
    이에 대한 해결책이 있을까요?
    어레이에 포지션별 T/F 값을 저장하여 리사이클 될 때 뷰를 변경상태로 설정 하려 했으나, 배경의 높이가 다르기 때문에 아이템이 생성된 뒤 변화된 배경으로 변하면서 커지는 현상이 생기네요..
    해당 아이템이 클릭되면 그 아이템은 리사이클 되지 않도록 하는 방법이 있을까요?

  9. 너무 늦게 답변을 달아서 죄송합니다.
    개인적인 일과 회사 일로 정신이 없어서 한동안 블로그를 전혀 신경쓰지 못했네요.

    혹시 질문하신 내용에 대한 답을 아직 찾지 못하셨다면,
    죄송하지만 다시 한번 질문글 남겨주세요.

    고민하여 도움드릴 수 있도록 하겠습니다.

    죄송하고, 감사합니다.

  10. Blog Icon
    김세진

    사랑합니다. 글을 정말 잘쓰십니다. 혹시 앱개발자시면 메일알려주시면 질문 몇가지만해도될까요? 프로젝트를 진행중인데 초보자라 잘하고 있는지 의문이 듭니당ㅜㅜ

  11. 흠.. 제가 방향성을 제시해줄 수 있을만큼 실력을 갖추었는지는... 좀 의문이고요. ^^;;

    프로젝트 진행을 잘하고 있는지에 대한 확인은, 저보다는,,, 같이 프로젝트를 진행하고 계시는 분들이나 주변 경력자 분들께 확인하는 게 더 좋지 않을까요?

    옆에서 지켜보는 사람이 확실한 조언을 해 줄 수 있을 것 같네요.

    그리고 궁금한 내용은 웬만하면 댓글로 남겨주시는 게 좋을 것 같습니다.
    그게 답을 드리기 더 수월할 것 같습니다.

    감사합니다.

  12. Blog Icon
    비틀

    충분히 어뎁터의 뷰홀더에서 아이템 클릭 이벤트를 처리할 수 있을텐데, 리사이클러뷰 외부(액티비티, 프래그먼트, ...)에서 아이템 클릭 이벤트 처리하는 실제 예가 무엇이 있을까요?? 또 그렇게 할 수 밖에 없는 이유는 무엇인지 알 수 있을까요?

  13. 네. 어댑터의 뷰홀더에서 아이템 클릭 이벤트를 처리할 수 있습니다.

    하지만 아이템 클릭 이벤트가 발생했을 때 리스트뷰에서 처리될 기능 외의 작업을 수행하기 위해서는 리스트뷰 외부에서 아이템 클릭 이벤트에 따른 기능을 수행해줘야 합니다.

    예를 들어,
    리스트뷰 아이템에는 URL이 표시되어 있고, 해당 아이템을 클릭했을 때 HTTP 연결을 통해 URL로부터 데이터를 가져온다고 가정해보죠.

    이런 경우 일반적으로 HTTP 연결 처리는 리스트뷰에 들어 있지 않고 액티비티에 이벤트 핸들러가 만들어져 있을 것입니다.
    그럼 당연히 리사이클러뷰의 클릭 이벤트를 액티비티에서 처리해야 겠죠.

    흠... 조금 더 정확히 표현하자면, "아이템 클릭 이벤트를 처리"한다기 보다는 "아이템 클릭 이벤트에 따른 후속 작업 처리"라고 하는 게 맞는 거 같네요.

    답변이 되었는지 모르겠습니다.
    추가적으로 궁금한 내용이 있으면 질문글 남겨주세요.

    감사합니다.

  14. Blog Icon
    지방대학생

    선생님 정말 감사합니다.

    리싸이클러뷰를 찾다가 들어와서 쭉 보고 있는데 글 하나하나마다 정성들인 이미지, 상세한 설명과 그에 맞는 적절한 소스 코드 예시까지....

    기초적인 자바 지식도 없이 시작해서 많이 헤맸지만 선생님 덕분에 조금씩 알아가고 있는 것 같습니다.

    앞으로도 화이팅해주세요!

    다시 한 번 감사합니다!!

  15. 칭찬과 격려의 댓글 남겨주셔서 감사합니다.

    누추한 블로그에 과분한 칭찬글을 남겨주셨네요.

    감사합니다!

  16. Blog Icon
    뉴비

    선생님 좋은 글 감사합니다.
    질문 한 가지만 여쭤봐도 될까요.
    리사이클러뷰 내부에 버튼을 2개 넣을려 하는데 각각의 버튼에 다른 클릭이벤트를 넣을려면 어떻게 구현해야 하는지 어렵습니다.
    예를들어 아이템 안에 버튼1 과 버튼2 를 넣고 버튼1을 누르면 동영상이 재생되고 버튼2를 누르면 토스트메세지가 나오는 식으로 하고싶습니다.

  17. 본문에 작성된 내용은 리사이클러뷰 아이템 클릭에 관한 내용입니다.
    아시겠지만, 하나의 리사이클러뷰 아이템을 클릭 가능한 뷰로 취급해서 처리하는 방법에 대한 내용이고요.

    아이템 내에 두 개의 버튼을 넣고, 각 버튼이 클릭되었을 때의 이벤트를 처리해주기 위해서는, 일단 리사이클러뷰 아이템 내에서, 각 버튼에 대한 클릭 이벤트 핸들러를 작성하시면 됩니다.

    어떤 동작을 할지에 따라, 버튼 클릭 이벤트 핸들러에서 처리를 완료할지, 아니면 상위 레이아웃의 리스너 인터페이스를 호출해줄지 결정하시면 됩니다.

    댓글이 이해가 잘 안되시면, 일단 상위 레이아웃의 리스너 인터페이스 호출에 대한 내용을 아래 링크를 통해 살펴보시고요. (절차에 대해서 참고 용도로만 보시면 될 것 같습니다.)

    안드로이드 리스트뷰에 버튼 넣기. (Android ListView with Button) - Button 클릭 이벤트 처리.
    https://recipes4dev.tistory.com/45#29-button-%ED%81%B4%EB%A6%AD-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%B2%98%EB%A6%AC

    아래 스택오버플로우의 내용이 도움되실지도 모르겠네요.

    https://stackoverflow.com/questions/30284067/handle-button-click-inside-a-row-in-recyclerview

    감사합니다.

  18. Blog Icon
    궁금이

    안녕하세요.
    질문이 있습니다. ^^
    https://developer.android.com/guide/topics/ui/layout/recyclerview#java에서 ViewHolder가 static으로 되어 있는데 차이가 있을까요? memory leak이랑 연관이 있을까요?

  19. 네. 본문에서는 ViewHolder 클래스를 Adapter의 내부에 만들면서 따로 static을 붙여주지는 않았는데요.
    일반적으로 내부 클래스는 되도록 static으로 만드는 것을 권장합니다.

    말씀하신대로 외부 클래스에 대한 참조로 인한 memory leak과 성능 문제 때문이 맞습니다.

    감사합니다.

  20. 안녕하세요.

    하나의 액티비티에 여러 리싸이클러뷰를 설정할 때(예를들면 네이버의 시리즈온)
    어댑터를 공유해서 쓸 수 있을까요?

    여러 .xml 레이아웃을 하나의 어댑터로 공유할 수 있는지 궁금합니다.

  21. 질문하신 내용대로 만드는 것은 가능합니다.

    보통 하나의 뷰를 표시하기 위해 하나의 어댑터를 사용하지만, 동일한 데이터리스트를 여러 뷰로 표시하고자 하는 경우에는 하나의 어댑터로 구현하는 게 더 좋을 수 있습니다.

    가독성과 유지보수 효율성을 잘 고려하셔서 구현하시는 게 중요하겠죠.

    감사합니다.

  22. 답변 감사합니다.

    그렇다면 유지보수, 효율성을 고려한다면 역시나 하나의 뷰-하나의 어댑터로 구성하는 게 나을까요?
    onCreateViewHolder 안에 두개의 xml을 인플레이션해도 결국 리턴은 하나의 뷰만 리턴이 되더라구요ㅠㅠ
    메모리 문제만 없다면 저는 여러개의 어댑터를 만들어서 관리하고 싶은데 어떻게 생각하세요??ㅎㅎ

  23. 이건,
    정답이 있는 문제가 아니기도 하고,
    구체적인 요구사항이 어떠한지 모르는 관계로,
    뭐가 "낫다"라고 말씀드리기가 어려울 것 같습니다.

    하지만 분명한 것은,
    어떤 방법으로 하더라도 크게 잘못된 방법이 아니고,
    설사 구현 방법이 잘못되더라도 쉽게 바꿀 수 있으니,
    너무 어렵게 생각하지 마세요.

    아니면 두 가지 방법을 모두 채용해보고 구현과 유지보수에서의 장단점을 몸소 체험하시는 것도 나쁘지 않다고 생각합니다.

    일단 만들어보세요. ^^

    감사합니다.

  24. Blog Icon
    코틀린초보

    뽀따님 글 잘 읽었습니다. 감사합니다. 질문하고 싶은것이 있는데,
    6-4에서 list는 무엇을 가리키는 것인지 상세히 설명좀 부탁드리고 싶습니다.

  25. "6.4" 의 예제 코드에서 XxxAdapter(list)의 list를 말씀하시는 것이죠?

    list는 어댑터가 관리하고 리스트뷰에 아이템뷰로 표시할 데이터의 리스트를 가리킵니다.

    설명을 차근차근 읽어보셨다면, "7.3"에서 SimpleTextAdapter를 만들고 생성자를 아래와 같은 코드로 작성한 것을 볼 수 있을텐데요.

    // 생성자에서 데이터 리스트 객체를 전달받음.
    SimpleTextAdapter(ArrayList<String> list) {
    mData = list ;
    }

    어댑터에 전달할 데이터 리스트를 전달한 것이죠.

    음... 단순히 소스코드 응용을 통해 리사이클러뷰 사용법을 이해하시는 것도 나쁘지 않지만, 설명도 잘 읽어보시고 글의 맥락을 잘 짚어보시는 것도 좋을 것 같습니다.

    감사합니다.

  26. Blog Icon
    viewbinding으로 해야죠이제

    지금 코틀린 공식문서에서 viewbinding하는것을 권장하는데,
    이것을 사용하면 난이도가 대폭올라가요
    recyclerview를 viewbimding으로 하는방법도좀 알려주세요

  27. 난이도가 올라간다기 보다는,
    뭔가 익숙하지 않아서... 이겠죠?

    편하라고 만드는 거니깐, 천천히 살펴보시면 쉽게 이해하실 수 있을거라 생각합니다.

    View Binding 관련해서는, 당장 정리할 계획이 없음을 양해바랍니다.

    감사합니다.

  28. Blog Icon

    부탁하는 꼬라지가 역겹네요

  29. 아마 View Binding 관련 내용이 생소하기도 하고, 상대적으로 자료가 많이 없어서 부탁하신 것 같아요~ ^^

    제가 2년 가까이 심리적 여유를 가지지 못해서 블로그 내용들을 업데이트하지 못하고 있어요.

    그래도 최근에는 조금씩 새로운 내용들 정리하고 있으니, 활발한 활동할 수 있도록 노력하겠습니다.

    관심 가져주셔서 감사드립니다.

  30. Blog Icon
    안드슬레이브

    public ViewHolder(@NonNull View itemView) {
    super(itemView);
    part = itemView.findViewById(R.id.part);
    part.setOnClickListener(new View.OnClickListener() {
    // int pos = getAdpaterPosition() // 에러
    @Override
    public void onClick(View v) {
    if(isSelected == true) { // 선택되었다면
    listener.OnItemClick(itemView, getAdapterPosition(), true);
    isSelected = false;
    }
    else {
    listener.OnItemClick(itemView, getAdapterPosition(), false);
    isSelected = true;
    }
    }
    });
    }
    선생님 이러한 코드에서. getAdatperPostion을 OnClick 외부, 즉 익명객체의 필드로 선언했을때
    에러가나고 onClick 내부에 getAdatperPostion() 을 호출했을때는 에러가 안나던데 이유가 뭔가요?

  31. 질문하신 내용에 대해서는 문제되는 상황을 잘 모르겠네요.
    제가 지금 별도로 시험할 수 있는 상황이 아니라서, 도움드리기가 쉽지 않을 것 같습니다.
    양해바랍니다.

    감사합니다.

  32. 와 정말 정리를 잘 해주셔
    처음으로 댓글 남겨봅니다.

    정말 감사합니다.

  33. 방문해 주셔서 감사합니다.

  34. Blog Icon
    거북

    안녕하세요 강의 정말 잘봤습니다 감사합니다.
    혹시 어댑터에 mListener가 계속 null인데 어떤 문제가 있을까요
    디버깅 해보니 클릭하면 어댑터에만든 클릭리스너까지 들어오는데 mListener가 널이라 실행이 안되는거 같습니다. ㅠㅠ

  35. 본문의 내용 중에,
    "6.4 액티비티(또는 프래그먼트)에서 커스텀 리스너 객체 생성 및 전달."의 내용을 다시 한번 확인해보시는 게 좋을 것 같습니다.

    [STEP-4]에서 setOnItemClickListener()를 호출하는 부분 확인해보시면 해답이 있을 것 같네요.

    그리고 본문의 예제 코드와 다시 한번 비교해보시고, 차이점을 확인해보시기 바랍니다.

    감사합니다.

  36. Blog Icon
    턲끼

    뽀따님 좋은글 정말 감사합니다.
    궁금한점이 있습니다.
    1. 아이템뷰 레이아웃에서 TextView말고 Button으로 진행하고 싶은데 어떤 식으로 리스너를 진행해야 하나요?? TextView를 Button으로 변경하면 클릭이 안되요ㅜ

    2. 아이템뷰 레이아웃에서 TextView로 진행할 때 터치 시 색상이 변하는 효과를 주고 싶은데
    drawable에 xml파일을 만들어서 selector로 정의한 후 TexvView에 background로 @drawable/~ 지정해 주면 Adapter부분 중 View view = inflater.inflate(R.layout.item_post, parent, false); 이 라인에서
    오류가 발생합니다.... 둘중 하나만 해결해 주실 수 있으실까요? 감사합니다.


  37. 제가 답글을 너무 늦게 달아드렸네요.
    문제는 해결하셨는지요.
    아직 해결 못하셨으면 글 남겨주세요.

    감사합니다.

  38. Blog Icon
    안녕하세요

    좋은 글 정말 감사합니다.
    실례가 안 된다면 도움을 받고 싶은 부분이 있는데요.
    자바 문법이랑 자료구조 조금만 아는 상태에서 프로젝트 헤딩하는 중인데요.
    정리된 내용 중에 6.리사이클려뷰 외부에서 이벤트 처리하기 항목에서 어댑터 바깥에서 데이터를 활용하고자 하는데,
    //Adapter내부
    public static String item
    Stirng item = mData.get(pos) ;

    //Adapter바깥의 OnItemClickListener
    String item
    item = adapter.item
    이런식으로 사용하고 있는데 Adapter 내부의 item을 static 선언을 하지 않고 가져오는 방법이 있을까요? Adapter 내부에 함수를 사용해서 get하려고 했는데 쉽지 않아서요...
    포스트는 정말 잘 보고 있어요. 장인정신이 느껴지는 포스트들이에요.

  39. 질문에 대한 답이 늦어서 죄송합니다.
    건강 문제로 인해 꽤 오랜 시간 동안 신경을 못썼네요.

    질문에 대한 답은 의외로 간단한데요.

    본문의 "6.1 커스텀 리스너(Custom Listener) 인터페이스 정의."의 제목에서 알 수 있듯이,
    어댑터 외부에서 구현(implement)될 인터페이스는 "커스텀"입니다.

    즉, 고정된(변경할 수 없는) 인터페이스가 아니라, 개발자의 의도에 따라 언제든 수정이 가능한 인터페이스라는 것이지요.

    그러므로 굳이 데이터 리스트를 Adapter의 static 변수로 선언하여 참조하지 않고, 인터페이스 메서드에 전달하면 됩니다.

    public interface OnItemClickListener {
    void onItemClick(View v, int position, String item) ;
    }

    이런 형태로 onItemClick() 메서드에 item을 전달하게 만들면 됩니다. 물론 변경된 메서드에 따라 그 구현도 바꿔주면 되겠죠.

    답변이 도움되셨는지 모르겠네요.

    해보시고 또 궁금한 내용 있으면 질문글 남겨주세요.

    감사합니다.

  40. Blog Icon
    컴퓨터전공

    정말 감사합니다 이틀 내내 고민했는데 글 보고 한방에 해결했습니다.

  41. 도움이 되신 것 같아서 뿌듯하네요.
    저도 감사합니다!

  42. Blog Icon
    뚜뚜

    친절한 글 너무 감사합니다.
    조금만 더 하면 결과가 나올 것 같은데 에러가 나는 부분이 있어 질문드립니다
    아래 부분에서 itemClickListner를 찾을 수 없다고 나옵니다
    클래스 밖으로 나갔을때는 문제없이 불러오는데 혹시 어떤 문제인지 알 수 있을까요?

    class stickerChapterListHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    @SuppressLint("ResourceAsColor")
    fun bindItems(chapter: ChapterModel) {
    itemView.setOnClickListener {
    var pos = adapterPosition
    if(pos != RecyclerView.NO_POSITION){
    if(itemClickListner != null){
    itemClickListner.onItemClick(itemView,pos)
    }
    }
    }

  43. 올려주신 내용만으로는 정확한 이유를 알 수 없는데요.

    어쨌든 코드에는 itemClickListner가 보이지 않네요. 코드를 생략하고 올려주신 건지 모르겠습니다.

    itemClickListner를 한번 더 확인해보시면 될 것 같습니다.

    감사합니다.