Android/Kotlin

[Android, Kotlin] RecyclerView

겨울시인 2022. 4. 20. 16:43

1. 기능 구현

  • 화면에 내가 만든 Custom 리싸이클러뷰(RecyclerView)를 만들기
  • 각각의 리스트에 이벤트 발생 시, 토스트 메세지 띄우기

 
 

2. Android Studio에서 기본 프로젝트(with empty activity) 생성하자!

생성시 'Empty Activity'로 기본 생성

 
 

3. ViewBinding 사용을 위한 build.gradle 설정

  android {
          // 뷰 바인딩 옵션 활성화
          viewBinding {
              enabled = true
          }
      }

 
 

4. list_item.xml

  • RecyclerView에서 하나의 itemView 에 대한 Column 들을 정의해보자.
  • res -> layout 우클릭 -> New -> Layout Resource file : list_item.xml 생성
  • ImageView 에 쓰일 이미지 2개 (남/여 = profile_m/profile_w) 는 미리 res/drawable 에 저장해 두자!
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    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="wrap_content">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0">

        <ImageView
            android:id="@+id/iv_profile"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/profile_m" />

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:text="겨울시인"
            android:textColor="#000000"
            android:textStyle="bold"
            app:layout_constraintBottom_toTopOf="@+id/tv_job"
            app:layout_constraintStart_toEndOf="@+id/iv_profile"
            app:layout_constraintTop_toTopOf="@+id/iv_profile" />

        <TextView
            android:id="@+id/tv_job"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:text="안드로이드 앱 개발자"
            android:textColor="#000000"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="@+id/iv_profile"
            app:layout_constraintStart_toEndOf="@+id/iv_profile"
            app:layout_constraintTop_toBottomOf="@+id/tv_name"
            app:layout_constraintVertical_bias="0.0" />

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="27"
            android:textColor="#FF0000"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="@+id/tv_name"
            app:layout_constraintStart_toEndOf="@+id/tv_name"
            app:layout_constraintTop_toTopOf="@+id/tv_name" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 
 

5. Profile.kt

하나의 itemView Column 들을 모아 객체 모델 클래스를 만들어 놓자.

class Profile (val gender:Int, val name:String, val age:Int, val job:String)

 
 

6-1. ProfileAdapter.kt (Custom Adaper Class 만들기)

  • Class 의 param으로 Profile 객체들을 담는 ArrayList 타입의 변수 profileList 를 가진다.
  • 상속 : RecyclerView.Adapter
    • RecyclerView 의 Adapter 에서는 각각의 View(gender, name, age, job)를 보관하는 ViewHolder 를 통해 재사용이 가능하다.
    • 여기서는 CustomViewHolder Class 를 만들어 사용하자.
class ProfileAdapter(val profileList:ArrayList<Profile>):RecyclerView.Adapter<ProfileAdapter.CustomViewHolder>()
  • 상속 후, 반드시 구현해야할 메소드도 override 해보자 (onCreateViewHolder, getItemCount, onBindViewHolder)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileAdapter.CustomViewHolder 

override fun getItemCount(): Int

override fun onBindViewHolder(holder: ProfileAdapter.CustomViewHolder, position: Int)

 
 

6-2. ProfileAdapter.kt (메소드, inner class 구현)

  • onCreateViewHolder
    • 화면을 최초 로딩하여 만들어진 View가 없는 경우, xml파일을 inflate하여 ViewHolder를 생성한다
    • 여기서는 CustomViewHolder 를 생성함과 동시에 apply{} 구문을 통해 각 itemView 에 대한 setOnClickListener 를 적용해보자.
  • onBindViewHolder
    • 생성된 Holder 에서 보관중인 View(gender, name, age, job) 들과 데이터(profileList)를 연결
  • getItemCount
    • itemView 의 총 갯수를 리턴
  • inner class CustomViewHolder
    • Holder 에서 보관해야할 View(gender, name, age, job) 들을 변수에 담아 둔다.
