【9】应用开发——聊天界面实战教程

一、聊天界面实战

本文作为第8篇博客的实战案例,将运用我们上一篇文章所学的知识开发一个较为复杂的聊天界面。

1.1 制作9-Patch图片

9-patch是一种缩放不失真或缩放不变形的图片格式,常用于Android系统的聊天框实现。它两处个特点:

  1. 文件名以.9.png结尾。
  2. 图像边缘具有1像素宽度的边框。

那9-Patch图片和普通图片有什么区别呢?例如,我们给LinearLayout设置一个北背景图片,当宽度指定为跟随父类u,将高度设置为50dp,这时会发现图片被拉伸了。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@drawable/message_left"
    tools:context=".MainActivity">
</LinearLayout>

图片被拉伸的效果:
在这里插入图片描述
这种效果看起来很差,但是通过9-Patch图片就不会出现这种问题。接下来我们来学习一下如何制作9-Patch图片:右键目标图片(以message_left.png为例) ——> Creat 9-Patch file。这里我们就创建了一张9.png的图片。
在这里插入图片描述
我们可以通过鼠标在图片的四个边框绘制黑色线条。

  • 上边框和左边框的黑色线条表示当图片需要拉伸时,就拉伸黑色线条对应的区域。
  • 下边框和右边框的绘制部分表示允许放置内容的区域。
    message_left.9.png:
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/44fc18cccced477d9ef2f8c12e2cd152.png
    此时我们再来运行一下程序,你会发现图片需要拉伸的时候,就会只拉伸我们指定的区域了。
    在这里插入图片描述

1.2 编写聊天界面

首先你需要添加RecyclerView的依赖,这里就不再赘述了。然后开始编写主界面的布局文件activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d8e0e8"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/inputText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:hint="Type something here"
            android:maxLines="2"
            android:weight="1"
            tools:ignore="Suspicious0dp" />

        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send" />
    </LinearLayout>
</LinearLayout>

接下来我们要创建消息实体类Msg,这里需要补充几个知识点。

  • 在Msg实体类中我们将TYPE_RECEIVED和TYPE_SENT两个成员声明在companion object中,这样可以让我们直接通过类名直接访问,而不需要创建类的实例
  • 我们还将TYPE_RECEIVED和TYPE_SENT通过const关键字定义为常量。需要注意,const常量关键字只能在单例类、companion object、顶层方法中才能使用

Kotlin中,顶层方法是指直接定义在文件中的方法,而不是定义在类、接口或对象中。这种方法可以直接使用,无需创建对象。

以下是一个顶层方法的声明:

//顶层方法 直接声明在文件在
fun printMessage(message: String) {  
    println(message)  
}

以下是顶层方法的调用:

