我的 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 的回收器视图解决方案
回收视图开始从上到下滚动,但我希望它是 就像聊天一样,从下到上。添加新消息后,它会滚动到 底部,但在现有列表的最后一项上,而不是在新项目上。 我尝试过使用..
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 设计模式以获取更多代码结构。