当用户滚动时如何显示或隐藏日期文本视图就像whatsapp聊天android Kotlin

Posted

技术标签:

【中文标题】当用户滚动时如何显示或隐藏日期文本视图就像whatsapp聊天android Kotlin【英文标题】:How to show or hide date textview when user scroll just like whatsapp chat android Kotlin 【发布时间】:2021-11-29 18:58:58 【问题描述】:

嘿,我正在开发聊天应用程序。我完成了来自How to use different layouts for incoming & outgoing chat messages 的传入和传出消息。我也试过这个How to show date in between conversation in recyclerview or in listview。我添加了输出我的所有外观。

ConversationAdapter.kt

class ConversationAdapter : ListAdapter<Message, RecyclerView.ViewHolder>(MESSAGE_COMPARATOR) 

    companion object 
        private val MESSAGE_COMPARATOR = object : DiffUtil.ItemCallback<Message>() 
            override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean 
                return oldItem.id == newItem.id
            

            override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean 
                return ((oldItem.name == newItem.name) && (oldItem.text == oldItem.text)
                        && (oldItem.time == newItem.time) && (oldItem.type == newItem.type))
            

        

        private const val INCOMING_MESSAGE = 1
        private const val OUTGOING_MESSAGE = 2
        private const val TIMING = 3
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder 
        return when (viewType) 
            INCOMING_MESSAGE -> 
                IncomingViewHolder(
                    IncomingLayoutBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            
            OUTGOING_MESSAGE -> 
                OutGoingViewHolder(
                    OutgoingLayoutBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            
            else -> 
                TimingViewHolder(
                    TimingLayoutBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            
        
    

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) 
        when (holder) 
            is IncomingViewHolder -> 
                holder.bindItem(getItem(position))
            
            is OutGoingViewHolder -> 
                holder.bindItem(getItem(position))
            
            is TimingViewHolder -> 
                holder.bindItem(getItem(position))
            
        
    

    override fun getItemViewType(position: Int): Int 
        return when 
            getItem(position).type.equals("incoming") -> 
                INCOMING_MESSAGE
            
            getItem(position).type.equals("outgoing") -> 
                OUTGOING_MESSAGE
            
            getItem(position).type.equals("added") -> 
                TIMING
            
            else -> 
                super.getItemViewType(position)
            
        
    


    inner class IncomingViewHolder(val binding: IncomingLayoutBinding) : RecyclerView.ViewHolder(binding.root) 
        fun bindItem(item: Message) 
            binding.incomingMessage.text = item.text
        
    

    inner class OutGoingViewHolder(val binding: OutgoingLayoutBinding) : RecyclerView.ViewHolder(binding.root) 
        fun bindItem(item: Message) 
            binding.outGoingMessage.text = item.text
        
    

    inner class TimingViewHolder(val binding: TimingLayoutBinding) : RecyclerView.ViewHolder(binding.root) 
        fun bindItem(item: Message) 
            binding.timing.text = item.time
        
    

MM.kt

data class Message(
    val id: String? = null,
    val text: String? = null,
    val time: String? = null,
    val type: String? = null,
    val name: String? = null
)

MainActivity.kt

class MainActivity : BaseActivity() 
    
        lateinit var binding: MainLayoutBinding
    
        override fun onCreate(savedInstanceState: Bundle?) 
            super.onCreate(savedInstanceState)
            binding = MainLayoutBinding.inflate(layoutInflater)
            setContentView(binding.root)
            setupAdapter()
        
    
        private fun setupAdapter() 
     val list = listOf<Message>(
            Message(id = "1", text = "avc", time = "2021-10-08T15:34:00", type = "outgoing"),
            Message(id = "2", text = "dsds", time = "2021-10-08T15:36:00", type = "incoming"),
            Message(id = "3", type = "added", time = "2021-10-08T00:00:00", name = "ABC"),
            Message(id = "4", type = "added", time = "2021-10-07T15:50:00", name = "XYZ"),
            Message(id = "5", text = "asvc", time = "2021-10-07T15:46:00", type = "outgoing"),
            Message(id = "6", text = "asvc", time = "2021-10-07T15:46:00", type = "incoming"),
            Message(id = "6", text = "asvc", time = "2021-10-06T12:34:00", type = "outgoing"),
            Message(id = "8", text = "asvc", time = "2021-10-06T12:50:00", type = "incoming"),
            Message(id = "9", type = "added", time = "2021-10-06T12:46:00", name = "DEF")
        )
        val adapter = ConversationAdapter()
        binding.conversationRecyclerView.adapter = adapter
        adapter.submitList(list)
    
        
    

timing_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_
    android:background="@color/aqua"
    android:layout_>

    <TextView
        android:id="@+id/timing"
        android:layout_
        android:layout_
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

incoming_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_
    android:background="@color/red"
    android:layout_>

    <TextView
        android:id="@+id/incomingMessage"
        android:layout_
        android:layout_
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

outgoing_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_
    android:background="@color/yellow"
    android:layout_>

    <TextView
        android:id="@+id/outGoingMessage"
        android:layout_
        android:layout_
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

输出

如何实现其他功能。

1.由于我的列表太大,如何对我的日期进行分类以有效地显示每天对话之间的日期。我刚刚添加了示例

2. 用户滚动聊天时如何显示日期的滚动效果,就像whatsapp的昨天,今天和其他日期格式一样。

3. 在上面的列表示例中,有一个属性added,这意味着有人在群聊中添加了。在whatsapp XYZ 使用群组邀请链接加入。

我正在添加whatsapp的屏幕截图

【问题讨论】:

您可以使用getItemViewType。 Example in java @Kaushik 我已经使用 getitemViewType 但它不起作用。请参阅我更新的问题。谢谢 【参考方案1】:

即使不清楚“这在我的解决方案中不起作用”是什么意思 而且我不知道问题是崩溃还是空列表还是...但是 在方法areItemsTheSameareContentsTheSame 中检查项目的逻辑是错误的: 方法areItemsTheSame 用于检查旧项目的身份是否等于新项目的身份。 并使用areContentsTheSame方法来检查两个项目的值是否相等

所以在areContentsTheSame 中,您只需检查id 的项目,因为其他内容是项目的值,而您在areContentsTheSame 中编写的内容没有任何意义,因为您只是在检查项目的参考。所以把它改成这样:

   private val MESSAGE_COMPARATOR = object : DiffUtil.ItemCallback<MM>() 
        override fun areItemsTheSame(oldItem: MM, newItem: MM): Boolean 
            return oldItem.id == newItem.id
        

        override fun areContentsTheSame(oldItem: MM, newItem: MM): Boolean 
   return ((oldItem.added == newItem.added)
                    && (oldItem.addedTime == newItem.addedTime)
                    && (oldItem.sentAt == newItem.sentAt)
                    && (oldItem.text == newItem.text))            

    

【讨论】:

感谢您回复我。我用完整的解释更新了我的问题。请再看看【参考方案2】:

TimingViewHolder的持有者在哪里?

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) 
        if (holder is IncomingViewHolder) 
            holder.bindItem(getItem(position))
         else if (holder is OutGoingViewHolder) 
            holder.bindItem(getItem(position))
        
    

[更新]

由于我的列表太大,如何对我的日期进行分类以显示每天对话之间的日期效率低下。

您想制作这样的对话界面吗?

如果是,你可以试试这个方法,为了解释清楚,我需要修改你的代码。

data class MessageEntry // 它来自服务器,是原始数据

数据类Message // 来自MessageEntry

data class MessageEntry(
    val id: String? = null,
    val text: String? = null,
    val time: String? = null,
    val type: String? = null,
    val name: String? = null
)

确保 Message 类中没有可为空的字段

data class Message(
    val id: String,
    val text: String,
    val time: Date, // SDK's class
    val type: String, // It's better to use enum
    val name: String
)
//Raw data from Server
val mutableRawList = mutableListOf<MessageEntry>(
            MessageEntry(id = "1", text = "avc", time = "2021-10-08T15:34:00", type = "outgoing"),
            MessageEntry(id = "2", text = "dsds", time = "2021-10-08T15:36:00", type = "incoming"),
            MessageEntry(id = "3", type = "added", time = "2021-10-08T00:00:00", name = "ABC"),
            MessageEntry(id = "4", type = "added", time = "2021-10-07T15:50:00", name = "XYZ"),
            MessageEntry(id = "5", text = "asvc", time = "2021-10-07T15:46:00", type = "outgoing"),
            MessageEntry(id = "6", text = "asvc", time = "2021-10-07T15:46:00", type = "incoming"),
            MessageEntry(id = "6", text = "asvc", time = "2021-10-06T12:34:00", type = "outgoing"),
            MessageEntry(id = "8", text = "asvc", time = "2021-10-06T12:50:00", type = "incoming"),
            MessageEntry(id = "9", type = "added", time = "2021-10-06T12:46:00", name = "DEF")
        )

//I ignore the changing, MessageEntry.toMessage(), for an instance.
val mutableMessageList = mutableListOf<Message>(
            Message(id = "1", text = "avc", time = Date(2021-10-08T15:34:00), type = "outgoing"),
            Message(id = "2", text = "dsds", time = Date(2021-10-08T15:36:00), type = "incoming"),
            Message(id = "3", type = "added", time = Date(2021-10-08T00:00:00), name = "ABC"),
            Message(id = "4", type = "added", time = Date(2021-10-07T15:50:00), name = "XYZ"),
            Message(id = "5", text = "asvc", time = Date(2021-10-07T15:46:00), type = "outgoing"),
            Message(id = "6", text = "asvc", time = Date(2021-10-07T15:46:00), type = "incoming"),
            Message(id = "6", text = "asvc", time = Date(2021-10-06T12:34:00), type = "outgoing"),
            Message(id = "8", text = "asvc", time = Date(2021-10-06T12:50:00), type = "incoming"),
            Message(id = "9", type = "added", time = Date(2021-10-06T12:46:00), name = "DEF")
        )


private var lastMessageDate: Date? = null
...
val adapter = ConversationAdapter()
binding.conversationRecyclerView.adapter = adapter

// Need to change raw list MessageEntry to Message
// TODO, maybe you should change the related code of xxxAdapter, xxxViewHolder...

adapter.submitList(mutableMessageList.toFlatMessageItems)

toFlatMessageItems()的方法是什么

//Rebuild the message list from TreeMap<Date, List<Message>>
private fun MutableList<Message>.toFlatMessageItems() =
        mutableListOf<Message>().apply 
            val messageByDate = this@toFlatMessageItems.splitByDate()
            for (time in messageByDate.keys) 
                if (time != lastMessageDate) 
                    val dateHeaderItem = time.toMessage()
                    add(dateHeaderItem)                   
                
                for (message in messageByDate.getValue(date)) 
                    val messageItem = message
                    add(messageItem)
                
            
            lastMessageDate = messageByDate.keys.lastOrNull()
        

splitByDate的方法是什么:

//Split the data by Date
private fun List<Message>.splitByDate() =
        TreeMap<Date, List<Message>>(Collections.reverseOrder()).apply 
            for (message in this@splitByDate) 
                val date = Message.time.noTime() // need you implement noTime() extension function, only the (year, month, day)
                var value = this[date] as MutableList<Message>?
                if (value == null) 
                    value = mutableListOf()
                    this[date] = value
                

                value.add(message)
            
        

Kotlin 扩展函数

    将 MessageEntry 更改为 Message
MessageEntry.toMessage() = Message(
    id = id ?: "", // or add an extension fuction to set empty string
    text = text ?: "",
    time = time.toDate(), // extension function from time string to Date, for spliting date
    type = type ?: "",
    name = name ?: ""
)
    时间(字符串或长)到日期
String/Long.toDate(): Date 
 //TODO change string/long time format to Date

    日期.noTime()
Date.toDate(): Date 
 //TODO, only year, month, day

    Date.toMessage()
Date.toMessage() = Message(
    id = "",
    text = "",
    time = time, // the type of time is Date
    type = "added",
    name = ""
)

注意:

    我没有测试,只是给你一个想法。 使用 MutableList 或 List,由您决定。 有些扩展功能需要你完成。

【讨论】:

感谢您回复我。我用完整的解释更新了我的问题。请再看看 更新代码和输出后,我认为您已经解决了您的问题。 UI 显示消息列表是什么。 我想对我的日期进行分类。如何做到这一点? @vivekmodi,我更新了我的答案,请检查一下。 很好的答案,它对我有很大帮助。你能帮我解决这个当用户滚动聊天时如何显示日期的滚动效果,就像whatsapp有昨天、今天和其他日期格式一样。

以上是关于当用户滚动时如何显示或隐藏日期文本视图就像whatsapp聊天android Kotlin的主要内容,如果未能解决你的问题,请参考以下文章

当softKeyBoard隐藏按钮和其他元素时如何使布局可滚动[ANDROID]

Swift:仅当键盘隐藏 TextField 或 Button 时滚动视图

如何在 UIView 中设置滚动视图以向上滑动被键盘隐藏的文本字段?

当用户快速滚动表格视图时如何隐藏和取消隐藏导航栏?

Swift:如何防止未显示的 UITableViewCells 在滚动时被关闭

如何在移动时调整 UIView 的大小,就像 SnapGuide 一样?