前提条件
安装并配置好Android Studio
Android Studio Electric Eel | 2022.1.1 Patch 2
Build #AI-221.6008.13.2211.9619390, built on February 17, 2023
Runtime version: 11.0.15+0-b2043.56-9505619 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Windows 11 10.0
GC: G1 Young Generation, G1 Old Generation
Memory: 1280M
Cores: 6
Registry:
external.system.auto.import.disabled=true
ide.text.editor.with.preview.show.floating.toolbar=false
ide.balloon.shadow.size=0
Non-Bundled Plugins:
com.intuit.intellij.makefile (1.0.15)
com.github.setial (4.0.2)
com.alayouni.ansiHighlight (1.2.4)
GsonOrXmlFormat (2.0)
GLSL (1.19)
com.mistamek.drawablepreview.drawable-preview (1.1.5)
com.layernet.plugin.adbwifi (1.0.5)
com.likfe.ideaplugin.eventbus3 (2020.0.2)
gradle-wrapper.properties
#Tue Apr 25 13:34:44 CST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
build.gradle(:Project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
}
setting.gradle
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url 'https://jitpack.io' }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url 'https://jitpack.io' }
}
}
rootProject.name = "logindemo"
include ':app'
build.gralde(:app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.example.fechat'
compileSdk 33
defaultConfig {
applicationId "com.example.fechat"
minSdk 26
targetSdk 33
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// 沉浸式状态栏 https://github.com/gyf-dev/ImmersionBar
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' // fragment快速实现(可选)
implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' // kotlin扩展(可选)
implementation 'com.google.code.gson:gson:2.8.9'
}
对Kotlin语言有基本了解
内容在前一篇博客中写了基础配置,如果本篇内容看不懂,可以先去上一篇。
增加聊天页面
activity_message.xml中 标题+内容RecyclerView+底部输入框+发送
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<RelativeLayout
android:id="@+id/titleLayout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/title">
<TextView
android:id="@+id/backTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回"
android:textSize="16sp"
android:textColor="#000000"
android:padding="10dp"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"/>
<TextView
android:id="@+id/userName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户X"
android:textSize="16sp"
android:textColor="#000000"
android:layout_centerInParent="true"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/itemView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:layout_below="@+id/titleLayout"
android:layout_above="@+id/messageLayout"/>
<RelativeLayout
android:id="@+id/messageLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#F6F6F6"
android:gravity="center_vertical">
<EditText
android:id="@+id/inputText"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/message_text_shape"
android:layout_toStartOf="@+id/sendText"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="6dp"
android:padding="10dp"
android:maxLines="1"
android:singleLine="true"
android:textSize="16sp"
android:textColor="#000000"/>
<TextView
android:id="@+id/sendText"
android:layout_width="70dp"
android:layout_height="40dp"
android:background="@drawable/message_send_shape"
android:layout_alignParentEnd="true"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="6dp"
android:textSize="16sp"
android:textColor="@color/white"
android:text="发送"
android:gravity="center"/>
</RelativeLayout>
</RelativeLayout>
package com.example.fechat.activity
import android.annotation.SuppressLint
import android.os.Bundle
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.fechat.R
import com.example.fechat.adapter.ChatAdapter
import com.example.fechat.bean.ChatBean
import com.example.fechat.bean.MessageBean
import com.google.gson.Gson
import com.gyf.immersionbar.ImmersionBar
class MessageActivity : AppCompatActivity() {
private val beans: MutableList<MessageBean> = ArrayList()
private var adapter: ChatAdapter? = null
private lateinit var itemView: RecyclerView
private lateinit var chatBean: ChatBean
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ImmersionBar.with(this)
.statusBarDarkFont(true)
.statusBarColor(R.color.title)
.navigationBarColor(R.color.white)
.navigationBarDarkIcon(true)
.init()
setContentView(R.layout.activity_message)
val backTv = findViewById<TextView>(R.id.backTv)
val inputText: EditText = findViewById(R.id.inputText)
val sendText: TextView = findViewById(R.id.sendText)
val userName: TextView = findViewById(R.id.userName)
backTv.setOnClickListener {
finish()
}
sendText.setOnClickListener {
sendText(inputText.text.toString())
}
initItemRecyclerView()
getBundle()
userName.text = chatBean.nick
}
private fun getBundle() {
val userInfo = intent.getStringExtra("UserInfo")
chatBean = Gson().fromJson(userInfo, ChatBean::class.java)
}
private fun initItemRecyclerView() {
itemView = findViewById(R.id.itemView)
val layoutManager = LinearLayoutManager(this)
layoutManager.orientation = RecyclerView.VERTICAL
itemView.layoutManager = layoutManager
adapter = ChatAdapter(beans)
itemView.adapter = adapter
}
@SuppressLint("NotifyDataSetChanged")
private fun sendText(message: String) {
beans.add(
MessageBean(
message,
"用户",
false,
System.currentTimeMillis()
)
)
beans.add(
MessageBean(
message,
chatBean.nick,
true,
System.currentTimeMillis(),
true
)
)
adapter?.notifyDataSetChanged()
}
}
制作聊天适配器
适配器的xml中,分为两部分 主用户发送消息,同时接收聊天对象的消息
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout
android:id="@+id/userLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="10dp">
<com.example.fechat.view.CircleImageView
android:id="@+id/userHead"
android:layout_width="46dp"
android:layout_height="46dp"
android:src="@mipmap/ic_ai_user"
android:layout_alignParentEnd="true"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/messageLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@+id/userHead"
android:layout_marginEnd="12dp">
<TextView
android:id="@+id/userMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@drawable/message_user_shape"
android:textSize="16sp"
android:textColor="@color/black"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:text="请推荐"
android:textIsSelectable="true"/>
<TextView
android:id="@+id/userTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/userMessage"
app:layout_constraintStart_toStartOf="parent"
android:text="22:32"
android:textColor="#B1B1B1"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/respLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
android:layout_marginTop="10dp"
android:visibility="gone">
<com.example.fechat.view.CircleImageView
android:id="@+id/respHead"
android:layout_width="46dp"
android:layout_height="46dp"
android:src="@mipmap/ic_ai_user"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/respHead"
android:layout_marginStart="12dp">
<RelativeLayout
android:id="@+id/respMessageLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@drawable/message_ai_shape">
<TextView
android:id="@+id/respMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/black"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:text="小猪佩奇"
android:textIsSelectable="true"/>
<ImageView
android:id="@+id/respImage"
android:layout_width="220dp"
android:layout_height="160dp"
android:layout_below="@+id/respMessage"
android:scaleType="center"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:visibility="gone"/>
</RelativeLayout>
<TextView
android:id="@+id/respTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="22:32"
android:textColor="#B1B1B1"
android:textSize="14sp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/respMessageLayout"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
</RelativeLayout>
package com.example.fechat.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import com.example.fechat.R
import com.example.fechat.bean.MessageBean
import com.example.fechat.view.CircleImageView
import java.text.SimpleDateFormat
import java.util.*
class ChatAdapter(private val data: List<MessageBean>) : Adapter<ChatAdapter.BaseHolder>() {
private fun getTime(time: Long): String {
val sDateFormat = SimpleDateFormat("MM-dd HH:mm", Locale.getDefault())
return sDateFormat.format(time)
}
class BaseHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userLayout: RelativeLayout = itemView.findViewById(R.id.userLayout)
val userHead: CircleImageView = itemView.findViewById(R.id.userHead)
val userMessage: TextView = itemView.findViewById(R.id.userMessage)
val userTime: TextView = itemView.findViewById(R.id.userTime)
val respLayout: RelativeLayout = itemView.findViewById(R.id.respLayout)
val respHead: CircleImageView = itemView.findViewById(R.id.respHead)
val respMessage: TextView = itemView.findViewById(R.id.respMessage)
val respTime: TextView = itemView.findViewById(R.id.respTime)
val respImage: ImageView = itemView.findViewById(R.id.respImage)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_message, null, false);
return BaseHolder(view)
}
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: BaseHolder, position: Int) {
val item = data[position]
if (item.isResponse) {
holder.respLayout.visibility = View.VISIBLE
holder.userLayout.visibility = View.GONE
holder.respMessage.text = item.message
holder.respTime.text = getTime(item.time)
} else {
holder.userLayout.visibility = View.VISIBLE
holder.respLayout.visibility = View.GONE
holder.userMessage.text = item.message
holder.userTime.text = getTime(item.time)
}
}
}
聊天内容适配器中的data数据结构如下:
package com.example.fechat.bean
data class MessageBean(
var message: String,
var userName: String,
var isResponse: Boolean = false,
var time: Long,
var isSuccess: Boolean = true
)
其中首页用户聊天列表中的data修改,主要是data转化为string方便intent传输,如下:
package com.example.fechat.bean
data class ChatBean(
val head: String, val nick: String,
val newest: String, val date: String
) {
override fun toString(): String {
return "{head:$head,nick:$nick,newest:$newest,date:$date}"
}
}
首页用户聊天记录的适配器作如下修改,主要是增加了适配器点击事件监听器:
package com.example.fechat.base
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.fechat.R
import com.example.fechat.bean.ChatBean
class BaseAdapter(private val data: List<ChatBean>) :
RecyclerView.Adapter<BaseAdapter.BaseHolder>() {
private lateinit var onItemClickListener: OnItemClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.base_item, null, false);
return BaseHolder(view)
}
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: BaseHolder, position: Int) {
holder.headTv.visibility = if (data[position].head.isEmpty()) View.GONE else View.VISIBLE
holder.nickTv.visibility = if (data[position].nick.isEmpty()) View.GONE else View.VISIBLE
holder.newestTv.visibility =
if (data[position].newest.isEmpty()) View.GONE else View.VISIBLE
holder.dateTv.visibility = if (data[position].date.isEmpty()) View.GONE else View.VISIBLE
holder.headTv.text = data[position].head
holder.nickTv.text = data[position].nick
holder.newestTv.text = data[position].newest
holder.dateTv.text = data[position].date
holder.itemView.setOnClickListener {
onItemClickListener.onItemClick(holder.itemView, position)
}
}
class BaseHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val headTv: TextView = itemView.findViewById(R.id.headTv)
val nickTv: TextView = itemView.findViewById(R.id.nickTv)
val newestTv: TextView = itemView.findViewById(R.id.newestTv)
val dateTv: TextView = itemView.findViewById(R.id.dateTv)
}
fun setOnItemClickListener(onItemClickListener: OnItemClickListener) {
this.onItemClickListener = onItemClickListener
}
interface OnItemClickListener {
fun onItemClick(view: View, position: Int)
}
}
点击跳转到聊天页面
package com.example.fechat.fragment
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.fechat.R
import com.example.fechat.activity.MessageActivity
import com.example.fechat.base.BaseAdapter
import com.example.fechat.bean.ChatBean
class ChatFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_chat, container, false)
recyclerView = view.findViewById(R.id.recyclerView)
val data = ArrayList<ChatBean>()
data.add(ChatBean("头像0", "用户0", "聊天记录0", "4月25日"))
data.add(ChatBean("头像1", "用户1", "聊天记录1", "4月24日"))
data.add(ChatBean("头像2", "用户2", "聊天记录2", "4月23日"))
data.add(ChatBean("头像3", "用户3", "聊天记录3", "4月22日"))
data.add(ChatBean("头像4", "用户4", "聊天记录4", "4月21日"))
data.add(ChatBean("头像5", "用户5", "聊天记录5", "4月20日"))
data.add(ChatBean("头像6", "用户6", "聊天记录6", "4月19日"))
data.add(ChatBean("头像7", "用户7", "聊天记录7", "4月18日"))
data.add(ChatBean("头像8", "用户8", "聊天记录8", "4月17日"))
data.add(ChatBean("头像9", "用户9", "聊天记录9", "4月16日"))
recyclerView.layoutManager = LinearLayoutManager(context)
val baseAdapter = BaseAdapter(data)
recyclerView.adapter = baseAdapter
baseAdapter.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
val intent = Intent(context, MessageActivity::class.java)
intent.putExtra("UserInfo", data[position].toString())
startActivity(intent)
}
})
return view
}
}