Web | App/Android

[Kotlin] RecyclerView, 리스트 리소스 관리

며용 2021. 5. 16. 20:53

 

이런 유튜브처럼, 스크롤을 내려보면 리스트가 계속 나오는 어플을 먼저 구현하라고 하셨다

리소스 관리하면서 하라고 하셔서 리소스 재사용을 할 수 있는 RecyclerView로 구현하게 되었다

 

 

 

RecycelrView는 이렇게 리스트가 새로 생길 때마다 추가로 view를 만드는 것이 아니라

이전에 만들어뒀던, 지금은 사용자에게 보이지 않는 뷰를 가져와서 재사용한다

 

 

 

1. import recyclerview

    // RecyclerView
    implementation "androidx.recyclerview:recyclerview:$version_recyclerview"
    // For control over item selection of both touch and mouse driven selection
    implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc03"

 

 

2. item list layout 구성하기

 

우선 리스트에 반복적으로 표시해줄 하나의 아이템을 만들었다

 

content_list_item.xml 에 ImageView와 TextView로 구성

더보기
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>
        <variable
            name="item"
            type="com.example.firstapp.data.ContentModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="1dp"
        android:layout_marginTop="1dp"
        android:layout_marginEnd="1dp"
        android:layout_marginBottom="1dp"
        android:background="@drawable/item_border"
        android:gravity="center"
        android:orientation="vertical"
        android:paddingLeft="2dp"
        android:paddingTop="2dp"
        android:paddingRight="2dp"
        android:paddingBottom="2dp">

        <ImageView
            android:id="@+id/thumbnail"
            android:layout_width="154dp"
            android:layout_height="85dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="4dp"
            android:layout_marginBottom="4dp"
            android:scaleType="fitCenter"
            app:imageUrl="@{item.thumbnail}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@mipmap/ic_launcher_round"
            android:layout_marginLeft="8dp" />

        <TextView
            android:id="@+id/contentTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:textSize="20sp"
            android:textStyle="bold"
            app:layout_constraintStart_toEndOf="@+id/thumbnail"
            app:layout_constraintTop_toTopOf="@+id/thumbnail"
            tools:text="@{item.title}"
            android:layout_marginLeft="16dp" />

        <TextView
            android:id="@+id/contentDetail"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxLines="1"
            android:textSize="16sp"
            app:layout_constraintBottom_toBottomOf="@+id/thumbnail"
            app:layout_constraintStart_toStartOf="@+id/contentTitle"
            tools:text="@{item.details}" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

 

그리고 이 아이템들이 RecyclerView로 구성될 수 있도록 만들었다

 

fragment_content_list.xml 에 RecyclerView 추가

 

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/contentListRecyclerView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:padding="6dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:listData="@{contentView.contents}"
            tools:layout_editor_absoluteY="6dp"
            tools:listitem="@layout/content_list_item" />

 

LayoutManager는 말 그대로 Recycler의 변경하고 싶은 레이아웃 설정이 있으면 변경하는 곳이다

(리스트를 1열 여러 행으로 할 지, 그리드 형태로 할지, 스크롤을 좌우로 할지 위아래로 할지 등등)

 

 

 

3. 재사용할 뷰 객체를 기억하고 있을 ViewHolder

 

ContentListAdapter.kt

    class ContentViewHolder(private var binding: ContentListItemBinding):
        RecyclerView.ViewHolder(binding.root) {
        fun bind(content: ContentModel) {
            binding.item = content

            binding.executePendingBindings()
        }
    }

 

원하는 데이터에 bind를 걸어주면, 뷰 객체는 유지하면서 안에 데이터값만 바꾸며 사용하게 된다

 

 

 

4. Adapter

Adapter는 뷰 객체에 데이터를 바인딩하기 전 사전 작업이 이루어지는 객체이다

 

ContentListAdapter.kt

class ContentListAdapter(private val onClickListener: OnClickListener ) :
    ListAdapter<ContentModel,
            ContentListAdapter.ContentViewHolder>(DiffCallback){

    class ContentViewHolder(private var binding: ContentListItemBinding):
        RecyclerView.ViewHolder(binding.root) {
        fun bind(content: ContentModel) {
            binding.item = content

            binding.executePendingBindings()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContentViewHolder {
        return ContentViewHolder(ContentListItemBinding.inflate(LayoutInflater.from(parent.context)))
    }

    override fun onBindViewHolder(holder: ContentViewHolder, position: Int) {
        val content = getItem(position)
        holder.itemView.setOnClickListener {
            onClickListener.onClick(content)
        }
        holder.bind(content)
    }
}

 

데이터는 ListAdapter를 사용해 가져온다

ListAdapter는 RecyclerView에서 사용되고 background thread에서 돌아가고 데이터의 변화가 생기면 그 변화를 처리한다

 

:: ListAdapter<DATA CLASS, RecyclerView HOLDER>(CALLBACK) 으로 구성되어있다

  • onCreateViewHolder: 정의한 ViewHolder 생성
  • onBindViewHolder: ViewHolder에 데이터 위치시키기
    - getItem: Apdater 내에 List를 인덱싱할 때 사용 (리스트를 알아서 관리함)

 

    companion object DiffCallback : DiffUtil.ItemCallback<ContentModel>() {
        override fun areItemsTheSame(oldItem: ContentModel, newItem: ContentModel): Boolean {
            return oldItem === newItem
        }

        override fun areContentsTheSame(oldItem: ContentModel, newItem: ContentModel): Boolean {
            return oldItem.title == newItem.title
        }
    }

 

그래서 이렇게 데이터 변화가 생기면 처리하는 DiffCallBack 함수를 구현해준다

  • areItemsTheSame : item이 동일한지 비교
  • areContentsTheSame: item의 내용이 같은지 비교 (areItemsTheSame 다음으로 수행됨)

 

 

 

- Reference

화성 사진 가져오기: https://developer.android.com/codelabs/kotlin-android-training-internet-filtering#1

recycler view 설명: https://wooooooak.github.io/android/2019/03/28/recycler_view/

ListAdapter: https://developer.android.com/reference/androidx/recyclerview/widget/ListAdapter