프로그래밍/Android

[Android] RecyclerView를 이용해 리스트 구현하기

 


 

안드로이드 앱을 구현하다보면 리스트를 구현해야 할 때가 있다. 위에 그림처럼 버킷 리스트들을 리스트로 뿌려줄 때가 그때다.

 

그래서 이번 안드로이드 글은 RecyclerView를 구현할 것이다.

 

 

근데 왜 ListView 말고 RecyclerView를 쓸까?

 

안드로이드의 View 중 ListView가 존재한다. 하지만 왜 이 거위는 RecyclerView를 쓰는 걸까? 이유는 아주 간단하다. 대부분 리스트들은 값이 변경될 때가 많기에 아주 편하게 Recycle 되어야 한다. ListView는 초창기에 사용하던 리스트이기에 부족한 점이 많았다. 특히 리스트 안에 아이템들을 개별 터치해서 내용을 바꿔줄 때도 있고, 리스트를 삭제할 때 사용자의 관점에서 부드럽게 진행되어야 한다. 이것을 개선한 것이 RecyclerView이다. RecyclerView는 전설이다...

 

 


 

레이아웃 Xml code 작성해보기

 

 

xml을 작성하는 것은 어렵지 않다.

	<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

 

ID는 rv_list 라고 명시해주었다. 정말 간단하다! 이렇게 추가만 해주어도 미리보기에 리스트가 생성된 것을 볼 수 있다.

 

좌우 여백 같은 경우는 따로 정해주었다.

 

RecyclerView에 들어갈 Item View 만들기

 

 

이제 RecyclerView 안에 들어갈 item의 View를 만들어야 한다. layout폴더에 Layout Resource File을 만들어보자.

 

그후엔 원하는 리스트 안에 들어갈 View를 만들어 주면 된다.

거위가 만든 Item View 

이런식으로 xml 파일을 만들었으면 View 부분은 건드릴게 없다. 다음은 RecyclerView의 Adapter Class를 만들어주어야 한다. RecyclerView의 Adapter엔 ViewHolder를 사용해 View를 제어한다.

 

 

RecyclerViewAdapter 만들기

이제 대망의 Adapter 구현이다. RecyclerView.Adatper엔 필수로 들어가야 할 함수들이 있는데 하나하나 살펴보자.

class MainBucketListAdapter(private val context: Context, private val list: ArrayList<Bucket>) :
    RecyclerView.Adapter<MainBucketListAdapter.MainBucketHolder>() {
    
}

일단 class를 만들어준다. 저기 ArrayList안에 있는 Bucket은 임의로 만든 data class이고, MainBucketListAdapter.MainBucketHolder라는 이름도 내가 임의로 만든 이름이다.

(이름은 바뀌어도 상관 없읍니다 ^~^)

 

onCreateViewHolder

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainBucketHolder {
        return MainBucketHolder(
            LayoutInflater.from(context).inflate(R.layout.bucket_list_item, parent, false)
        )
    }

이것은 말그대로 사용할 ViewHolder를 호출하여 만들어주는 것이다. 여기서 return하는 'MainBucketHolder'는 내가 만든 inner class의 이름이다. 그 안에 View를 넣어서 return 해줬다.

 

MainBucketHolder(inner class)

    inner class MainBucketHolder(v: View) : RecyclerView.ViewHolder(v) {

        val itemView = v
        val title: TextView = itemView.findViewById(R.id.t_title)
        val content: TextView = itemView.findViewById(R.id.t_content)
        val imgRight: ImageView = itemView.findViewById(R.id.img_bucket_right)

        fun bind(data: Bucket) {

            title.text = data.BUCKET_NAME
            if (data.BUCKET_CONTENT.isNullOrEmpty()) {
                content.text = null
                content.visibility = View.GONE
            } else {
                content.text = data.BUCKET_CONTENT
                content.visibility = View.VISIBLE
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                itemView.outlineSpotShadowColor =
                    ContextCompat.getColor(context, android.R.color.darker_gray)
            } else {
                itemView.translationZ = 1f
            }

            if (!data.BUCKET_COLOR.isNullOrEmpty()) {
                imgRight.setColorFilter(Color.parseColor(data.BUCKET_COLOR))
            }
        }
    }
 

아까 호출해준 inner class이다. bind나 클래스 이름은 다르게 지정해줘도 된다.(호출만 잘해주면 된다..)

나눠서 보면 이 ViewHolder라는 놈은 RecyclerView의 각각 item들을 생성해주는 녀석이다. 그 안에 bind라는 함수는 Bucket이라는 내가 만든 data class를 받아서 값들을 변경해주고 있다.

 

