안드로이드에서 리사이클러뷰를 구현할때 뷰홀더 클래스를 지정해서 해야 합니다. 뷰홀더 클래스에는 onClick 함수가 재정의되어야 하는데 리사이클러뷰가 표시하는 목록에서 아이템이 클릭되면 일어나야 하는 기능을 구현하는데 쓰입니다. 그러나 구조적인 문제로 몇번째 아이템인지 알려주는 position 값을 넘겨받기가 애매해집니다.
이 경우 코틀린에서 제공하는 higher order function을 써야 합니다. higher order function은 클래스 시그니처에 지정하는 함수 문법인데 함수를 파라메터로 넘겨서 함수의 리턴값 등으로도 쓰이게 하는 함수 지정 방법입니다. 이를 이용하면 뷰홀더 함수에 특정 함수를 넘겨서 onClick 함수에서 받아서 쓸 수 있게 됩니다. 이 문법을 쓰면 position 값이 onClick 함수 내에 전달이 되어 선택된 아이템 클릭시 그 내용을 보여주는 기능을 구현할 수 있게 됩니다.
구현 순서는
(1) 액티비티나 프래그먼트에 두가지 지정을 해줍니다.
— adapter 설정시 position 값을 전달하는 람다식 사용
— onListItemClick(position: Int) 함수를 구현해서 navigation의 action에 position 변수를 전달 (Safe Args를 쓴 예)
예:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class ListFragment : Fragment() { lateinit var bind: FragmentRecyclerviewBinding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) bind = DataBindingUtil.inflate(inflater, R.layout.fragment_recyclerview, container, false) <strong> var adapter = IdolsListAdapter(image, title) { position -> onListItemClick(position) // position 값을 전달하는 람다식 } bind.idolsList.adapter = adapter</strong> return bind.root } // position을 Safe Args로 내용 보여주는 프래그먼트에 전달 <strong> private fun onListItemClick(position: Int) { val action = ListFragmentDirections.actionRecyclerviewFragmentToDetailFragment(position) findNavController().navigate(action) }</strong> } |
(2) adapter에서 onCreateViewHolder의 리턴값으로 뷰와 함수 전달
예:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
inner class IdolsListAdapter( var image: TypedArray?, var title: Array<String>?, <strong>val onItemClicked: (position: Int) -> Unit</strong>) // higher order function 지정 : RecyclerView.Adapter<ListHolder>() { <strong>override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ListHolder</strong> { val view = layoutInflater.inflate(R.layout.item_view, parent, false) <strong>return ListHolder(view, onItemClicked)</strong> // 아답터 시그니처로 전달받은 onItemClicked를 뷰홀더 객체의 파라메터로 전달 } override fun getItemCount() = title!!.size // 목록에 표시할 이미지와 제목을 표시 override fun onBindViewHolder(holder: ListHolder, <strong>position: Int</strong>) { var _image = image!!.getResourceId(<strong>position</strong>, -1) var _title = title!![<strong>position</strong>] holder.apply { titleTextView.text = _title imageView.setImageResource(_image) } } } |
(3) 뷰홀더 클래스에서 View.onClickListener와 onClick 함수를 구현한다.
예:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
inner class ListHolder( view: View, <strong>private val onItemClicked: (position: Int</strong>) -> Unit // higher order function ) : RecyclerView.ViewHolder(view), View.OnClickListener { <strong>init { view.setOnClickListener(this) // 클릭 리스너 달기 } override fun onClick(v: View) { val position = adapterPosition // position 값 지정 onItemClicked(position) // higher order function 으로 전달받은 함수 실행 }</strong> } |
뷰홀더와 아답터는 프래그먼트 클래스의 inner 클래스로 정의하면 코딩 제한이 없어진다.
Safe Args로 position값을 아이템 내용 보여주는 프래그먼트에서 받아서 아래처럼 처리해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class DetailFragment : Fragment() { lateinit var bind: FragmentDetailBinding var title: Array<String>? = null var image: TypedArray? = null var description: Array<String>? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { bind = DataBindingUtil.inflate(inflater, R.layout.fragment_detail, container, false) // position 값을 받아 처리 준비 <strong>val args by navArgs<DetailFragmentArgs>() var _position = args.positionDatum</strong> title = resources.getStringArray(R.array.idols_title_array) image = resources.obtainTypedArray(R.array.idols_list_image_array) description = resources.getStringArray(R.array.idols_description_array) // position 값으로 보여줄 내용 표시하는 배열 첨자 지정 bind.writingDetailTitle.text = title!![<strong>_position</strong>] bind.writingDetailImage.setImageResource(image!!.getResourceId(<strong>_position</strong>, -1)) bind.writingDetailContent.text = description!![<strong>_position</strong>] return bind.root } } |
이해가 잘 되게 예시한지는 미상인데 참고는 되실 것이다.
Safe Args는 별도의 기능이지만 위에 navArgs로 불러온 position 값 처리를 위한 또다른 Safe Args 처리로 아래와 같이 navigation XML에 정의해둔다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0" encoding="utf-8"?> <navigation 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:id="@+id/nav_main" app:startDestination="@id/main_fragment"> <fragment android:id="@+id/recyclerview_fragment" android:name="info.shutterpress.idols.ListFragment" tools:layout="@layout/fragment_recyclerview"> <action android:id="@+id/action_recyclerview_fragment_to_detail_fragment" app:destination="@id/detail_fragment"> <strong><argument android:name="positionDatum" app:argType="integer" android:defaultValue="0" /></strong> </action> </fragment> <fragment android:id="@+id/detail_fragment" android:name="info.shutterpress.idols.DetailFragment" tools:layout="@layout/fragment_detail"> <strong><argument android:name="positionDatum" app:argType="integer" android:defaultValue="0" /></strong> </fragment> </navigation> |
클릭 이벤트 코딩과 직접 상관이 없어서 본문에서는 생략한 설정이 있는데요. Safe Args로 프래그먼트 전환시 position값을 넘겨서 처리해야 되니, Safe Args 설정을 더 찾아보시고 추가해야 합니다. 본문에서 안 언급한게 그래이들 설정파일에 Safe Args 의존성을 추가하는 것입니다. 책이나 구글에서 찾아보시면 참고가 되실 것 같습니다.