Android类似微信聊天的页面开发教程(Kotlin)三

前提条件

安装并配置好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
    }
}

开源地址

FeChat: 模仿微信

猜你喜欢

转载自blog.csdn.net/mozushixin_1/article/details/130371121