https://imgur.com/qIVcF7N // 결과 이미지
https://imgur.com/mInNyLu // 구현해야할 이미지
제목대로 중첩리사이클러뷰를 사용하고 있습니다.
버튼을 누르면 부모아이템을 추가할 수 있습니다.
부모아이템에는 다시 버튼이 달려있어 버튼을 누를때마다 서브아이템의 추가 및 삭제가 가능합니다.
원래는 기본 Adapter를 사용했다가 효율적인 아이템 업데이트를 위해
ListAdapter로 바꿨습니다.
MVVM 패턴을 사용하려고하기에 ViewModel을 사용하고 있고,
ViewModel 클래스 내에서 부모아이템이 추가되는 것을 관찰해서 실시간으로 화면에
업데이트 되도록 하였습니다.
결과는 반은 성공이고 반은 실패입니다. 부모아이템은 추가가 잘되나, 이후에 서브아이템은
즉각적으로 추가가 되지 않습니다. 다음 부모아이템이 추가되어야 반영되더군요.
어떻게 해야 서브아이템을 추가할때마다 바로바로 업데이트되게 할 수 있을까요?
Fragment
class WriteRoutineFragment : Fragment() {
private lateinit var adapter : RoutineListAdapter
private val vm : WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWriteRoutineBinding.inflate(inflater, container, false)
adapter = RoutineListAdapter(::addDetail, ::deleteDetail)
binding.rv.adapter = this.adapter
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 리사이클러뷰 업데이트
vm.items.observe(viewLifecycleOwner) { updatedItems ->
adapter.submitList(updatedItems)
}
}
// 서브아이템 추가 및 삭제
private fun addDetail(pos: Int) {
vm.addDetail(pos)
}
private fun deleteDetail(pos: Int) {
vm.deleteDetail(pos)
}
}
ViewModel
class WriteRoutineViewModel : ViewModel() {
private var _items: MutableLiveData<ArrayList<RoutineModel>> = MutableLiveData(arrayListOf())
var set: Int = 1
val items: LiveData<ArrayList<RoutineModel>> = _items
fun addRoutine(workout: String) { // 부모아이템 추가
val item = RoutineModel(workout, "TEST")
_items.value?.add(item)
}
fun addDetail(pos: Int) { // 서브아이템 추가
val detail = RoutineDetailModel(set.toString(), "TEST","33")
set += 1
_items.value?.get(pos)?.addSubItem(detail) // 부모아이템의 데이터만 변화시켜서는 관찰하지못함
}
fun deleteDetail(pos: Int) { // 서브아이템 삭제
if(_items.value?.get(pos)?.getSubItemSize()!! > 1) // Detail 아이템이 1개일경우에는 Routine을 삭제
_items.value?.get(pos)?.deleteSubItem()
else
_items.value?.removeAt(pos)
}
}
Listadapter
class RoutineListAdapter(val addDetailClicked: (Int) -> Unit,
val deleteDetailClicked: (Int) -> Unit) :
ListAdapter<RoutineModel, RecyclerView.ViewHolder>(RoutineDiffCallback()) {
private val viewPool = RecyclerView.RecycledViewPool()
companion object {
private const val TYPE_ROUTINE = 1
private const val TYPE_ROUTINE_FOOTER = 2
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
lateinit var itemView: View
return when (viewType) {
TYPE_ROUTINE -> {
val binding = ItemRoutineBinding.inflate(LayoutInflater.from(parent.context), parent, false)
RoutineViewHolder(binding) // return
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(getItemViewType(position)) {
TYPE_ROUTINE -> {
val item = getItem((position))
holder as RoutineViewHolder
holder.bind(item)
initDetailLayoutManager(holder, item, position)
}
}
}
override fun getItemViewType(position: Int): Int {
return when(position) {
currentList.size -> TYPE_ROUTINE_FOOTER
else -> TYPE_ROUTINE
}
}
override fun getItemCount(): Int = currentList.size + 1 // footer 때문에 +1
private fun initDetailLayoutManager(holder : RoutineViewHolder, item: RoutineModel, pos: Int) {
val lm = LinearLayoutManager( // Detail RV를 위한 lm
holder.subRV.context,
LinearLayoutManager.VERTICAL,
false
)
lm.initialPrefetchItemCount = item.getSubItemList().size
val detailAdapter = RoutineDetailListAdapter()
holder.subRV.apply {
layoutManager = lm
adapter = detailAdapter
detailAdapter.submitList(currentList[pos].getSubItemList()) // 서브아이템 업데이트
setRecycledViewPool(viewPool)
}
}
inner class RoutineViewHolder(private val binding: ItemRoutineBinding)
: RecyclerView.ViewHolder(binding.root) {
val subRV = binding.rv
init {
binding.add.setOnClickListener {
addDetailClicked(adapterPosition)
}
binding.delete.setOnClickListener {
deleteDetailClicked(adapterPosition)
}
}
}
}
부모 DIffUtil
class RoutineDiffCallback : DiffUtil.ItemCallback<RoutineModel>() {
override fun areItemsTheSame(
oldItem: RoutineModel,
newItem: RoutineModel
): Boolean = (oldItem.workout == newItem.workout)
override fun areContentsTheSame(
oldItem: RoutineModel,
newItem: RoutineModel
) : Boolean = oldItem.equals(newItem)
}
자식 DIffUtil
class RoutineDetailDiffCallback : DiffUtil.ItemCallback<RoutineDetailModel>() {
override fun areItemsTheSame(
oldItem: RoutineDetailModel,
newItem: RoutineDetailModel
): Boolean = (oldItem.set == newItem.set)
override fun areContentsTheSame(
oldItem: RoutineDetailModel,
newItem: RoutineDetailModel
): Boolean = oldItem.equals(newItem)
}
부모 모델
data class RoutineModel(
val workout: String, // 운동 종목
val unit: String, // 무게 단위 (kg or lbs)
var routineDetail: ArrayList<RoutineDetailModel> = arrayListOf()
) {
init {
// 루틴 생성시 최소 1개의 상세 아이템을 가지고 있게 하기 위함
val detail = RoutineDetailModel("1","test","33")
routineDetail.add(detail)
}
fun getSubItemList() : ArrayList<RoutineDetailModel> = routineDetail
fun getSubItemSize() = routineDetail.size
fun addSubItem(item: RoutineDetailModel) {
routineDetail.add(item)
}
fun deleteSubItem() {
routineDetail.removeLast()
}
}