希望 recyclerview 在创建活动时从下到上开始,并在添加新项目时再次在底部滚动

问题描述 投票:0回答:1

我的 Android 应用程序中有一个名为 ChatActivity 的活动。这是 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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:windowSoftInputMode="adjustResize"
    tools:context=".ui.home.ui.chat.ChatActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="?attr/homeAsUpIndicator"
        app:navigationIconTint="@color/black"
        app:title="@string/chat"
        app:titleCentered="true" >
        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/ivProfile"
            android:layout_width="@dimen/_30sdp"
            android:layout_height="@dimen/_30sdp"
            android:src="@drawable/ic_dummy_user"
            android:layout_marginHorizontal="@dimen/_10sdp"
            android:layout_gravity="end"
            />
    </com.google.android.material.appbar.MaterialToolbar>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvMessages"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/toolbar"
        android:paddingBottom="@dimen/_95sdp"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintVertical_bias="1.0"
        tools:listitem="@layout/item_message_sent" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/tiMessage"
        style="@style/textInputLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:maxHeight="@dimen/_200sdp"
        android:layout_marginLeft="@dimen/_5sdp"
        android:layout_marginRight="@dimen/_35sdp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        app:errorEnabled="false"
        android:layout_marginVertical="@dimen/_5sdp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        >

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxHeight="@dimen/_200sdp"
            android:autofillHints="name"
            android:hint="@string/message"
            android:inputType="textMultiLine" />
    </com.google.android.material.textfield.TextInputLayout>
    <ImageView
        android:id="@+id/ivSend"
        android:layout_width="@dimen/_25sdp"
        android:layout_height="@dimen/_25sdp"
        android:src="@drawable/ic_send_message"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="@dimen/_12sdp"
        app:layout_constraintLeft_toRightOf="@id/tiMessage"
        app:tint="@color/blue" />

</androidx.constraintlayout.widget.ConstraintLayout>

当我打开 textInput 字段时,布局不适应它,它仅显示要打开的键盘,但是当我在键盘中键入时,它确实会在文本输入布局中键入,但在键入时用户无法看到文本输入布局。关闭键盘

+

回收视图开始从上到下滚动,但我希望它像聊天一样,从下到上。在添加的新消息上,它确实滚动到底部,但在现有列表的最后一项上,而不是在新项目上。 我尝试过使用

android:windowSoftInputMode="adjustResize"
作为键盘和

private fun scrollToBottom() {
        binding.rvMessages.scrollToPosition(adapter.itemCount -1)
    }

用于回收视图。

欢迎任何优化代码/改进的建议。我的活动代码是:

@AndroidEntryPoint
class ChatActivity : AppCompatActivity() {

