ListView와 RecyclerView

최윤성·2021년 4월 7일
2
post-thumbnail

ListView대신 RecyclerView가 나온 배경

ListView에서는 모든 데이터에대한 View를 만들고, View가 사라졌다가
나타날때마다 리소스를 불러와야한다.

즉 이말은 화면을 아래로 스크롤했다가 다시 위로 올릴때마다 리소스를 다시 불러오게 된다는소리다.

이러한 방법은 많은 메모리와 저장공간을 사용하기 때문에 대용량의 정보(데이터)를 불러오게된다면 앱이 느려지거나 충돌할 가능성이 있다.

이러한 이유때문에 RecyclerView가 ListView의 단점을 보안하기위해 나온것이다.

ViewHolder를 필수적으로 사용해야하고, LayoutManager를 설정하는등 ListView보다 더 복잡한 구조긴하지만 앱에서 메모리를 효율적으로 사용할 수 있게 해주니 복잡한 구조말고는 장점이 두드러지는것이다.

사용하기위해 준비해야할것

1.Gradle에서 Implement 추가
2.데이터 클래스 정의
3.레이아웃에 RecyclerView 추가
4.item 생성
5.어댑터 생성
6.어댑터 설정

다음과 같은 리스트를 다뤄보도록 하겠다.

1. Gradle에서 Implementation 추가

RecyclerView는 기본 API에 제공되어 있지 않기 때문에, Support Library 추가를 해주어야 사용가능

Gradle Scrpits - build.gradle (Module: app)경로로 파일을 열어서 dependencies 안에 implementation을 추가해주어야한다.

implementation 'com.android.support:recyclerview-v7:26.1.0'

혹은

implementation "androidx.recyclerview:recyclerview:1.1.0"

을 입력해준 다음 gradle 파일을 수정하고 나면 우측 상단의 Sync Now 를 눌러서 설정을 업데이트 한다.

2. 데이터 클래스 정의

데이터를 저장하는 공간!

여기서는 개의 목록을 나타내는 RecyclerView를 만들기때문에 새로운 클래스인 DogClass를 만들었다. 데이터 변수는 breed,gender,age,photo로 구성하였다.

  • 여기서 photo는 drawable에 들어갈 이미지 파일의 이름

class Dog (val breed: String, val gender: String, val age: String, val photo: String)

그리고 변수를 초기화한후 개의 목록을 가지고있을 ArrayList를 추가로 써준다.

/* MainActivity.kt* /

var dogList = arrayListOf<Dog>() // 우선 빈 dogList라는 리스트 생성

3.레이아웃에 RecyclerView 추가

전에 추가한 Gradle에 대해서 추가한 코드가 이번에는 빛을 발할시간이다! <android.support.v7.widget.RecyclerView> 경로를 통해 RecyclerView를 레이아웃에 추가하였다!

// XML Layout코드
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/mRecyclerView"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp">
    </android.support.v7.widget.RecyclerView>

</android.support.constraint.ConstraintLayout>

4. Item 생성

이번엔 RecyclerView의 항목 하나하나를 담당할 item뷰를 만든다. 여기서는 xml Layout파일을 왼쪽에 사진, 상단에 종류, 하단에 나이와 성별이 오도록 했다.

<!--main_rv_item.xml-->
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="@drawable/item_border"
    android:layout_marginTop="2dp"
    android:layout_marginBottom="2dp"
    android:layout_marginStart="4dp"
    android:layout_marginEnd="4dp">

    <ImageView
        android:id="@+id/dogPhotoImg"
        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/dogBreedTv"
        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/dogPhotoImg"
        app:layout_constraintTop_toTopOf="@+id/dogPhotoImg"
        tools:text="Breed"/>

    <TextView
        android:id="@+id/dogAgeTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="@+id/dogPhotoImg"
        app:layout_constraintStart_toStartOf="@+id/dogBreedTv"
        tools:text="Age" />

    <TextView
        android:id="@+id/dogGenderTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="@+id/dogAgeTv"
        app:layout_constraintStart_toEndOf="@+id/dogAgeTv"
        app:layout_constraintTop_toTopOf="@+id/dogAgeTv"
        tools:text="Gender" />

</android.support.constraint.ConstraintLayout>

또한 다음 테두리를 가진 레이아웃을 만들기위해 drawable을 만들고,android:background="@drawable/item_border"옵션을 주었다.

5. 어댑터 생성

RecyclerView와 그곳에 들어갈 각각의 item, 연동할 데이터까지 설정을 마쳤다면 Adapter를 만들어야한다! 다음 사진과같이 사진,종류,나이,성별등 어느 요소를 View에 넣을 것인지 연결해주는것이 Adapter의 역할이다! (이제부터 신경쓸것이 많아지기 시작한다)

우선, ContextArrayList<Class>를 필요로 한다.

class MainRvAdapter(val context: Context, val dogList, ArrayList<Dog>) :
RecyclerView.Adapter<>() {

    }

RecyclerView.Adapter 에서는 ViewHolder라는것이 필요한데, 아직 만들어지지않았다. 따라서 RecyclerView.Adapter<> 부분에 오류가 날것이다. 당황하지말자!! 여기서 여기에 필요한 Holder클래스를 추가해주면 된다.

class MainRvAdapter(val context: Context, val dogList, ArrayList<Dog>):
RecyclerView.Adapter<>() {

    inner class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
        val dogPhoto = itemView?.findViewById<ImageView>(R.id.dogPhotoImg)
        val dogBreed = itemView?.findViewById<TextView>(R.id.dogBreedTv)
        val dogAge = itemView?.findViewById<TextView>(R.id.dogAgeTv)
        val dogGender = itemView?.findViewById<TextView>(R.id.dogGenderTv)

        fun bind (dog: Dog, context: Context) {
          /* dogPhoto의 setImageResource에 들어갈 이미지의 id를 파일명(String)으로 찾고,
          이미지를 불러오지 못했을경우 안드로이드 기본 아이콘을 표시한다.*/
            if (dog.photo != "") {
                val resourceId = context.resources.getIdentifier(dog.photo, "drawable", context.packageName)
                dogPhoto?.setImageResource(resourceId)
            } else {
                dogPhoto?.setImageResource(R.mipmap.ic_launcher)
            }
    /* 나머지 TextView와 String 데이터를 연결한다! 가져온 내용을 나타내주어야 하기때문 */
            dogBreed?.text = dog.breed
            dogAge?.text = dog.age
            dogGender?.text = dog.gender
        }
    }
}