//调用顶层方法  直接使用方法名
printMessage("Hello, world!")```

好啦,到这里你应该就能顺利看懂下面的Msg实体类了:

               消息内容              消息类型
class Msg(val content: String, val type: Int) {
    companion object {
        //表明是一条收到的消息
        const val TYPE_RECEIVED = 0
        //表明是一条发送的消息
        const val TYPE_SENT = 1
    }
}

创建RecyclerView的子项布局,新建接收消息的布局msg_left_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left">

        <TextView
            android:id="@+id/leftMsg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:margin="10dp"
            android:textColor="#fff" />

    </LinearLayout>
</FrameLayout>

在这里插入图片描述

新建发送消息的布局msg_right_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right">

        <TextView
            android:id="@+id/rightMsg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:margin="10dp"
            android:textColor="#000" />

    </LinearLayout>
</FrameLayout>

在这里插入图片描述
接下来创建RecyclerView的适配器MsgAdapter。在MsgAdapter中我们声明了两个ViewHolder内部类,分别用来缓存msg_left_item和msg_right_item。然后重写了getItemViewType(),该方法会返回当前position的消息类型。在onCreateViewHolder方法中根据不同的viewType来创建不同的界面。

RecyclerView的ViewHolder用于缓存RecyclerView列表子项的控件

class MsgAdapter(val msgList: List<Msg>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

	//内部类ViewHolder用于缓存列表项控件
    inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val leftMsg: TextView = view.findViewById(R.id.leftMsg)
    }

	//发送消息的ViewHolder 缓存msg_right_item布局中的控件
    inner class RightViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val rightMsg: TextView = view.findViewById(R.id.rightMsg)
    }

	//获取当前position的消息类型
    override fun getItemViewType(position: Int): Int {
        val msg = msgList[position]
        return msg.type
    }

                                            viewType的值由getItemViewType决定
                                                          |
                                                          V
    //用于创建ViewHolder对象
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        //根据不同的viewType创建不同的界面
        return if (viewType == Msg.TYPE_RECEIVED) {
            val view = 
                LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
            LeftViewHolder(view)
        } else {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
            RightViewHolder(view)
        }
    }

    //用于对RecyclerView列表项进行赋值
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val msg = msgList[position]
        when (holder) {
            is LeftViewHolder -> holder.leftMsg.text = msg.content
            is RightViewHolder -> holder.rightMsg.text = msg.content
            else -> throw IllegalArgumentException()
        }
    }

    //用于告诉RecyclerView一共有多少列表项
    override fun getItemCount(): Int {
        return msgList.size
    }
}

修改MainActivity的代码:

class MainActivity : AppCompatActivity(), View.OnClickListener {

    //Msg数据列表
    private val msgList = ArrayList<Msg>()
    //Msg适配器
    private var myAdapter: MsgAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //初始化Msg数据列表
        initMsg()
        //创建线性布局管理器并设置到RecyclerView上
        val myLayoutManager = LinearLayoutManager(this)
        myRecyclerView.layoutManager = myLayoutManager
        //初始化Msg适配器并设置到RecyclerView上
        myAdapter = MsgAdapter(msgList)
        myRecyclerView.adapter = myAdapter
        //为发送按钮设置点击事件
        send_button.setOnClickListener(this)
    }
    
    override fun onClick(v: View?) {
        when (v) {
            send_button -> {
                //获取输入框中的内容
                val content = inputText.text.toString()
                //若输入框中有内容
                if (content.isNotEmpty()) {
                	//将输入框中内容和消息类型添加至消息列表
                    val msg = Msg(content, Msg.TYPE_SENT)
                    msgList.add(msg)
                    //通知RecyclerView列表有新的数据插入
                    myAdapter?.notifyItemInserted(msgList.size - 1)
                    //定位到RecyclerView列表的最后一行
                    myRecyclerView.scrollToPosition(msgList.size - 1)
                    //清空输入框中的内容
                    inputText.setText("")
                }
            }
        }
    }
    
    private fun initMsg() {
    	//为msgList的元素添加内容和type字段值
        val msg1 = Msg("Hello guy.", Msg.TYPE_RECEIVED)
        val msg2 = Msg("Hello.Who's asking?", Msg.TYPE_SENT)
        val msg3 = Msg("It's Mike.Nice to meet u !", Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        msgList.add(msg2)
        msgList.add(msg3)
    }
}

在MainActivity中我们首先创建了Msg数据列表msgList和Msg适配器myAdapter。在onCreate()方法中我们首先对Msg数据列表进行初始化操作,然后创建了一个布局管理并将其配置到RV上。之后我们让MsgAdapter初始化,并添加到RV上。
在send_button按钮的点击事件中我们先判断EditText是否有内容,若有内容则将内容和消息类型添加到msgList列表中。然后通过Adapter.notifyItemInserted()方法告诉RecyclerView列表有新数据要插入,并通过RecyclerView.scrollToPosition()方法定位到RecyclerView列表的最后一行,保证总是可以看到最后一行消息。

运行效果图:
在这里插入图片描述
发送消息:
在这里插入图片描述

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值