Kotlin

[android : kotlin] 코틀린 RecyclerView 사용방법 및 예제


리사이클러뷰(RecyclerView)는 리스트뷰(ListView)와 사용목적과 동작 방식이 매우 유사하다. 하지만 리스트뷰의 단점을 보완하였다. 그래서 개인적인 생각인데 리스트뷰를 쓰는 일은 이제 없을 것이다. 많은 데이터를 불러와 표시하여도 메모리 부족에 따른 프로그램 강제 종료의 상황은 발생하지 않는다. 리스트뷰를 사용시 스크롤을 내리다 보면 화면이 버벅거리는 현상이 발생한다. 리사이클러뷰는 이런 현상이 사라졌다. 그만큼 리스트뷰를 써야할 이유가 없어졌다고 본다.


코틀린 RecyclerView 사용방법

[스탭1] 리사이클러뷰를 사용하기위해 build.gradle(Module:app)파일을 열고 아래 라이브러리를 추가해준다.

dependencies {
   ...............
   implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

RecyclerView를 만들기 위해서는 RecyclerView 레이아웃과 RecyclerView의 ItemView, Adapter, Value Obejct 클래스 등 총 4 가지가 필요하다.

[스탭2] 메인액티비티(activity_main.xml)에 리사이클러뷰를 추가한다. 리사이클러뷰에 리스트 데이터를 뿌려줄 것이다.

androidx.recyclerview.widget.RecyclerView을 사용하였다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:gravity="center"
        android:text="RecyclerView 예제"
        android:textAppearance="@style/TextAppearance.AppCompat.Display1" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_below="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottom_layout"/>

    <LinearLayout
        android:id="@+id/bottom_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_margin="10dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/del_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="삭제" />

        <Button
            android:id="@+id/add_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="추가" />
    </LinearLayout>

</RelativeLayout>

[스탭3] 리사이클러뷰에서 사용될 아이템뷰 레이아웃(view_item_layout.xml)을 추가한다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@color/colorAccent"
    android:layout_margin="5dp">

    <ImageView
        android:id="@+id/userImg"
        android:layout_width="54dp"
        android:layout_height="54dp"
        android:layout_marginBottom="4dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="4dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/ic_launcher_round" />

    <TextView
        android:id="@+id/userNameTxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@+id/userImg"
        app:layout_constraintTop_toTopOf="@+id/userImg"
        tools:text="홍길동"/>

    <TextView
        android:id="@+id/payTxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="17sp"
        app:layout_constraintBottom_toBottomOf="@+id/userImg"
        app:layout_constraintStart_toStartOf="@+id/userNameTxt"
        tools:text="연봉" />

    <TextView
        android:id="@+id/addressTxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:textSize="17sp"
        app:layout_constraintBottom_toBottomOf="@+id/payTxt"
        app:layout_constraintStart_toEndOf="@+id/payTxt"
        app:layout_constraintTop_toTopOf="@+id/payTxt"
        tools:text="주소" />

</androidx.constraintlayout.widget.ConstraintLayout>

[스탭4] Value Object를 생성하자. 리스트 레이아웃에 뿌려줄 데이터를 담아놓은 클래스를 만드는 것이다. 별도의 클래스 파일로 생성하였다.

package edu.kotlin.study

/*dataVo.kt*/
class DataVo(val name: String, val id: String, val address: String, val pay: Int, val photo: String)

[스탭5] 리사이클러뷰 어댑터 클래스를 만들자. RecyclerView.Adapter를 확장하여 생성한다.

onCreateViewHolder(), onBindViewHolder(), getItemCount() 3개의 메서드를 필수로 오버라이드 하여 처리한다.

onCreateViewHolder() 메서드는 화면을 최초 로딩하여 만들어진 View가 없는 경우 레이아웃을 inflate하여 ViewHolder를 생성한다. onBindViewHolder() 메서드에서는 layout의 view와 데이터를 연결한다. getItemCount() 메서드는 아이템 갯수를 리턴처리하면 된다.

package edu.kotlin.study

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

class CustomAdapter(private val context: Context, private val dataList: ArrayList<DataVo>) :
    RecyclerView.Adapter<CustomAdapter.ItemViewHolder>() {
 
    inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val userPhoto = itemView.findViewById<ImageView>(R.id.userImg)
        private val userName = itemView.findViewById<TextView>(R.id.userNameTxt)
        private val userPay = itemView.findViewById<TextView>(R.id.payTxt)
        private val userAddress: TextView = itemView.findViewById<TextView>(R.id.addressTxt)

        fun bind(dataVo: DataVo, context: Context) {
		//사진 처리
            if (dataVo.photo != "") {
                val resourceId =
                    context.resources.getIdentifier(dataVo.photo, "drawable", context.packageName)

                if (resourceId > 0) {
                    userPhoto.setImageResource(resourceId)
                } else {
                    userPhoto.setImageResource(R.mipmap.ic_launcher_round)
                }
            } else {
                userPhoto.setImageResource(R.mipmap.ic_launcher_round)
            }

            //TextView에 데이터 세팅
            userName.text = dataVo.name
            userPay.text = dataVo.pay.toString()
            userAddress.text = dataVo.address
        }
    }

    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.view_item_layout, parent, false)
        return ItemViewHolder(view)
    }

    
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        holder.bind(dataList[position], context)
    }

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

[스탭6] 메인액티비티클래스(MainActivity.kt)에 리사이클러뷰에 대한 선언 및 초기화를 진행하자.

ListView Adapter와 달리 RecyclerView Adapter는 레이아웃 매니저 (LayoutManager)를 설정해야한다.

기본적으로 LinearLayoutManager, GridLayoutManager , StaggeredGridLayoutManager 등 3가지의 LayoutManager 라이브러리를 지원한다.

setHasFixedSize(true) 메서드는 리사이클러뷰 안 아이템들의 크기를 가변적으로 적용할지, 일정한 고정 크기를 적용할지를 지정한다. 만약 false값으로 적용하면 매번 아이템들의 크기를 계산해야 하므로 성능 저하가 발생할 수 있다. 데이터를 신규 추가하거나 삭제한 경우 반드시 notifyDataSetChanged()메소드를 호출하여 adapter에게 값이 변경되었음을 알려주어야 리사이클러뷰가 갱신됨을 잊지 말아야 한다.

package edu.kotlin.study

import android.media.MediaPlayer
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    var userList = arrayListOf<DataVo>(
        DataVo("IU", "test1", "전주시", 30000000,"user_img_01"),
        DataVo("홍길동", "test2", "서울시",10000000, "user_img_02"),
        DataVo("김영수", "test3", "광주시", 20000000, "user_img_03")
    )
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) 

        val mAdapter = CustomAdapter(this, userList)
        recycler_view.adapter = mAdapter

  
        val layout = LinearLayoutManager(this)
        recycler_view.layoutManager = layout
        recycler_view.setHasFixedSize(true)
	}

}


[build.gradle(Module:app)]

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29

    defaultConfig {
        applicationId "edu.kotlin.study"
        minSdkVersion 22
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.1'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}

[build.gradle(Project)]

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = "1.3.72"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

실행화면

[REFERENCE]

Leave a Reply

error: Content is protected !!