Holder의 상단에서는 각 View의 변수이름을 정해 변수를 가져오고, findViewById를 통해 ImageView인지 TextView인지 등등 종류를 구별하고 id를 통해 layout과 연결된다. bind함수는 ViewHolder와 클래스의 각 변수를 연결하는역할을 한다. 쉽게 말해 값을 itemLayout에서 가져오고 값을 대입해주는 역할을 해주는것이다.

그리고 상단 RecyclerView.Adapter<>의 괄호에 지금 만든 Holder를 넣는다. inner Class로 만들었기 때문에 MainRvAdapter.Holder를 입력한다.

class MainRvAdapter(val context: Context, val dogList, ArrayList<Dog>):
RecyclerView.Adapter<>() //<- 여기부분!

또한 메인 클래스에 들어가보게되면 오류가 발생되어있는데, 필수로 사용해야 하는 함수를 Override하지 않았기 때문이다. Alt + Enter를 눌러 목록에 있는 세 개의 함수를 모두 Override 한다.

뷰홀더 설명

onCreateViewHolder : 화면을 최초 로딩하여 만들어진 View가 없는 경우, xml파일을 inflate하여 ViewHolder를 생성한다.

getItemCount : RecyclerView로 만들어지는 item의 총 개수를 반환한다.

onBindViewHolder : 위의 onCreateViewHolder에서 만든 view와 실제 입력되는 각각의 데이터를 연결한다.

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

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

override fun onBindViewHolder(holder: Holder?, position: Int) {
    holder?.bind(dogList[position], context)
}

모든함수 + 뷰홀더 최종코드

class MainRvAdapter(val context: Context, val dogList: ArrayList<Dog>) :
RecyclerView.Adapter<MainRvAdapter.Holder>() {
    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
        val view = LayoutInflater.from(context).inflate(R.layout.main_rv_item, parent, false)
        return Holder(view)
    }

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

    override fun onBindViewHolder(holder: Holder?, position: Int) {
        holder?.bind(dogList[position], context)
    }

    inner class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
        val dogPhoto = itemView?.findViewById<ImageView>(R.id.dogPhotoImg)
        val dogBreed = itemView?.findViewById<TextView>(R.id.dogBreedTv)
        val dogAge = itemView?.findViewById<TextView>(R.id.dogAgeTv)
        val dogGender = itemView?.findViewById<TextView>(R.id.dogGenderTv)

        fun bind (dog: Dog, context: Context) {
            if (dog.photo != "") {
                val resourceId = context.resources.getIdentifier(dog.photo, "drawable", context.packageName)
                dogPhoto?.setImageResource(resourceId)
            } else {
                dogPhoto?.setImageResource(R.mipmap.ic_launcher)
            }
            dogBreed?.text = dog.breed
            dogAge?.text = dog.age
            dogGender?.text = dog.gender
        }
    }
}

6.어댑터 설정

*LayoutManager : RecyclerView의 각 item들을 배치하고, item이 더이상 보이지 않을 때 재사용할 것인지 결정하는 역할을 한다.

ListView Adapter와는 다르게, RecyclerView Adapter에서는 *레이아웃 매니저 (LayoutManager) 를 설정해주어야 한다.

레이아웃 매니저를 써야하는 이유?

LayoutManager 사용을 통해 불필요한 findViewById를 수행하지 않아도 되고, 앱 성능을 향상시킬 수 있다. 또한 RecyclerView를 불러올 액티비티에 LayoutManager를 추가한다.

마지막으로 recyclerView에 setHasFixedSize 옵션에 true 값을 준다.

true 옵션을 주는이유!

item이 추가되거나 삭제될 때 RecyclerView의 크기가 변경될 수도 있고, 그렇게 되면 계층 구조의 다른 View 크기가 변경될 가능성이 있기 때문

      //[기본 3개의 Manager중 하나]
       //-LinearLayoutManager
       //-GridLayoutManager
       //-StaggeredGridLayoutManager
       
        val lm = LinearLayoutManager(this)
       mRecyclerView.layoutManager = lm
       mRecyclerView.setHasFixedSize(true)

하지만 여기서 마지막이 아니다! itemLayout에 넘겨줄 Arraylist가 비어있기 때문이다! 마지막으로 값이 잘들어가는지 Arraylist에 값을 넣어보자!
(Edit Text와 Button으로 값을 입력해주는것도 해줄 수 있지만 우리는 여기까지만하자)

var dogList = arrayListOf<Dog>(
   Dog("Chow Chow", "Male", "4", "dog00"),
   Dog("Breed Pomeranian", "Female", "1", "dog01"),
   Dog("Golden Retriver", "Female", "3", "dog02"),
   Dog("Yorkshire Terrier", "Male", "5", "dog03"),
   Dog("Pug", "Male", "4", "dog04"),
   Dog("Alaskan Malamute", "Male", "7", "dog05"),
   Dog("Shih Tzu", "Female", "5", "dog06")
)

source.

profile
웹과 앱을 사랑하는 남자

0개의 댓글