data class Bucket(
    /**
     * 카테고리 ID
     */
    val CATEGORY_ID: String,

    /**
     * 버킷 ID
     */
    val BUCKET_ID: String,

    /**
     * 버킷 INDEX
     */
    val BUCKET_IDX: Int,

    /**
     * 버킷 NAME
     */
    val BUCKET_NAME: String,

    /**
     * 버킷 CONTENT
     */
    val BUCKET_CONTENT: String? = null,

    /**
     * 버킷 D-DAY
     */
    val BUCKET_DDAY: String? = null,

    /**
     * 버킷 START_DATE
     */
    val BUCKET_START_DATE: String? = null,

    /**
     * 버킷 END_DATE
     */
    val BUCKET_END_DATE: String? = null,

    /**
     * 버킷 COMPLETE
     */
    val BUCKET_COMPLETE: Boolean,

    /**
     * 생성날짜
     */
    val CRE_DATETIME: Long,

    /**
     * 수정날짜
     */
    val UPD_DATETIME: Long = 0,

    /**
     * 대표 색상
     */
    var BUCKET_COLOR: String? = null
)

대충 내가 짠 data class다. 혹시몰라 올려봅니다아!

 

getItemCount

이 함수는 RecyclerView에 들어갈 item의 갯수를 return해줘야 한다. 그렇기에 ArrayList로 보내준(다른 배열 가능) 녀석의 size값을 return해주면 된다.

    override fun getItemCount(): Int {
        return list.size
    }

요로코롬 item의 갯수를 정해준다.

 

onBindViewHolder

요 함수는 데이터(위에 쓴 Bucket)를 지정된 위치에 알맞게 표시하기 위해 호출하는 함수이다. class에서 list라는 변수를 넣어주면 된다.

    override fun onBindViewHolder(holder: MainBucketHolder, position: Int) {
        list[position].let { holder.bind(it) }
    }

요로코롬 넣어주었다. (꼭 이렇게 안넣어줘도 된다. holder.bind(list[position])해도 된다. let으로 겉멋좀 부려봤다 헿)

이렇게 넣어줬다면, RecyclerView의 Adapter에 필요한 것들을 다 선언해준 것이다.

 

자, 그렇다면 전체 코드를 보자.

 

class MainBucketListAdapter(private val context: Context, private val list: ArrayList<Bucket>) :
    RecyclerView.Adapter<MainBucketListAdapter.MainBucketHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainBucketHolder {
        return MainBucketHolder(
            LayoutInflater.from(context).inflate(R.layout.bucket_list_item, parent, false)
        )
    }

    override fun getItemCount(): Int {
        return list.size
    }

    override fun onBindViewHolder(holder: MainBucketHolder, position: Int) {
        list[position].let { holder.bind(it) }
    }

    inner class MainBucketHolder(v: View) : RecyclerView.ViewHolder(v) {

        val itemView = v
        val title: TextView = itemView.findViewById(R.id.t_title)
        val content: TextView = itemView.findViewById(R.id.t_content)
        val imgRight: ImageView = itemView.findViewById(R.id.img_bucket_right)

        fun bind(data: Bucket) {

            title.text = data.BUCKET_NAME
            if (data.BUCKET_CONTENT.isNullOrEmpty()) {
                content.text = null
                content.visibility = View.GONE
            } else {
                content.text = data.BUCKET_CONTENT
                content.visibility = View.VISIBLE
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                itemView.outlineSpotShadowColor =
                    ContextCompat.getColor(context, android.R.color.darker_gray)
            } else {
                itemView.translationZ = 1f
            }

            if (!data.BUCKET_COLOR.isNullOrEmpty()) {
                imgRight.setColorFilter(Color.parseColor(data.BUCKET_COLOR))
            }
        }
    }

}

별로 좋은 코드는 아니니 복붙하고 고쳐쓰도록하자..

 

 

만든 Adapter호출하기

이제 필요한 View의 Activity에서 adapter를 호출해주면 된다. 호출은 간편하다. xml로 만든 recyclerView의 id를 가져와서 setAdapter해주면 된다.

 

rv_list.adapter = MainBucketListAdapter(this, bucketDataList)

여기서 bucketDataList는 임의로 만든 ArrayList다. 이걸로 끝내기 아쉬우니 recyclerView 추가적인 설정도 넣어보자.

rv_list.layoutManager = LinearLayoutManager(this)
rv_list.setHasFixedSize(true)

해당 recyclerView의 레이아웃을 Linear로 잡아줬다. 그리고 recyclerView를 생성할 때 사이즈 크기가 고정되도록 설정해 주었다.

 

 

Run

자, 이제 한번 실행해보자. 에뮬레이터를 실행해서 RecyclerView가 잘 나오는지 확인해보자.

Recycler View!

데이터를 막 넣고 돌려봤다. RecyclerView가 잘 나오는 걸 확인할 수 있다.

 

추가적으로 RecyclerView의 Data를 추가하거나 삭제한 후에 View를 갱신할 땐,  Adapter를 변수로 받아서 notifyDataSetChanged() 해주면 된다.

 


이렇게 RecyclerView를 사용하는 법을 적어보았다. 미숙한 글이라고 생각되어 좀 더 공부를 더 해서 추가해볼 생각이다... 

설명이 부족한 부분은 댓글을 써주신다면 바로 반영해보겠습니다.

'프로그래밍 > Android' 카테고리의 다른 글

[Android] ConstraintLayout 에 대해  (0) 2020.10.10