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가 더이상 화면에 보이지 않을때의 재사용 여부를 결정한다.
- LinearLayoutManager
- GridLayoutManager
- 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가 모두 가지고 있으므로 재활용하여 씀. 다르긴 다르네~!!