그냥 가끔의 기록장

[Kotlin] RecyclerView 예제 본문

Android

[Kotlin] RecyclerView 예제

the phoenix 2021. 6. 6. 18:09

안드로이드 기본기가 부족한데 마땅한 강의가 없어서 Google Android Kotlin Course를 듣고 있다. Unit 2 Part3에서 RecyclerView를 사용하는 예제가 나와서 이를 정리해보았다. 

 

0. RecyclerView가 뭔지

과거에는 한 화면에 여러개의 item 목록들을 보여주려고 ListView, GridView를 사용했으나 안드로이드 3.1 버전 이후부터 얘네들은 legacy가 되었다고 한다. RecyclerView를 구글에서도 권장하니, 앞으로는 item 목록들을 보여줄 때 RecyclerView를 사용하자. RecyclerView에는 LayoutManager가 속성으로 들어있어서, grid, linear 처럼 item 목록들을 어떻게 보여줄지도 결정해준다. 

 

매번 헷갈렸는데, Recyclerview를 만들고 적용하는 것은 크게 네 단계로 구성된다.

1. data 만들기

-> 2. xml만들기 (activity나 fragment에 들어갈 recyclerview 자체 & recyclerview 내부 item으로 사용할 xml)

-> 3. adapter와 viewHolder 만들기

-> 4. adapter와 RecyclerView 연동하기 

 

다음과 같은 네 단계를 거쳐서 Affirmations 앱을 만들어보자. 최종 결과는 다음과 같게 나온다. 

 

1.  item으로 사용할 문자열 10개 만들어주기

  명언 10개를 string.xml에 저장해 보았다. 각각의 name은 affirmation1~10이다. 코드는 다음과 같다. (모든 글귀 데이터, 사진 데이터, 코드는 https://developer.android.com/courses/pathways/android-basics-kotlin-unit-2-pathway-3#codelab-https://developer.android.com/codelabs/basic-android-kotlin-training-recyclerview-scrollable-list 여기서 갖고온 것이다. 매우 잘되어있음! 나는 이해하려고 얘를 직접 따라해보면서 정리해봤다..)

<resources>
    <string name="app_name">Affrimations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

  사진은 다음 링크에 들어가면 있다. res > drawable에 image1~10을 복붙해주면 된다. 

사진 링크: https://developer.android.com/codelabs/basic-android-kotlin-training-display-list-cards?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-unit-2-pathway-3%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-display-list-cards#1 

 

2.  model 만들기

(1) Affirmation이라는 data class 생성

  java>com.example.affirmations 에서 우클릭 한 후 new package로 만든다. package명은 com.example.affirmations.model이 된다. 해당 package에 Affirmation 이라는 데이터 클래스를 만든다. (데이터를 정의하는 부분)

  각 RecylerView 안의 item으로 사진과 text를 넣을 것이므로 stringId와 imageId가 생성자로 들어가야 한다.

코드는 다음과 같다. (imageResourceId와 stringResourceId 순서가 뒤바뀌는것을 방지하기 위해 @StringRes, @DrawableRes로 표시해준다.) 

 

<Affirmation.kt >

package com.example.affrimations.model

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes

data class Affirmation(@StringRes val stringResourceId: Int, @DrawableRes val imageResourceId:Int)

 

(2) Datasource라는 data를 load하는 class 생성

  앞서 (1)에서 data class를 생성했다. 일반적으로 앱을 만들 때 데이터는 외부에서 갖고오는 경우가 많다. 예제 코드에서는 최대한 간단히 하려고 내부에 미리 저장한 데이터를 사용할 것이지만, 실전처럼 데이터를 로드하는 부분도 따로 분리할 것이다.

  

  java>com.example.affirmations 에서 우클릭 한 후 new package로 만든다. package명은 com.example.affirmations.data이다. 해당 package 안에 Datasource라는 클래스를 만든다. (데이터를 갖고 오는 부분) 

  이 예제에서는 데이터를 string.xml에 저장해놓고 불러올 것이므로 string.xml에 저장된 affirmation 들을 list로 저장해 반환할 것이다.

즉, loadAffirmations라는 함수를 만들 것인데, 이 함수는 List<Affirmation>을 반환한다. 코드는 다음과 같다.

 

<Datasource.kt>

package com.example.affrimations.data

import com.example.affrimations.R
import com.example.affrimations.model.Affirmation

class Datasource() {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1, R.drawable.image1),
            Affirmation(R.string.affirmation2, R.drawable.image2),
            Affirmation(R.string.affirmation3, R.drawable.image3),
            Affirmation(R.string.affirmation4, R.drawable.image4),
            Affirmation(R.string.affirmation5, R.drawable.image5),
            Affirmation(R.string.affirmation6, R.drawable.image6),
            Affirmation(R.string.affirmation7, R.drawable.image7),
            Affirmation(R.string.affirmation8, R.drawable.image8),
            Affirmation(R.string.affirmation9, R.drawable.image9),
            Affirmation(R.string.affirmation10, R.drawable.image10)
        )
    }
}

 

