본문 바로가기
Android Application/기초 사용법

안드로이드 리사이클러뷰(recyclerview) 사용(with DiffUtil)

by sdchjjj 2023. 3. 30.
728x90

안녕하세요.

이번 포스팅에서는 리사이클러뷰를 diffUtil과 함께 사용해 보겠습니다.

 

언어: 코틀린

sdk vsersion

  - compile: 33

  - min: 21

  - target: 33

 

먼저 프래그먼트를 하나 생성한 후, xml을 아래와 같이 수정합니다.

저는 RecyclerFragment라는 이름으로 생성하였습니다.

fragment_recycler.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".RecyclerFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/sample_recycler_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:padding="10dp"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

프래그먼트에서는 view binding을 사용하도록 수정합니다.

뷰바인딩에 관한 내용은 아래 링크를 참고해 주세요.

https://it-of-fortune.tistory.com/21

 

안드로이드 ViewBinding(뷰바인딩) 구현

안녕하세요. 이번 포스팅에서는 안드로이드 프래그먼트에서의 뷰바인딩 구현을 진행해 보겠습니다. 언어: 코틀린 sdk vsersion - compile: 33 - min: 21 - target: 33 바인딩 진행 전 사전 준비 작업입니다.

it-of-fortune.tistory.com

 

RecyclerFragment.kt

class RecyclerFragment : Fragment() {
    
