[본 캠프(앱 개발 숙련) 강의내용 + 스탠다드 3, 4주차 강의 + 기타 사이트]
1. ViewHolder
- 화면에 표시될 데이터나 아이템들을 저장한다.
- (RecyclerView의 개념을 적용하려면) 스크롤 해서 안보이게 된 View를 재활용하기 위해
그 View를 기억하고 있어야 하는데, 이 역할을 ViewHolder가 한다.
기본 강의와 코드를 분석하여 아래와 같이 정리함. -> 스탠다드 (3, 4주차) 강의 들은 후 다시 작성함.
기본적인 메서드들 | 역할 | callTime |
onCreateViewHolder( parent: ViewGroup, viewType: Int ): Holder |
binding 셋업, Holder 객체 생성 및 반환 | ?? |
inner class ViewHolder( binding: ItemRvBinding ) |
bind() 메서드 및 onClick Event 로직 구현 onBindViewHolder에서 data연동을 위한 bind() 함수 구현 |
onCreateViewHolder()에 의해서 생성 |
onBindViewHolder( holder: Holder, position: Int ) |
Holder class의 파라미터 즉, 위젯을 DataSource와 연동 position값이 있기 때문에 DataSource[position]으로 index 접근이 가능함 |
새로운 Item을 불러와야 할 때 즉, 사용자에게 보여지기 전에 call |
getItemCount() | Item의 개수를 저장하는 getter 느낌 Adapter라면 모두 다 가지고 있는 듯 |
몇 개의 item을 띄워야 하는지 체크할 때 호출 |
위 메서드를 담은 풀코드 (단순히 강의 내용을 정리한 부분 -> 추후 Adapter 확정본은 제일 아래에)
class MyAdapter(val dataList: List<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>() {
override fun getItemId(position: Int) = position.toLong()
override fun getItemCount() = dataList.size
/**
* 역할
* 1. binding 초기화
* 2. binding을 전달해서 Holder 객체를 생성하고, 생성된 Holder 객체를 리턴한다.
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val binding = ItemRvBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(binding)
}
/**
* @param binding Recycler의 item(xml)
* 역할 : binding 객체를 이용해서 layout 내부의 위젯들을 초기화(연결)한다.
*/
inner class Holder(binding: ItemRvBinding) : RecyclerView.ViewHolder(binding.root) {
val image: ImageView
val name: TextView
val age: TextView
init {
with(binding) {
image = ivImg
name = tvName
age = tvAge
}
}
}
/**
* 역할 : Holder class의 파라미터인 위젯을 Data Source와 연동
* CallTime : 새로운 Item을 불러와야 할 때 (= 사용자에게 보여지기 직전에)
*/
override fun onBindViewHolder(holder: Holder, position: Int) {
val data = dataList[position]
with(holder) {
image.setImageResource(data.icon)
name.text = "name: ${data.name}"
age.text = "age: ${data.age}"
}
}
}
2-1. RecyclerView Item Click 처리 방법 - 강의내용 -> 아래에 개선된 존재함
클릭에 대한 처리를 Adapter가 아닌 Activity에서 하는 방법이다.
만약 처리를 Acitivity가 아닌 Adapter에서 해도 된다면, onBindViewHolder 내부의 itemView(ViewHolder의 파라미터임 type은 View)에서 하면 된다.
// Adapter
class MyAdapter(val dataList: List<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>() {
// Acitivity에서 클릭이벤트 처리 코드를 담은 ItemClick를 생성하고 초기화해줌
var itemClick: ItemClick? = null
interface ItemClick {
fun onClick(view: View, position: Int)
}
/**
* 역할 : Holder class의 파라미터인 위젯을 Data Source와 연동
* CallTime : 새로운 Item을 불러와야 할 때 (= 사용자에게 보여지기 직전에)
*/
override fun onBindViewHolder(holder: Holder, position: Int) {
with(holder) {
// 클릭 이벤트 감지
itemView.setOnClickListener {
// !!!중요 : 만약 액티비티에서 클릭이벤트 처리를 안하고, Adapter에서 해도 무관하다면
// 여기에서 클릭이벤트 처리를 하면 끝이다.
// 사실상, 메인에서 클릭이벤트 처리 코드를 호출하느 것과 같음.
itemClick?.onClick(it, position)
}
//
}
}
}
// Activity
class MyActivity : AppCompatActivity() {
private val binding: MyActivityBinding by lazy {
MyActivityBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val adapter = MyAdapter(dataList)
binding.rvMain.adapter = adapter
// 클릭 이벤트 처리 방법
// 문법은 잘 모르겠지만, 이런 흐름도 가능하구나,,, 신기하다
adapter.itemClick = object : MyAdapter.ItemClick { // TODO: 코드 해석 불가,,,
override fun onClick(view: View, position: Int) {
val name = dataList[position].name
Toast.makeText(this@MyActivity, "$name 선택!", Toast.LENGTH_SHORT).show()
}
}
}
companion object {
// Data Source
val dataList = listOf(
MyItem(R.drawable.img_post1, "Bella", "1"),
MyItem(R.drawable.img_post2, "Charlie", "2"),
MyItem(R.drawable.img_post3, "Daisy", "1.5"),
MyItem(R.drawable.img_post4, "Duke", "1"),
)
}
}
data class MyItem(
val icon: Int,
val name: String,
val age: String,
)
2-2. RecyclerView Item Click 처리 방법
클릭에 대한 처리를 Adapter가 아닌 Activity에서 하는 방법이다.
이번엔 Adapter의 파라미터를 통해서 onClick Event를 처리해보자.
3. "intent 생성, intent에 데이터 채워서 다른 Activity 실행"을 Extention(확장함수 및 제네릭 타입)으로 작업하는 방법
// intent 생성
inline fun <reified T : Any> newIntent(context: Context) : Intent = Intent(context, T::class.java)
// intent에 데이터 추가 -> 액티비티 시작
inline fun <reified T : Any> Context.launchActivity(vararg pair: Pair<String, Any?>) {
val intent = newIntent<T>(this)
intent.putExtras(bundleOf(*pair))
startActivity(intent)
}
// intent를 통해 전달받은 값 반환하기
inline fun <reified T : Any> Activity.extraNotNull(key: String, default : T? = null) = lazy {
val value = intent.extras?.get(key) ?: default
requireNotNull(value) { "$key is not contain" }
}
4. RecyclerView Item Click 처리 방법 2가지 최종 정리 + Adapter의 각 메서드의 역할 재정의
- click event 처리를 onBindViewHolder에서 해도 된다 안된다로 예전부터 말이 있었다고 한다.
- 본 캠프의 강의와 스탠다드 강의에서 RecyclerView의 Adapter를 다루는 부분이 달라서 혼동이 있었지만,
이벤트 로직을 제외한 코드는 하나로 정리할 수 있었다.
아래는 RecyclerView의 Item Click 이벤트 처리를 Activity에서 할 수 있는 2가지 방법이다.
사용시기는 정확하지 않고, 단순히 뇌피셜이다..
방법 1. MyActivity에서 어댑터 생성 시 onClick1 메소드를 정의해서 넘겨준다. (GPT: 가독성, 간결성 측면이 좋다)
사용시기 : [정적?] onClick1 메소드의 code가 같은 position 값일 때, 항상 같은 로직을 수행한다면 무관할 듯
방법 2. MyActivity에서 ItemClickInterface를 구현한 뒤, 이 프로퍼티에 셋업한다. (GPT: 확장성, 유연성, 명시적인 측면이 좋다)
사용시기 : [동적?] 같은 position일 때도 case에 따라 다른 로직을 돌려야 할 때 사용??
Adapter Code
package com.example.recyclerviewtest.camp
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerviewtest.databinding.ItemRvBinding
// 방법 1. MyActivity에서 어댑터 생성 시 onClick1 메소드를 정의해서 넘겨준다.
// onClick1 메소드의 code가 같은 position 값일 때, 항상 같은 로직을 수행한다면 무관할 듯
class MyAdapter(private val onClick1: (View, Int) -> Unit) : RecyclerView.Adapter<MyAdapter.Holder>() {
// DataSource
var dataList = listOf<MyItem>()
// 방법 2. MyActivity에서 ItemClickInterface를 구현한 뒤, 이 프로퍼티에 셋업한다.
// 같은 position일 때도 case에 따라 다른 로직을 돌려야 할 때 사용?
var itemClickInterface: ItemClickInterface? = null
interface ItemClickInterface {
fun onClickInterface(view: View, position: Int)
}
override fun getItemCount() = dataList.size
/**
* - binding 셋업, Holder 객체 생성 및 반환
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val binding = ItemRvBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(binding, onClick1)
}
/**
* @param binding Recycler의 item(xml)
* 역할 : onBindViewHolder에서 data 연동을 지원하기 위한 bind() 함수를 구현한다.
*/
inner class Holder(val binding: ItemRvBinding, onClickParameter: (View, Int) -> Unit) : RecyclerView.ViewHolder(binding.root) {
init {
itemView.setOnClickListener {
onClickParameter(it, adapterPosition)
}
}
fun bind(myItem: MyItem) {
with(binding) {
ivImg.setImageResource(myItem.icon)
tvName.text = myItem.name
tvAge.text = myItem.age
}
}
}
/**
* 역할 : data 연동 (Holder에서 정의한 bind 함수 호출) -> getView()와 비슷한 듯?
* CallTime : 새로운 Item을 불러와야 할 때 (= 사용자에게 보여지기 직전에)
*/
override fun onBindViewHolder(holder: Holder, position: Int) {
val data = dataList[position]
holder.bind(data)
}
}
Activity Code
class MyActivity : AppCompatActivity() {
private val binding: MyActivityBinding by lazy {
MyActivityBinding.inflate(layoutInflater)
}
// 1번 방법
private val myAdapterParameter: MyAdapter by lazy {
MyAdapter { view, position ->
eventProcess(view, position)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val myAdapterInterface = MyAdapter() { view, position ->
// myAdapterParameter 때문에 함수를 받도록 되어 있어 이렇게 초기화함
}
with(binding.rvMain) {
// adapter = myAdapterParameter // 둘 다 click event 처리 가능
adapter = myAdapterInterface // 둘 다 click event 처리 가능
layoutManager = LinearLayoutManager(this@MyActivity)
}
// 2가지 어댑터에 동일한 데이터 셋업
listOf(myAdapterParameter, myAdapterInterface).forEach {
it.dataList = dataLists // dataLists = 더미데이터 : List<MyItem>
}
with(myAdapterInterface) {
// 2번 방법
// object인데 MyAdapter의 ItemClickInterface를 구현한다.
// 당연히 onClickInterface() 함수 오버라이드함.
itemClickInterface = object : MyAdapter.ItemClickInterface {
override fun onClickInterface(view: View, position: Int) {
eventProcess(view, position)
}
}
}
}
fun eventProcess(view: View, position: Int) {
val name = dataLists[position].name
Toast.makeText(this@MyActivity, "$name 선택!", Toast.LENGTH_SHORT).show()
}
}
하루종일 RecyclerView와 씨름하다보니 그새 많이 친해진 것 같아 기쁘다.
까먹지 않도록 내일은 스탠다드반 4주차 과제인 RecyclerView를 활용한 Muti Item 구현을 시도해보자.
'Android' 카테고리의 다른 글
Retrofit2 (2) | 2024.04.15 |
---|---|
Fragment의 정의, 사용법 및 데이터 전달 방식 (1) | 2024.04.12 |
Adapter, AdapterView 및 ListView, GridView 정리 (0) | 2024.04.11 |
스탠다드 2주차 강의내용 정리 및 과제(LifeCycle) (0) | 2024.04.10 |
챌린지 3주차 과제 해설 리뷰 (0) | 2024.04.09 |