Problem
昨天我们提到ListAdapter + DiffUtil
在一般RecyclerView
的基本使用。
而实际上工作中我们经常会需要在RecyclerView
上显示不同的itemView
,
我们会添加Footer
、Header
,或是不同样式的itemView
,
这时候该怎麽办呢?
Solution
在submitList
之前,把dataList
中的item
顺序重新编排後,
再使用submitList
提交我们的dataList
做更新。
实作
假设我们要做一个像Line一样的聊天室,
而且底部要添加一个名为“已滑至底”的TextView
做为Footer
。
资料的部分会用Message
这个data class
来显示讯息,
并根据isFromMe
来判断是否为自己发送的讯息,展示不同的`itemView
data class Message(
val id: Long,
val timeStamp: Long,
val isFromMe: Boolean,
val message: String
)
首先,因为会展示不同的项目,我们不喂ListAdapter
吃data class
了,
改吃自己创建的sealed class
- 这边我们命名为DataItem
。
class MessageAdapter() : ListAdapter<DataItem, RecyclerView.ViewHolder>(DiffCallback()) {}
我们添加一个sealed class
,
这个sealed class
负责用来控管不同item的型态类别。
这边我们列举出Item
和Footer
这两个型态(data type)。
因为DiffUtil
需要一个参数作为判断新旧item是否一样的依据。
所以我们创建一个abstract item id
作为interface回传判别用的数据。
isFromMe
则是用来判断显示讯息在左侧还是右侧的item view
。
sealed class DataItem {
abstract val id: Long
abstract val isFromMe: Boolean
data class Item(val message: Message) : DataItem() {
override val id = message.id
override val isFromMe = message.isFromMe
}
object Footer : DataItem() {
override val id = Long.MIN_VALUE
override val isFromMe = false
}
}
(关於sealed class
後续文章有机会会讲解,或是你也可以看这篇写得很详尽)
这边因为Footer
只是作为静态显示layout
因此只使用object
而非data class
。
DiffUtil也改为判断DataItem
中的id
class DiffCallback : DiffUtil.ItemCallback<DataItem>() {
override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem == newItem
}
}
创建一个enum class列举需展示的所有viewType
,
enum class ItemViewType {
MESSAGE_FROM_ME, MESSAGE_TO_ME, FOOTER
}
这边列举viewType
,是给Adapter判断要展示哪个ViewHolder
来使用的。
因为我们有不同的型别需判断,
所以我们要覆写getItemViewType
。
键盘按下control
+o
,选择getItemViewType
并覆写他。
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is DataItem.FromMe -> ItemViewType.MESSAGE_FROM_ME.ordinal
is DataItem.ToMe -> ItemViewType.MESSAGE_TO_ME.ordinal
is DataItem.Footer -> ItemViewType.FOOTER.ordinal
}
}
这边返回的int是onCreateViewHolder
会用到的viewType
,继续往下看下去。
onCreateViewHolder
与onBindViewHolder
判断 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ItemViewType.MESSAGE_FROM_ME.ordinal -> FromMeViewHolder.from(parent)
ItemViewType.MESSAGE_TO_ME.ordinal -> ToMeViewHolder.from(parent)
else -> FooterViewHolder.from(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is FromMeViewHolder -> {
val data = getItem(position) as DataItem.Item
holder.bind(data.message)
}
is ToMeViewHolder -> {
val data = getItem(position) as DataItem.Item
holder.bind(data.message)
}
is FooterViewHolder -> {
}
}
}
ViewHolder
为FromMe
、ToMe
及Footer
创建对应的ViewHolder
class FromMeViewHolder private constructor(val binding: ItemAccountHistoryNextContentBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(message: Message) {
itemView.apply {
tv_message_from_me.text = message.content
}
}
companion object {
fun from(parent: ViewGroup): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemview_message_from_me, parent, false)
return ToMeViewHolder(view)
}
}
}
class ToMeViewHolder (view: View) : RecyclerView.ViewHolder(view) {
fun bind(message: Message) {
itemView.apply {
tv_message_to_me.text = message.content
}
}
companion object {
fun from(parent: ViewGroup): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemview_message_to_me, parent, false)
return ToMeViewHolder(view)
}
}
}
class FooterViewHolder (view: View) : RecyclerView.ViewHolder(view) {
companion object {
fun from(parent: ViewGroup): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemview_footer, parent, false)
return FooterViewHolder(view)
}
}
}
最後一步了!
现在因为ListAdapter
吃的是DataItem,
我们只要创建一个function - addFooterAndSubmitList
来取代原本的submitList
,
利用DataItem
整理过後再交给submitList
处理就大功告成。
private val adapterScope = CoroutineScope(Dispatchers.Default)
fun addFooterAndSubmitList(list: List<Message>) {
adapterScope.launch {
val items = list.map {
if (it.isFromMe) DataItem.FromMe(it)
else DataItem.ToMe(it)
} + listOf(DataItem.Footer)
withContext(Dispatchers.Main) { //update in main ui thread
submitList(items)
}
}
}
只要透过呼叫addFooterAndSubmitList
就能让ListAdapter
成功运作,
让DiffUtil
自动去筛选判断更新的内容。
rvAdapter.addFooterAndSubmitList(dataList)
data class Message(
val id: Long,
val timeStamp: Long,
val isFromMe: Boolean,
val content: String
)
class MessageAdapter() : ListAdapter<DataItem, RecyclerView.ViewHolder>(DiffCallback()) {
enum class ItemViewType {
MESSAGE_FROM_ME, MESSAGE_TO_ME, FOOTER
}
private val adapterScope = CoroutineScope(Dispatchers.Default)
fun addFooterAndSubmitList(list: List<Message>) {
adapterScope.launch {
val items = list.map {
if (it.isFromMe) DataItem.FromMe(it)
else DataItem.ToMe(it)
} + listOf(DataItem.Footer)
withContext(Dispatchers.Main) { //update in main ui thread
submitList(items)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ItemViewType.MESSAGE_FROM_ME.ordinal -> FromMeViewHolder.from(parent)
ItemViewType.MESSAGE_TO_ME.ordinal -> ToMeViewHolder.from(parent)
else -> FooterViewHolder.from(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is FromMeViewHolder -> {
val data = getItem(position) as DataItem.FromMe
holder.bind(data.message)
}
is ToMeViewHolder -> {
val data = getItem(position) as DataItem.ToMe
holder.bind(data.message)
}
is FooterViewHolder -> {
}
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is DataItem.FromMe -> ItemViewType.MESSAGE_FROM_ME.ordinal
is DataItem.ToMe -> ItemViewType.MESSAGE_TO_ME.ordinal
is DataItem.Footer -> ItemViewType.FOOTER.ordinal
}
}
class FromMeViewHolder private constructor(val binding: ItemAccountHistoryNextContentBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(message: Message) {
itemView.apply {
tv_message_from_me.text = message.content
}
}
companion object {
fun from(parent: ViewGroup): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemview_message_from_me, parent, false)
return ToMeViewHolder(view)
}
}
}
class ToMeViewHolder (view: View) : RecyclerView.ViewHolder(view) {
fun bind(message: Message) {
itemView.apply {
tv_message_to_me.text = message.content
}
}
companion object {
fun from(parent: ViewGroup): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemview_message_to_me, parent, false)
return ToMeViewHolder(view)
}
}
}
class FooterViewHolder (view: View) : RecyclerView.ViewHolder(view) {
companion object {
fun from(parent: ViewGroup): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.itemview_footer, parent, false)
return FooterViewHolder(view)
}
}
}
class DiffCallback : DiffUtil.ItemCallback<DataItem>() {
override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem == newItem
}
}
}
sealed class DataItem {
abstract val id: Long
abstract val isFromMe: Boolean
data class FromMe(val message: Message) : DataItem() {
override val id = message.id
override val isFromMe = message.isFromMe
}
data class ToMe(val message: Message) : DataItem() {
override val id = message.id
override val isFromMe = message.isFromMe
}
object Footer : DataItem() {
override val id = Long.MIN_VALUE
override val isFromMe = false
}
}
<<: Day-7 带着童年的好朋友任天堂红白机、重新在 HDMI 电视上发光吧!
>>: [Day8] 从入门到入狱! 用Python窃听电脑键盘事件!
我们每新增一个函式,浏览器都会向函式内新增一个属性叫 prototype function Per...
按照之前的进度制作,现在按下▶Player应该会魔性地扭动起来,但就没有其他效果了,接下来就改造Pl...
本次将延续前一章节的教学 点选Cube Animation往CubeAttack Animation...
我们写的脚本不仅仅是自己使用,有时需要分享给别人使用。这种情况下,帮助信息可以更好地帮助使用者,使用...
请问各位电脑高手 我现在把我的笔电跟HPE服务器的IP网段都已经设定成一样的 但是开启网页输入还是无...