    private var _binding: FragmentRecyclerBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentRecyclerBinding.inflate(layoutInflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

 

RecyclerView에서 보여줄 개별 item layout이 필요합니다.

item view xml을 생성합니다(figure1, figure2 참조).

figure1. create item view_1

 

figure2. create item view_2

 

그리고 아래와 같이 수정해 줍니다.

view_sample_item.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:layout_width="140dp"
    android:layout_height="40dp"
    android:layout_marginVertical="5dp"
    android:background="@color/black">

    <ImageView
        android:id="@+id/sample_image"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginStart="10dp"
        android:src="@drawable/ic_launcher_background"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/sample_data"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginStart="10dp"
        android:gravity="center"
        android:textColor="@color/white"
        app:layout_constraintStart_toEndOf="@id/sample_image"
        tools:text="1" />

</androidx.constraintlayout.widget.ConstraintLayout>

위의 작업이 끝난 후 프래그먼트의 xml에서 item의 preview를 확인하는 코드를 추가할 수 있습니다.

fragment_recycler.xml

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/sample_recycler_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:padding="10dp"
    app:layout_constraintStart_toStartOf="parent"
    tools:listitem="@layout/view_sample_item"/>

 

tools:listitem="@layout/view_sample_item"

위의 코드를 추가하면 item preview가 확인됩니다(figure3 참조).

figure3. recyclerView item preview

이제 샘플 아이템을 생성하겠습니다.

Data class를 생성합니다(figure4, figure5 참조).

figure4. create data class_1

 

figure5. create data class_2

아래와 같이 작성합니다.

SampleData.kt

data class SampleData(
    val data: Int
)

 

위에서 만든 data class를 이용해, 프래그먼트에서 실질적인 데이터를 생성해 주겠습니다.

RecyclerFragment.kt

class RecyclerFragment : Fragment() {
    
    private var _binding: FragmentRecyclerBinding? = null
    private val binding get() = _binding!!

    private val sampleItems = ArrayList<SampleData>()
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentRecyclerBinding.inflate(layoutInflater, container, false)
        setSampleItems()
        return binding.root
    }

    private fun setSampleItems() {
        for (i in 1..30) {
            sampleItems.add(
                SampleData(
                    i
                )
            )
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

1~30까지의 데이터를 생성해 주도록 작성했습니다.

 

다음으로, recyclerView에 item을 장착시켜 줄 adpater를 생성해야 합니다.

새로운 kotlin class를 생성합니다. 저는 SampleAdapter.kt로 생성했습니다.

그리고 아래 코드를 추가합니다.

SampleAdapter.kt

class SampleAdapter : ListAdapter<SampleData, SampleAdapter.SampleDataViewHolder?>() {

    class SampleDataViewHolder(
        private val binding: ViewSampleItemBinding
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind() {
            
        }
    }
}

위의 코드를 작성할 때 ListAdapter의 import가 아래와 같은지 확인해 주세요.

import androidx.recyclerview.widget.ListAdapter

 

작성을 마친 뒤, class 이름을 클릭하고 Alt + Enter를 입력하여 Implement members를 선택해 줍니다(figure6, figure7 참조).

figure6. implement members
figure7. select all members

 

위의 과정을 마치면 아래와 같은 코드를 갖게 됩니다.

SampleAdapter.kt

class SampleAdapter : ListAdapter<SampleData, SampleAdapter.SampleDataViewHolder?>() {

    class SampleDataViewHolder(
        private val binding: ViewSampleItemBinding
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind() {

        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SampleDataViewHolder {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: SampleDataViewHolder, position: Int) {
        TODO("Not yet implemented")
    }
}

 

 

여기서 몇 가지 추가하여 다음과 같이 작성합니다.

SampleAdapter.kt

class SampleAdapter : ListAdapter<SampleData, SampleAdapter.SampleDataViewHolder?>(
    SampleDataCallback
) {

    class SampleDataViewHolder(
        private val binding: ViewSampleItemBinding
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: SampleData) {
            binding.sampleData.text = item.data.toString() 
        }
    }

    companion object {
        private val SampleDataCallback = object : DiffUtil.ItemCallback<SampleData>() {
            override fun areItemsTheSame(
                oldItem: SampleData,
                newItem: SampleData
            ): Boolean {
                return oldItem.hashCode() == newItem.hashCode()
            }

            override fun areContentsTheSame(
                oldItem: SampleData,
                newItem: SampleData
            ): Boolean {
                return oldItem == newItem
            }
        }
    }
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SampleDataViewHolder {
        val binding =
            ViewSampleItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        return SampleDataViewHolder(binding)
    }

    override fun onBindViewHolder(holder: SampleDataViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

 

간략하게 설명하자면, data를 textView에 보여주는 코드와 data의 갱신이 일어났을 경우 recyclerView에 새로운 item들을 넣어주는 diffUtil의 코드를 추가했습니다.

 

마지막으로, 프래그먼트에서 이 어답터를 사용하도록 합니다.

RecyclerFragment.kt

private val sampleAdapter by lazy {
    SampleAdapter()
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    ...
    binding.sampleRecyclerView.adapter = sampleAdapter
    binding.sampleRecyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
    sampleAdapter.submitList(sampleItems)
    ...
}

Linear 그리고 vertical의 형태로 item이 배치되도록 추가했습니다.

sampleAdapter.submitList(sampleItems)

이 코드로 인해 recycler view에 들어갈 아이템 리스트를 adapter에 전해주게 됩니다.

 

에뮬레이터에서 코드를 실행해 보겠습니다.

figure8. run_1

문제없이 동작합니다.

 

하지만 지금 이 상태에서는 diffUtil은 큰 의미가 없습니다. data의 변화가 없기 때문입니다.

 

그렇다면 diffUtil을 위해 약간의 수정을 해보겠습니다.

 

fragment_recycler.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".RecyclerFragment">
    
    <Button
        android:id="@+id/item_add_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="+"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/sample_recycler_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:padding="10dp"
        app:layout_constraintStart_toStartOf="parent"
        tools:listitem="@layout/view_sample_item"/>

</androidx.constraintlayout.widget.ConstraintLayout>

버튼을 하나 추가합니다.

 

프래그먼트를 다음과 같이 수정합니다.

RecyclerFragment.kt

class RecyclerFragment : Fragment() {

    private var _binding: FragmentRecyclerBinding? = null
    private val binding get() = _binding!!

    private var currentNumber: Int = 0
    private val sampleItems = ArrayList<SampleData>()

    private val sampleAdapter by lazy {
        SampleAdapter()
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentRecyclerBinding.inflate(layoutInflater, container, false)
        binding.itemAddButton.setOnClickListener {
            sampleItems.add(
                SampleData(
                    currentNumber
                )
            )
            sampleAdapter.submitList(sampleItems.toMutableList())
            currentNumber += 1
        }
        binding.sampleRecyclerView.adapter = sampleAdapter
        binding.sampleRecyclerView.layoutManager =
            LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

+버튼을 클릭하면 currentNumber를 사용해 데이터가 생성되고 데이터 리스트에 넣어준 뒤 submitList를 진행합니다. 그 후 숫자 1이 더해집니다. Button을 계속 누르면 위의 과정이 반복됩니다.

이제는 submitList를 하면 diffUtil이 데이터의 변화를 판단하여 리스트를 갱신하게 됩니다.

 

의도한 동작은 버튼을 클릭할 때마다 새로운 데이터를 리스트에 추가합니다.

 

에뮬레이터에서 실행해 보겠습니다.

figure9. run_2

문제없이 동작합니다.

 

이상 포스팅을 마치겠습니다.

 

감사합니다.

728x90

댓글