[Android] Sunflower 클론코딩 (22.06.09)

유재민·2022년 6월 9일
0

Sunflower

목록 보기
8/12
post-thumbnail

PlantListFragment 안에 있는 RecyclerView를 위한 어댑터를 생성해주자.
그리고, 리사이클러뷰에 ListAdapter와 DiffUtil을 활용해보자.


DiffUtil?

간단하게 요약하자면 현재 데이터 리스트와 새로운 데이터 리스트를 서로 비교하여 어떤 아이템이 바뀌었는지 확인하는 클래스다. Eugene W. Myers 씨가 difference 알고리즘으로 구현을 했다. 데이터 전체를 갈아엎는 것이 아니라 변경된 부분만 바꾸는 것이라서 기존의 notify 보다 복잡도가 낮다.

변경된 item의 position을 알고있으면 notifyItemChaged(position) 으로 값을 변경하면 되지만, position을 모를 때 DiffUtil을 이용하여 불필요한 교체 비용을 줄일 수 있다.

먼저 두 리스트의 차이를 계산하는 DiffUtil.ItemCallback()을 구현한다.

private class PlantDiffCallback : DiffUtil.ItemCallback<Plant>() {

    override fun areItemsTheSame(oldItem: Plant, newItem: Plant): Boolean {
        return oldItem.plantId == newItem.plantId
    }

    override fun areContentsTheSame(oldItem: Plant, newItem: Plant): Boolean {
        return oldItem == newItem
    }
}
  • areItemsTheSame() : 두 아이템이 동일한 아이템인지 체크 (보통 객체의 고유한 id로 비교한다)
  • areContentsTheSame : 두 아이템이 동일한 내용물을 가지고 있는지 체크 (areItemsTheSame 이 true를 반환할 때만 체크)

ListAdapter?

DiffUtil을 단순하게 사용할 수 있도록 해주며, 자체적으로 멀티 쓰레드 처리가 되어 있는 AsyncListDiffer 가 있다. 그보다 더 간편하게 사용할 수 있게 해주는 것이 ListAdapter 이다.(블로그에 따로 정리할 예정) RecyclerView.Adapter 베이스로 RecyclerView의 List 데이터를 표현하며 백그라운드 스레드에서 diff 를 처리하는 특징이 있다.

class PlantAdapter : ListAdapter<Plant, RecyclerView.ViewHolder>(PlantDiffCallback()) {
	...
}
  • getCurrentList() : 현재 리스트 반환
  • onCurrentListChanged : 리스트가 업데이트 되었을 때 콜백 지정
  • submitList(Mutable<T> list) : 리스트 데이터 교체
adapter.submitList(plants)

뷰홀더에 아이템 클릭 시 PlantDetailFragment로 이동하는 로직을 추가한다.

init {
    binding.setClickListener {
        binding.plant?.let { plant ->
			navigateToPlant(plant, it)
        }
    }
}
private fun navigateToPlant(
    plant: Plant,
    view: View
) {
    val direction =
        HomeViewPagerFragmentDirections.actionViewPagerFragmentToPlantDetailFragment(
            plant.plantId
        )
    view.findNavController().navigate(direction)
}

Directions 클래스는 action을 추가하면 자동으로 생성되는 클래스인데 빌드가 안되는 경우가 있다. 그 경우에는 classpath와 plugin을 추가해서 해결한다.

// build.gradle (:project)


dependencies{
	...
	classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
}
// build.gradle (:app)

plugins {
	...
	id 'androidx.navigation.safeargs.kotlin'
}

RecyclerView (Adapter)

1. onCreateViewHolder()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return PlantViewHolder(
        ListItemPlantBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
    )
}

ListItemPlantBinding.inflate() 로 binding 생성.
ListItemPlantBinding 은 컴파일러가 생성한 클래스다.
해당 binding 객체를 ViewHolder가 가지도록 한다.

2. onBindingViewHolder()

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val plant = getItem(position)
    (holder as PlantViewHolder).bind(plant)
}

ViewHolder의 bind 함수를 호출한다.

3. bind()

fun bind(item: Plant) {
    binding.apply {
        plant = item
        executePendingBindings()
    }
}

뷰 홀더에 bind 함수를 생성하여 binding의 item에 새로운 data를 set한다.
executePendingBindings() : 각 뷰홀더에 데이터를 binding 하면서 안드로이드 프레임워크에 Data가 변경되었음을 알린다.


RecyclerView (Layout)

<!-- fragment_plant_list.xml -->

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/plant_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:paddingEnd="@dimen/card_side_margin"
    android:paddingStart="@dimen/card_side_margin"
    android:paddingTop="@dimen/header_margin"
    app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
    app:spanCount="@integer/grid_columns"
    tools:context="com.jaemin.sunflower_clone.GardenActivity"
    tools:listitem="@layout/list_item_plant"/>
  • android:clipToPadding : 스크롤 영역을 유지하며 원하는 리스트뷰 위치에 padding 값을 넣을 수 있게 함 (default: true)
  • app:layoutManager="~StaggeredGridLayoutManager" : 지그재그 형식인데 크기가 제각각이다. (텍스트가 길어지면 그만큼 아이템의 height도 늘어남)
  • app:spanCount : 한 줄에 들어갈 아이템 개수
profile
유잼코딩

0개의 댓글