3. RecyclerView 추가하기 

(1) xml에 RecyclerView 추가

  MainActivity에 Recyclerview만 추가할거라 다음과 같이 FrameLayout을 최상단으로 둔다. (사실 뭘 최상단으로 해도 상관은 없다.) RecyclerView의 경우 androidx에 있는 것을 사용한다. item 양이 많으면 세로로 스크롤 할 수 있게 scrollbars 속성을 true로 해준다. 또한 RecyclerView는 Grid나 linear등 다양하게 item들을 표현해주는데, 우리는 그냥 linear 세로로 나타낼거라 layoutManager 속성에 LinearLayoutManager를 넣어준다. 

 

- 참고: LayoutManager에는 세 종류가 지원된다. 

1. LinearLayoutManager : vertical, horizontal

2. GridLayoutManager: row당 표시되는 item 개수가 여러개인 list

3. StaggeredGridLayoutManager: 높이가 불규칙적인 grid list

 

<activity_main.xml>

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        android:scrollbars="vertical"/>
</FrameLayout>

 

(2) Adapter + ViewHolder

1) item xml 생성

  RecyclerView안에는 사진과 글귀를 같이 넣을 것이다. RecyclerView의 각 item으로 들어갈 xml을 정의하는 부분이다. LinearLayout을 최상단으로 두고 안에 ImageView와 TextView를 넣어주었다. 또한 각 item들이 카드 형태라 Card view를 추가하였다. 

 

<list_item.xml>

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/item_image"
            android:layout_width="match_parent"
            android:layout_height="194dp"
            android:importantForAccessibility="no"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/item_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="16dp"
            android:textAppearance="?attr/textAppearanceHeadline6" />

    </LinearLayout>

</com.google.android.material.card.MaterialCardView>

 

2) adapter.kt 생성 

java>com.example.affirmations 에서 우클릭으로 new package를 만들어준다. package명은 com.exmple.affirmations.adapter가 된다. 해당 package 안에 ItemAdapter.kt을 만든다. 

 

이제 이 class에서는 context, dataset을 인자로 받은 후 RecyclerView.Adapter<ItemAdapter.ItemViewHolder>()를 반환하는 adapter를 만들것이다. 코드는 3) 참고!

 

참고: RecyclerView는 item view와 직접 상호작용하지 않고 viewHolder를 대신 처리한다. 

3) adapter.kt에 중첩 클래스로 ViewHolder 생성

ItemAdapter 안에 중첩 클래스로 ItemViewHolder를 만든다. 앞서 ItemAdapter에서 RecyclerView.Adapter를 반환하므로 추상 메서드 3개를 구현해야 한다. 

 

1. onCreateViewHolder: RecyclerView의 새로운 viewHolder를 생성해준다. (재사용 가능한 viewHolder 없는 경우 호출 됨) 이를 위해서 list_item.xml을 inflate하고 ItemViewHolder를 반환한다. 

 

2. getItemCount: dataset의 크기를 반환한다. 

 

3. onBindViewHolder: item view의 contents를 바꿔주는 메서드이다. 이를 위해서 dataset[position]으로 item view에 들어갈 item을 반환받는다. 이후 textview, imageview에 item의 contents를 set해준다. 

 

<ItemAdapter.kt>

package com.example.affrimations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affrimations.R
import com.example.affrimations.model.Affirmation

class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
        val imageView: ImageView = view.findViewById(R.id.item_image)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        val adapterLayout = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
        holder.imageView.setImageResource(item.imageResourceId)
    }

    override fun getItemCount()=dataset.size
}

 

(3) MainActivity에 RecyclerView 추가 

  이제 MainActivity에서는 다음과 같은 순서로 RecyclerView를 추가하고 연동하면 된다.

 

1. 데이터 불러오기

-> Datasource().loadAffirmations() 함수로 string.xml과 drawable에 있는 text, image를 리스트로 반환받는다.

 

2. RecyclerView findViewById로 찾기

 

3. RecyclerView에 adapter 연결하기

-> 우리가 정의한 ItemAdapter를 생성해서 RecyclerView.adpater로 연동하되, adapter의 인자로는 context와 data를 넣어주어야 한다. 

 

<MainActivity.kt>

package com.example.affrimations

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.RecyclerView
import com.example.affrimations.adapter.ItemAdapter
import com.example.affrimations.data.Datasource

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //data 불러오기
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)

        recyclerView.adapter = ItemAdapter(this, myDataset)
        recyclerView.setHasFixedSize(true)
    }
}

 

Reference

https://penguin-story.tistory.com/18 

https://developer.android.com/codelabs/basic-android-kotlin-training-display-list-cards?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-unit-2-pathway-3%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-display-list-cards#2

https://developer.android.com/codelabs/basic-android-kotlin-training-recyclerview-scrollable-list?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-kotlin-unit-2-pathway-3%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-training-recyclerview-scrollable-list#3 

https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=ko 

Comments