룬아님의 취중코딩

Recyclerview + GridLayout + databinding + Multi Item ViewType 본문

개발/안드로이드 개발

Recyclerview + GridLayout + databinding + Multi Item ViewType

룬아님 2019. 11. 22. 16:25

이 예제는 구글 블루프린트의 recyclerview adapter를 여러 타입의 아이템으로 사용할 수 있도록 수정한 예제입니다.

 

1. recyclerview 아이템 타입

enum class ItemType(val typeInt: Int) {
    NORMAL(0),
    SECTION(1)
}

data class AttendeesItem(val type: ItemType, val item: Any)

 

2. NoticeAttendance

enum class VoteStatus {
    @SerializedName("unselected")
    UNSELECTED,
    @SerializedName("attend")
    ATTEND,
    @SerializedName("absent")
    ABSENT,
    @SerializedName("late")
    LATE,
    NONE
}

data class NoticeAttendance(
    val pk: Int,
    val user: User,
    var vote: VoteStatus,
    val voteDisplay: String
)

 

3. bindingAdapter

@BindingAdapter("app:items")
fun setItems(listView: RecyclerView, items: List<AttendeesItem>) {
    (listView.adapter as AttendeesDialogAdapter).submitList(items)
}

 

4. attendees_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="item"
            type="com.mashup.model.NoticeAttendance" />
    </data>

    <LinearLayout
        android:background="@color/author"
        android:orientation="vertical"
        android:layout_width="100dp"
        android:layout_height="wrap_content">

        <TextView
            tools:text="dsdssdsdds"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{item.user.name}" />
    </LinearLayout>
</layout>

 

5. attendees_section_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="title"
            type="String" />
    </data>

    <LinearLayout
        android:background="@color/author"
        android:orientation="vertical"
        android:layout_width="300dp"
        android:layout_height="wrap_content">

        <TextView
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{title}" />
    </LinearLayout>
</layout>

 

6. adapter

class AttendeesDialogAdapter :
    ListAdapter<AttendeesItem, RecyclerView.ViewHolder>(TaskDiffCallback()) {

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = getItem(position)

        if (item.type == ItemType.NORMAL)
            (holder as ItemViewHolder).bind(item.item as NoticeAttendance)
        else
            (holder as SectionViewHolder).bind(item.item as String)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == ItemType.NORMAL.typeInt)
            ItemViewHolder.from(parent)
        else
            SectionViewHolder.from(parent)
    }

    override fun getItemViewType(position: Int): Int {
        return getItem(position).type.typeInt
    }

    class ItemViewHolder private constructor(val binding: AttendeesItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: NoticeAttendance) {
            binding.item = item
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): ItemViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = AttendeesItemBinding.inflate(layoutInflater, parent, false)

                return ItemViewHolder(binding)
            }
        }
    }

    class SectionViewHolder private constructor(val binding: AttendeesSectionItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(title: String) {
            binding.title = title
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): SectionViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = AttendeesSectionItemBinding.inflate(layoutInflater, parent, false)

                return SectionViewHolder(binding)
            }
        }
    }
}

/**
 * Callback for calculating the diff between two non-null items in a list.
 *
 * Used by ListAdapter to calculate the minimum number of changes between and old list and a new
 * list that's been passed to `submitList`.
 */
class TaskDiffCallback : DiffUtil.ItemCallback<AttendeesItem>() {
    override fun areItemsTheSame(oldItem: AttendeesItem, newItem: AttendeesItem): Boolean {
        return oldItem.type == newItem.type && oldItem.item == newItem.item
    }

    override fun areContentsTheSame(oldItem: AttendeesItem, newItem: AttendeesItem): Boolean {
        return false
    }
}

 

7. ViewModel

class AttendeesDialogViewModel(
    attendanceList: List<NoticeAttendance>
) : ViewModel() {
    private val _items = MutableLiveData<List<AttendeesItem>>().apply { value = emptyList() }
    val items: LiveData<List<AttendeesItem>> = _items

    init {
        val attendList = ArrayList<AttendeesItem>()
        val absentList = ArrayList<AttendeesItem>()
        val unselectedList = ArrayList<AttendeesItem>()

        attendanceList.forEach {
            when {
                it.vote == VoteStatus.ATTEND -> attendList.add(AttendeesItem(ItemType.NORMAL, it))
                it.vote == VoteStatus.ABSENT -> absentList.add(AttendeesItem(ItemType.NORMAL, it))
                else -> unselectedList.add(AttendeesItem(ItemType.NORMAL, it))
            }
        }
        val array = ArrayList<AttendeesItem>()
        array.add(AttendeesItem(ItemType.SECTION, "참석 할게요 : ${attendList.size}"))
        array.addAll(attendList)
        array.add(AttendeesItem(ItemType.SECTION, "결석 할게요 : ${absentList.size}"))
        array.addAll(absentList)
        array.add(AttendeesItem(ItemType.SECTION, "미선택 : ${unselectedList.size}"))
        array.addAll(unselectedList)

        _items.value = array
    }
}

 

8. fragment

class AttendeesDialogFragment(attendanceList: List<NoticeAttendance>) : DialogFragment() {

    companion object {
        fun newInstance(attendanceList: List<NoticeAttendance>) = AttendeesDialogFragment(attendanceList)
        const val TAG_ATTENDEES_DIALOG = "tag_attendees_dialog"
    }

    private val viewModel: AttendeesDialogViewModel by viewModel { parametersOf(attendanceList) }
    private lateinit var viewDataBinding: AttendeesDialogFragmentBinding
    private lateinit var listAdapter: AttendeesDialogAdapter

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewDataBinding = AttendeesDialogFragmentBinding.inflate(inflater, container, false).apply {
            viewmodel = viewModel
        }
        return viewDataBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewDataBinding.setLifecycleOwner(this.viewLifecycleOwner)
        setupListAdapter()
    }

    private fun setupListAdapter() {
        val viewModel = viewDataBinding.viewmodel
        if (viewModel != null) {
            val gridLayoutManager = GridLayoutManager(this.context, 3)
            gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return if (listAdapter.getItemViewType(position) == ItemType.SECTION.typeInt) 3 else 1
                }
            }
            viewDataBinding.attendanceList.layoutManager = gridLayoutManager
            listAdapter = AttendeesDialogAdapter()
            viewDataBinding.attendanceList.adapter = listAdapter
        }
    }
}
반응형
Comments