    private lateinit var binding: ActivityChatBinding
    private lateinit var database: DatabaseReference
    private lateinit var adapter: ChatAdapter
    private var currentUserId: Int = 0
    private var otherUserId: Int = 0
    private lateinit var conversationId: String
    private var convoId = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityChatBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        binding.toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }

        val userDetails = intent.getParcelableExtra<ConnectionDetailDTO>("USER_DETAILS")
        val currentUser = Paper.book().read<User>(Constant.k_user)

        currentUserId = currentUser?.id!!
        otherUserId = userDetails?.id!!

        binding.toolbar.title = userDetails.name
        Glide.with(this).load(userDetails.profileUrl).placeholder(R.drawable.ic_dummy_user).into(binding.ivProfile)
        binding.ivProfile.setOnClickListener{showProfile()}

        adapter = ChatAdapter(currentUserId)
        binding.rvMessages.layoutManager = LinearLayoutManager(this)
        binding.rvMessages.adapter = adapter
        scrollToBottom()

        val database = FirebaseDatabase.getInstance()
        val reference = database.getReference("Conversations")

        reference.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val messages = mutableListOf<Message>()
                for (conversationSnapshot in dataSnapshot.children) {
                    val conversationIdParts = conversationSnapshot.key?.split("_")
                    if (conversationIdParts?.size == 3) {
                        val firstUserId = conversationIdParts[1]
                        val secondUserId = conversationIdParts[2]
                        convoId = "conversation_${firstUserId}_${secondUserId}"

                        if (firstUserId == currentUserId.toString() && secondUserId == otherUserId.toString()
                            || firstUserId == otherUserId.toString() && secondUserId == currentUserId.toString()) {
                            val conversationMessages = mutableListOf<Message>()
                            for (messageSnapshot in conversationSnapshot.child("messages").children) {
                                val content = messageSnapshot.child("content").value.toString()
                                val date = messageSnapshot.child("date").value.toString()
                                val messageId = messageSnapshot?.child("id")?.value.toString()
                                val isRead = messageSnapshot.child("isRead").value as? Boolean ?: false
                                val name = messageSnapshot.child("name").value.toString()
                                val senderEmail = messageSnapshot.child("senderEmail").value.toString()
                                val type = messageSnapshot.child("type").value.toString()

                                val message = Message(content, date, messageId, isRead, name, senderEmail, type)
                                conversationMessages.add(message)
                                Log.d(TAG, "onDataChange: $message")
                            }
                            messages.addAll(conversationMessages)
                        }
                    }
                }
                Log.d(TAG, "onDataChange: $messages")
                adapter.updateMessages(messages)
            }

            override fun onCancelled(databaseError: DatabaseError) {
                println("Error: ${databaseError.message}")
            }
        })
        binding.ivSend.setOnClickListener {
            val messageText = binding.tiMessage.editText?.text.toString().trim()

            if (messageText.isNotEmpty()) {
                val conversationId1 = "conversation_${currentUserId}_${otherUserId}"
                val conversationId2 = "conversation_${otherUserId}_${currentUserId}"
                reference.child(conversationId1).get().addOnCompleteListener { task ->
                    if (task.isSuccessful) {
                        val data = task.result?.value
                        val conversationId = if (data != null) {
                            conversationId1
                        } else {
                            conversationId2
                        }
                        val messageReference =
                            reference.child(conversationId).child("messages").push()
                        val message = Message(
                            content = messageText,
                            date = SimpleDateFormat(
                                "yyyy-MM-dd HH:mm:ss",
                                Locale.getDefault()
                            ).format(Date()),
                            id = messageReference.key!!,
                            isRead = false,
                            name = currentUser?.name ?: "",
                            senderEmail = currentUserId.toString(),
                            type = "text"
                        )
                        messageReference.setValue(message)
                        binding.tiMessage.editText?.setText("")
                        scrollToBottom()
                    }
                }

                addUser(currentUserId.toString())
            }
        }
}

    private fun scrollToBottom() {
        binding.rvMessages.scrollToPosition(adapter.itemCount -1)
    }
}

我尝试了 StackOverFLow 和 Chat GPT 的回收器视图解决方案

android xml kotlin android-recyclerview mobile-development
1个回答
0
投票

回收视图开始从上到下滚动,但我希望它是 就像聊天一样,从下到上。添加新消息后,它会滚动到 底部,但在现有列表的最后一项上,而不是在新项目上。 我尝试过使用..

recyclerView 从顶部开始并且不会在新项目上滚动到底部的原因是因为您在将项目添加到适配器之前调用了scrollToBottom()。

删除

scrollToBottom()
中的
binding.ivSend.setOnClickListener{}
并将其添加到adapter.updateMessages(messages)下面

Log.d(TAG, "onDataChange: $messages")
adapter.updateMessages(messages)
scrollToBottom()

这样,当首次从数据库加载项目以及添加新项目时,回收器视图将向下滚动。

为了更容易阅读代码,我建议您将代码排序到方法中,而不是将所有内容都放在

override fun onCreate(savedInstanceState: Bundle?) {}
中。

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    binding = ActivityChatBinding.inflate(layoutInflater)
    setContentView(binding.root)
    ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
        val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
        v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
        insets
    }


    setArgs()
    setupUI()
    setupDatabase()
    setupListeners()

}

