[Android] Interface로 액티비티, 리사이클러뷰 뷰홀더의 클릭 리스너를 만들어보자.

KIM 쥬얼리 (vs0610)·2021년 7월 17일
0

글을 시작하며

오랜만에 블로그 글을 작성해본다. 과제를 하면서 지난 개인 프로젝트에서도 구현했었던 Acitivity, Fragment(View)와 Recyclerview 아이템 간의 클릭 리스너를 인터페이스에 관해서 글을 써보겠다고 생각했다. 주된 이유는 현재 독서 스터디에서 읽고 있는 '객체지향의 사실과 오해'를 읽으며 인터페이스에 대해 설명한 문장을 보고 이 구현이 생각났기 때문이다.

일반적으로 인터페이스란 어떤 두 사물이 마주치는 경계 지점에서 서로 상호작용할 수 있게 이어주는 방법이나 장치를 의미한다.
...
객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 '메시지 전송'이다. 따라서 객체의 인터페이스는 객체가 수신할 수 있는 메시지의 목록으로 구성되며 객체가 어떤 메시지를 수신할 수 있는지가 객체가 제공하는 인터페이스의 모양을 빚는다.

_객체지향의 사실과 오해, 조영호

View와 어댑터와의 상호작용을 위해 click listener라는 인터페이스를 만들었던 것이 생각났고 이를 예시로 블로그에 정리를 해두면 좋겠다는 생각을 했다. 그래서 글을 작성한다.

글은...

글은 레이아웃이나 의존성에 대한 내용은 생략한다. 이유는 글의 목적인 인터페이스에 대한 내용을 전달하는 것에 부합하지 않고 가독성이 떨어질 것이라고 생각했기 때문이다. 또한 이벤트 이후의 객체의 내부동작은 적지 않는다. 이유는 동일하다.

상황은 리사이클러뷰에 뿌려진 사진 중 하나를 클릭했을 때 이 클릭 이벤트를 fragment에서 처리해주는 상황으로 예를 들어보려고 한다.

Glide, ListAdapter, ViewBinding을 사용했다.

언어는 Kotlin을 사용한다.

시작!

사진에 대한 정보가 있는 데이터 모델이 필요하다.

데이터 클래스로 만들어주자. 안에는 사진에 대한 url, 사진의 가로 픽셀, 세로 픽셀 이렇게 세가지 정보만 있다고 가정하자.

// PhotoDataModel.kt
data class PhotoDataModel(
	val photoUrl: String,
    	val width: Int,
        val height: Int)

인터페이스를 정의해주자.

클릭한 아이템을 fragment에서 이용하기 때문에 인터페이스 메소드의 인자로 데이터 모델을 넣어준다.

// PhotoClickLister.kt
interface PhotoClickLister {
	fun onPhotoClicked(photoDataModel: PhotoDataModel)
}

인터페이스를 받은 어댑터를 만들어주자.

인터페이스는 생성자로 받고 ListAdapter를 사용한다. custom diffUtil callback(PhotoDiffUtilCallback)을 사용한다.(diff Util 구현 부분은 생략, 추후 블로그에 작성해보겠습니다.) 뷰홀더는 inner class로 생성한다.

// PhotoRecyclerViewListAdapter.kt
class PhotoRecyclerViewListAdapter(private var photoClickLister: PhotoClickLister) :
    ListAdapter<PhotoDataModel, PhotoRecyclerViewListAdapter.PhotoViewHolder>(PhotoDiffUtilCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PhotoViewHolder(
        ItemPhotoListBinding.inflate(LayoutInflater.from(parent.context), parent, false),
        this.photoClickLister
    )


    override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) {
        holder.bindWithView(getItem(position))
    }

    inner class PhotoViewHolder(
        private val viewBinding: ItemPhotoListBinding,
        private var photoClickLister: PhotoClickLister
    ) :
        RecyclerView.ViewHolder(viewBinding.root) {

        private val photoImageView = viewBinding.photoImageView

        fun bindWithView(photoDataModel: PhotoDataModel) {
            Glide
                .with(photoImageView.context)
                .load(photoDataModel.photoUrl)
                .into(photoImageView)

            viewBinding.photoImageView.setOnClickListener {
                this.photoClickLister.onPhotoClicked(photoDataModel)
            }
        }
    }
}

프래그먼트에서 객체의 행동을 구현하자.

// MainFragment.kt
class MainFragment : Fragment(), PhotoClickListener {
	private lateinit var photoRecyclerViewListAdapter: PhotoRecyclerViewListAdapter
      // CODES
      // 어댑터 생성하기
     private fun initRecyclerView() {
     	photoRecyclerViewListAdapter = PhotoRecyclerViewListAdapter(this)
        binding.photoRecyclerview.apply {
            layoutManager = StaggeredGridLayoutManager(
                2,
                StaggeredGridLayoutManager.VERTICAL
            ) // 이 부분은 자유
            adapter = photoRecyclerViewListAdapter
        }
     }
     

}

이렇게 interface를 implement하게되면 빨간줄이 싹 그어지고 opt+enter를 누르면 implement할 멤버가 있다고 나온다. 그렇게 되면 PhotoClickListener에서 정의했던 method를 override하게 된다.

// MainFragment.kt
class MainFragment : Fragment(), PhotoClickListener {
	
    // CODES
	
    override fun onPhotoClicked(photoDataModel: PhotoDataModel) {
        // TODO
    }
}

TODO에서 사진이 클릭되었을 때 했으면 하는 행동을 상세 구현해주면 된다.

끝!

글을 마치며 인터페이스에 대해서 잘 모른 상태로 개발을 했었는데 이번 기회에 제대로 공부할 수 있었다. 아직 완벽하진 않지만 개발과 공부를 병행하며 더 성장하도록 하자.

다소 글 구체적인 구현부에서 구체적이지 않은 부분이 있었는데 글의 목적을 생각해 이렇게 작성했고 각종 질문 등은 환영한다!

0개의 댓글