일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- ViewModel
- 백준
- Coroutine
- hilt
- Algorithm
- recyclerview
- CoordinatorLayout
- CustomView
- HTTP
- View
- 안드로이드
- BOJ
- room
- lifecycle
- AppBarLayout
- onMeasure
- sqlite
- activity
- LiveData
- CollapsingToolbarLayout
- Behavior
- 코틀린
- notification
- 알고리즘
- DataBinding
- Android
- kotlin
- Navigation
- 알림
- onLayout
- Today
- Total
개발일지
Android in A..Z - RecyclerView (payload) 본문
RecyclerView에서 Item변경
RecyclerView에서 Item변경은 Adapter에 notifyItem~ 함수를 호출하면, onBindViewHolder가 호출되어 Item을 변경한다. 하지만 이러한 구조는 특졍 조건을 주어 특정한 부분만 변경하기에는 어렵다
=> 클릭을 했을 때 아이템 상태에 따른 에니메이션주기, View 변화주기 등
Payload
이러한 구조적 문제를 해결하기 위해 notifyItem~ 함수를 호출할 때 payload를 전달하여 특정 조건을 설정해 줄 수 있다.
Adapter에서는 onBindViewHolder(holder, position, payloads)를 구현하여 payload에 따른 특정 부분을 변화시킬 수 있다.
BaseHolder
abstract class BaseHolder<VB: ViewDataBinding, E: Any>(protected val binding: VB) : RecyclerView.ViewHolder(binding.root) {
val context: Context
get() { return itemView.context }
lateinit var element: E
open fun bind(element: E) {
this.element = element
}
open fun bind(element: E, payload: MutableList<Any>) {
this.element = element
}
}
ToDoHolder
inner class ToDoHolder(binding: HolderTodoBinding) : BaseHolder<HolderTodoBinding, ToDo>(binding) {
private var isFinished by Delegates.observable(false) { _, _, newValue ->
if (newValue) {
with(binding.isFinished) {
animate()
.withStartAction {
visibility = View.VISIBLE
}
.alpha(1F)
.scaleX(1F)
.scaleY(1F)
.setDuration(500)
.setInterpolator(OvershootInterpolator())
}
} else {
with(binding.isFinished) {
animate()
.alpha(0F)
.scaleX(0F)
.scaleY(0F)
.setDuration(500)
.withEndAction {
visibility = View.GONE
}
}
}
}
init {
itemView.setOnClickListener {
this@ToDoAdapter.notifyItemChanged(adapterPosition, "onClick")
}
}
private fun onClick() {
isFinished = !isFinished
}
private fun onRefresh() {
isFinished = false
}
override fun bind(element: ToDo) {
Log.d("PASS", "bind $element")
super.bind(element)
binding.todo = element
}
override fun bind(element: ToDo, payload: MutableList<Any>) {
Log.d("PASS", "bind with payload $element, $payload")
super.bind(element, payload)
for (any in payload) {
when(any) {
"onClick" -> {
onClick()
}
"onRefresh" -> {
onRefresh()
}
}
}
}
}
BaseAdapter
abstract class BaseAdapter<E: Any>(diffCallback: DiffUtil.ItemCallback<E>) : ListAdapter<E, BaseHolder<out ViewDataBinding, E>>(diffCallback) {
override fun onBindViewHolder(holder: BaseHolder<out ViewDataBinding, E>, position: Int) {
holder.bind(getItem(position))
}
override fun onBindViewHolder(holder: BaseHolder<out ViewDataBinding, E>, position: Int, payloads: MutableList<Any>) {
super.onBindViewHolder(holder, position, payloads)
holder.bind(getItem(position), payloads)
}
}
ToDoAdapter
class ToDoAdapter : BaseAdapter<ToDo>(ToDoItemCallback()) {
init {
setHasStableIds(true)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseHolder<out ViewDataBinding, ToDo> {
return ToDoHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.context), viewType, parent, false))
}
override fun getItemId(position: Int): Long {
return getItem(position).id
}
override fun getItemViewType(position: Int): Int {
return R.layout.holder_todo
}
inner class ToDoHolder(binding: HolderTodoBinding) : BaseHolder<HolderTodoBinding, ToDo>(binding) {
private var isFinished by Delegates.observable(false) { _, _, newValue ->
if (newValue) {
with(binding.isFinished) {
animate()
.withStartAction {
visibility = View.VISIBLE
}
.alpha(1F)
.scaleX(1F)
.scaleY(1F)
.setDuration(500)
.setInterpolator(OvershootInterpolator())
}
} else {
with(binding.isFinished) {
animate()
.alpha(0F)
.scaleX(0F)
.scaleY(0F)
.setDuration(500)
.withEndAction {
visibility = View.GONE
}
}
}
}
init {
itemView.setOnClickListener {
this@ToDoAdapter.notifyItemChanged(adapterPosition, "onClick")
}
}
private fun onClick() {
isFinished = !isFinished
}
private fun onRefresh() {
isFinished = false
}
override fun bind(element: ToDo) {
Log.d("PASS", "bind $element")
super.bind(element)
binding.todo = element
}
override fun bind(element: ToDo, payload: MutableList<Any>) {
Log.d("PASS", "bind with payload $element, $payload")
super.bind(element, payload)
for (any in payload) {
when(any) {
"onClick" -> {
onClick()
}
"onRefresh" -> {
onRefresh()
}
}
}
}
}
private class ToDoItemCallback : DiffUtil.ItemCallback<ToDo>() {
override fun areItemsTheSame(oldItem: ToDo, newItem: ToDo): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ToDo, newItem: ToDo): Boolean {
return oldItem.text == newItem.text
}
}
}
ToDoFragment
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
R.id.refresh -> {
adapter.notifyItemRangeChanged(0, adapter.itemCount, "onRefresh")
}
}
return super.onOptionsItemSelected(item)
}
Payload가 Any(Object)인 이유
Payload를 Any로 받으면 개발자 입장에서 어떠한 자료형이든 매개 변수로 넘길 수 있기 때문에 제약사항이 없이 쉽게 개발할 수 있다.
=> payload를 String, Int, Long등 다양한 자료형을 넘길 수 있으므로 payload에 따른 다양한 조건을 만들 수 있음.
Payload를 Adapter에서 MutableList로 받는 이유
onBindViewHolder를 호출하기 전에 여러가지의 payload를 받을 경우 list로 전달하여 한번에 처리하면서 불필요한 리소스를 아낄 수 있다.
Payload를 넘길 때 onBindViewHolder 호출 순서
onBindViewHolder(holder, position) -> onBindViewHolder(holder, position, payloads)
=> 기본적인 bind는 onBindViewHolder(holder, position)에 구현하고 특정 부분을 bind하는 부분을 onBindViewHolder(holder, position, payloads)에 구현하면 중복 호출을 막을 수 있다.
Git (예제코드)
github.com/KangTaeJong98/Example/tree/main/Android/RecyclerView
'Android (안드로이드) > RecyclerView' 카테고리의 다른 글
Android in A..Z - RecyclerView (Selection - Predicate) (0) | 2021.01.15 |
---|---|
Android in A..Z - RecyclerView (Selection Tracker) (0) | 2021.01.15 |
Android in A..Z - RecyclerView (setHasFixedSize) (1) | 2021.01.10 |
Android in A..Z - RecyclerView (setHasStableIds) (0) | 2021.01.10 |
Android in A..Z - RecyclerView (ItemDecoration) (0) | 2021.01.10 |