private fun setArgs(){
    userDetails = intent.getParcelableExtra<ConnectionDetailDTO>("USER_DETAILS")
    currentUser = Paper.book().read<User>(Constant.k_user)
}

private fun setupUI(){
    //toolbar
    binding.toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }

    binding.toolbar.title = userDetails.name
    Glide.with(this).load(userDetails.profileUrl).placeholder(R.drawable.ic_dummy_user).into(binding.ivProfile)
    binding.ivProfile.setOnClickListener{showProfile()}

    //recycler view
    adapter = ChatAdapter(currentUser.id)
    binding.rvMessages.layoutManager = LinearLayoutManager(this)
    binding.rvMessages.adapter = adapter
}

private fun setupDatabase(){
     database = FirebaseDatabase.getInstance()
     reference = database.getReference("Conversations")
}

private fun setupListeners(){
    reference.addValueEventListener(object : ValueEventListener {
        override fun onDataChange(dataSnapshot: DataSnapshot) {
            val messages = mutableListOf<Message>()
            for (conversationSnapshot in dataSnapshot.children) {
                val conversationIdParts = conversationSnapshot.key?.split("_")
                if (conversationIdParts?.size == 3) {
                    val firstUserId = conversationIdParts[1]
                    val secondUserId = conversationIdParts[2]
                    convoId = "conversation_${firstUserId}_${secondUserId}"

                    if (firstUserId == currentUser.id.toString() && secondUserId == currentUser.id.toString()
                        || firstUserId == userDetails.id.toString() && secondUserId == currentUser.id.toString()) {
                        val conversationMessages = mutableListOf<Message>()
                        for (messageSnapshot in conversationSnapshot.child("messages").children) {
                            val content = messageSnapshot.child("content").value.toString()
                            val date = messageSnapshot.child("date").value.toString()
                            val messageId = messageSnapshot?.child("id")?.value.toString()
                            val isRead = messageSnapshot.child("isRead").value as? Boolean ?: false
                            val name = messageSnapshot.child("name").value.toString()
                            val senderEmail = messageSnapshot.child("senderEmail").value.toString()
                            val type = messageSnapshot.child("type").value.toString()

                            val message = Message(content, date, messageId, isRead, name, senderEmail, type)
                            conversationMessages.add(message)
                            Log.d(TAG, "onDataChange: $message")
                        }
                        messages.addAll(conversationMessages)
                    }
                }
            }
            Log.d(TAG, "onDataChange: $messages")
            adapter.updateMessages(messages)
        }

        override fun onCancelled(databaseError: DatabaseError) {
            println("Error: ${databaseError.message}")
        }
    })


    binding.ivSend.setOnClickListener {
        val messageText = binding.tiMessage.editText?.text.toString().trim()

        if (messageText.isNotEmpty()) {
            val conversationId1 = "conversation_${currentUser.id}_${userDetails.id}"
            val conversationId2 = "conversation_${userDetails.id}_${currentUser.id}"
            reference.child(conversationId1).get().addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    val data = task.result?.value
                    val conversationId = if (data != null) {
                        conversationId1
                    } else {
                        conversationId2
                    }
                    val messageReference =
                        reference.child(conversationId).child("messages").push()
                    val message = Message(
                        content = messageText,
                        date = SimpleDateFormat(
                            "yyyy-MM-dd HH:mm:ss",
                            Locale.getDefault()
                        ).format(Date()),
                        id = messageReference.key!!,
                        isRead = false,
                        name = currentUser?.name ?: "",
                        senderEmail = currentUser.id.toString(),
                        type = "text"
                    )
                    messageReference.setValue(message)
                    binding.tiMessage.editText?.setText("")
                    scrollToBottom()
                }
            }

            addUser(currentUser.id.toString())
        }
    }
}

最后我建议您阅读 MVVM 设计模式以获取更多代码结构。

© www.soinside.com 2019 - 2024. All rights reserved.