class ProfileAdapter(val profileList:ArrayList<Profile>):RecyclerView.Adapter<ProfileAdapter.CustomViewHolder>()
{
    private var mBindingAdaper : ListItemBinding? = null
    private val bindingAdpter get() = mBindingAdaper!!

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileAdapter.CustomViewHolder {
        mBindingAdaper = ListItemBinding.inflate(LayoutInflater.from(parent.context))

        return CustomViewHolder(bindingAdpter.root).apply {
            itemView.setOnClickListener {
                val curPos:Int = this.adapterPosition
                val profile:Profile = profileList[curPos]
                Toast.makeText(parent.context, "이름:${profile.name}, 직업:${profile.job}", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onBindViewHolder(holder: ProfileAdapter.CustomViewHolder, position: Int) {
        holder.gender.setImageResource(profileList[position].gender)
        holder.name.text = profileList[position].name
        holder.age.text = profileList[position].age.toString()
        holder.job.text = profileList[position].job

    }

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

    inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        val gender = itemView.findViewById<ImageView>(R.id.iv_profile)
        val name = itemView.findViewById<TextView>(R.id.tv_name)
        val age = itemView.findViewById<TextView>(R.id.tv_age)
        val job = itemView.findViewById<TextView>(R.id.tv_job)

    }
}

 
 

7. MainActivity.kt

  • 데이터로 쓸 Profile 객체들을 arrayList 형태로 담아주자.
  • RecyclerView Adapter를 사용할때는 ListView와 다르게 LayoutManager 를 같이 사용해야 한다.
  • LayoutManager : 레이아웃의 종류에 따라 item을 배치, itemView가 더이상 화면에 보이지 않을때의 재사용 여부를 결정한다.
      1. LinearLayoutManager
      1. GridLayoutManager
      1. StaggeredGridLayoutManager
  • setHasFixedSize(true) : item들의 크기에 대해 가변적 or 고정적으로 할지를 설정 할수있다.
    • false 의 경우 - 매번 크기를 계산해야하므로 성능상 문제가 있을 가능성이 크다.
  • 마지막으로 플러그에 어댑터를 꼳듯이.. 열심히 만들어 놓은 ProfileAdapter(profileList)를 RecyclerView 에 달아주면 끝!!
class MainActivity : AppCompatActivity() {

    private var mBinding : ActivityMainBinding? = null
    private val binding get() = mBinding!!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val profileList = arrayListOf(
            Profile(R.drawable.profile_m, "겨울시인1", 25, "안드로이드 앱 개발자1"),
            Profile(R.drawable.profile_w, "겨울시인2", 26, "안드로이드 앱 개발자2"),
            Profile(R.drawable.profile_m, "겨울시인3", 27, "안드로이드 앱 개발자3"),
            Profile(R.drawable.profile_m, "겨울시인4", 28, "안드로이드 앱 개발자4"),
            Profile(R.drawable.profile_w, "겨울시인5", 29, "안드로이드 앱 개발자5"),
            Profile(R.drawable.profile_w, "겨울시인6", 30, "안드로이드 앱 개발자6"),
            Profile(R.drawable.profile_m, "겨울시인7", 31, "안드로이드 앱 개발자7"),
            Profile(R.drawable.profile_w, "겨울시인8", 32, "안드로이드 앱 개발자8"),
            Profile(R.drawable.profile_m, "겨울시인9", 33, "안드로이드 앱 개발자9"),
            Profile(R.drawable.profile_w, "겨울시인10", 34, "안드로이드 앱 개발자10")
        )

        binding.rvProfile.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
        binding.rvProfile.setHasFixedSize(true)

        binding.rvProfile.adapter = ProfileAdapter(profileList)
    }
}

 
 

8. 실행결과

profileList 데이터를 활용한 RecyclerView 가 보여지고 각 List(itemView)를 클릭 했을때 토스트 메세지도 OK!

Key Point!

  • RecyclerView 를 해보면서 제일 이해하기 어려웠던 부분은 Adapter 와 ViewHolder 부분이 아닌가 싶다. ListView 와 비교해서 이름마냥 재활용을 통해 성능부분에서도 개선됬다고 하나.. 이제 막 공부를 시작한 입장에서는 응?응? 뭐라고?
  • ListView 에서 이미 한번 Adapter 에 대해서는 해봤지만.. 업그레이드 버젼이 바로 또 있을 줄이야..
  • 재활용이란 개념은 로그를 찍어보고 나서야 이해하게 됐다. 예를들어 리스트가 길어 스크롤로 올렸다 내렸다하게 될 경우 :
    • ListView : getView() 를 계속 호출해서 매번 View Binding이 새롭게 이루어지므로 엄청 후짐.
    • RecyclerView : 처음 화면에 노출될때 한번 onCreateViewHolder()로 ViewHolder가 만들어지면 그 다음부터는 이미 View Binding을 ViewHolder가 모두 가지고 있으므로 재활용하여 씀. 다르긴 다르네~!!

 
 

9. Reference

유튜버 홍드로이드님의 안드로이드 코틀린 앱 만들기 #10