使用kotlin实现一个智能聊天机器人「图灵机器人,Android,kotlin」

简介: 推荐一部关于Ai的系列漫画,叫做代码的深渊相关代码已经上传到Github的仓库kotlinRobot先来看一下实现的效果图智能机器人效果预览~1.gif文章思路参考自刘桂林前辈的带领新手快速开发APP ,聊天界面代码参考于郭神的第一行代码第二版第三章3.7节,由衷感谢。

推荐一部关于Ai的系列漫画,叫做代码的深渊

相关代码已经上传到Github的仓库kotlinRobot

先来看一下实现的效果图

img_a46578492e1029f1b2d6ef5dc5fcf711.gif
智能机器人效果预览~1.gif

文章思路参考自刘桂林前辈的带领新手快速开发APP ,聊天界面代码参考于郭神的第一行代码第二版第三章3.7节,由衷感谢。自己也写过一篇讲解kotlin几个基础小知识点的文章,kotlin初体验,文中不足之处,希望能得到您的指正,十分感谢。

布局文件

创建我们的项目,勾选kotlin支持,并在build.gradle下填入依赖

implementation 'com.android.support:design:26.1.0'

然后我们进去res-values-styles下,把主题修改成没有标题的样式

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

在activity_main布局里写一个recyclerView,用来显示对话聊天消息,以及一个EditText输入框,和一个发送数据的button按钮

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.administrator.robot.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="输入" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="66dp"
            android:layout_height="36dp"
            android:layout_margin="5dp"
            android:background="#444444"
            android:text="发送"
            android:textColor="#FFFFFF" />
    </LinearLayout>

</LinearLayout>

先来写一个Log工具类,方便测试,创建util包,在util下创建L类

/**
 * Created by 舍长 on 2018/4/27.
 * 在kotlin中,加了object后,L类就成为了一个单例模式的类,相当于帮我们省略掉了以前Java实现单例的代码
 * 最后我们可以直接L.d调用类中的方法
 */
object L {

    //    TAG
    public var TAG: String = "tonJies"

    fun d(test: String) {
        Log.d(TAG, test)
    }
}

我们回到Activity,在activity的导包中添加这一行,我们就不需要再写findViewByid了

import kotlinx.android.synthetic.main.activity_main.*

我们给发送按钮设置点击事件,回调监听,在点击事件里面进行输入框内容的处理,依次是:

1,获取输入框的内容

2,判断是否为空

3,点击发送按钮后清空当前输入框

//加了这一行后我们就不需要再findViewById了
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity(), View.OnClickListener {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
//       设置控件的回调监听
        init()
        L.d("Hello")
    }

    /**
     * 设置控件的回调监听
     */
    private fun init() {
        btn_send.setOnClickListener(this)
    }


    /**
     * 重写OnClickListener接口的onClick方法
     */
    override fun onClick(v: View?) {
        when (v!!.id) {
            R.id.btn_send -> {
                /**
                 * 1,获取输入框的内容
                 * 2,判断是否为空
                 * 4,发送后清空当前的输入框
                 */
//               1,获取输入框的内容
                val text: String = et_text.text.toString()
//               2,判断是否为空
                if (!TextUtils.isEmpty(text)) {
                    L.d("不为空")
                } else {
                    L.d("为空")
                }
            }
        }
    }

}

聊天界面的书写

思路是使用recyclerView作为聊天对话列表的显示控件。让我们先在build.gradle下添加圆形化图片的框架CircleImageView,用来对头像的圆形处理

//CircleImageView
compile 'de.hdodenhof:circleimageview:2.1.0'

创建bean包,在文件夹类创建实体类Chat,用于储存后面的聊天数据

/**
 * data 数据类,默认帮我们实现了实体类的几个方法,例如toString,赋值
 * text 聊天文本数据
 * type 用来标示此对话类型是属于左边机器人发来的文本类型还是我们向机器人发送的文本类型,后面我们就根据这个属性来
 */
data class Chat(var text: String,var type: Int) {

}

这里的实体类和我们在Java中的实体类写法有些不同,data关键字在kotlin是数据类的意思,它的作用是帮我们默认生成了几个常用方法,如toString()方法,而且加了data后,括号里面的参数就可直接作为属性值使用了,不需要再像Java一样,在类中声明,然后再到构造方法中进行赋值。

然后我们开始写recyclerview的item布局,命名为chat_item,图片素材参考源代码

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

    <LinearLayout
        android:id="@+id/left_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left">

        <de.hdodenhof.circleimageview.CircleImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginLeft="10dp"
            android:src="@drawable/robot_logo" />

        <TextView
            android:id="@+id/tv_left_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:background="@drawable/chat_bg_cloud"
            android:gravity="center_vertical"
            android:padding="20dp"
            android:text="你好,我叫阿紫"
            android:textColor="@android:color/black" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/right_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right">

        <TextView
            android:id="@+id/tv_right_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            android:background="@drawable/chat_bg_user"
            android:gravity="center_vertical"
            android:padding="20sp"
            android:text="你好,你会些什么啊"
          android:textColor="@android:color/white" />

        <de.hdodenhof.circleimageview.CircleImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginRight="10dp"
            android:src="@drawable/user_logo" />
    </LinearLayout>

</LinearLayout>

我们先把左右的消息都写出来,在后面Adapter加载每一个item的时候我们再根据Chat的type类型来决定要隐藏那边的布局

img_fa62bf2db2b7611ed625ea3d4012bd7a.png
这里写图片描述

接下来开始写RecyclerView的适配器

/**
 * Created by 舍长 on 2018/5/7.
 * 描述: 聊天布局适配器
 */

class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {


    //    上下文
    private var context: Context? = null

    //   用户聊天消息列表
    private var mlist = ArrayList<Chat>()

    /**
     * 空参构造方法
     */
    constructor() {

    }

    /**
     * context 上下文
     * list  对话列表聊天数据
     */
    constructor(context: Context, list: ArrayList<Chat>) {
        this.context = context
        this.mlist = list
    }

    /**
     * 加载item布局
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.chat_item, parent, false)
        return ViewHolder(view)
    }


    /**
     *  在onBindViewHolder()方法中判断是要显示对应position位置的item布局要隐藏哪边的布局
     *  我们用0来表示机器人的文本类型,用1来表示用户的文本类型。
     */
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val chat = mlist[position]
//
        if (chat.type == 0) {
            //   如果数据是机器人的文本类型,就显示左边的布局,隐藏右边的布局
            holder.leftLayout.visibility = View.VISIBLE
            holder.rightLayout.visibility = View.GONE
            //    把文本设置到机器人对话框内
            holder.leftChat.text = chat.text
            //
        } else if (chat.type == 1) {
            //   如果数据是用户的文本类型,就隐藏左边布局,显示右边的布局
            holder.rightLayout.visibility = View.VISIBLE
            holder.leftLayout.visibility = View.GONE
            //            把文本设置到用户对话框内
            holder.rightChat.text = chat.text
        }
    }

    override fun getItemCount(): Int {
        return mlist.size
    }

    /**
     * 声明控件
     *
     */
    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        //      左边的机器人布局
        var leftLayout: LinearLayout

        //      右边的用户布局
        var rightLayout: LinearLayout

        //      左边的机器人文本
        var leftChat: TextView

        //      右边的用户发送文本
        var rightChat: TextView

        /**
         *
         */
        init {
            leftLayout = itemView.findViewById(R.id.left_layout)
            rightLayout = itemView.findViewById(R.id.right_layout)
            leftChat = itemView.findViewById(R.id.tv_left_text)
            rightChat = itemView.findViewById(R.id.tv_right_text)
        }
    }
}

我们声明一个mlist集合来进行数据的传递,在onBindViewHolder方法中使用0作为机器人文本的标识,使用1作为用户文本的标识,根据标识的不同来决定显示item哪边的布局,再把Chat类对象的text属性传入要显示布局的TextView上

Adapter写好后我们在Activity中进行数据的填充,并设置RecyclerView的布局管理器,以及适配器

//加了这一行后我们就不需要再findViewById了
import kotlinx.android.synthetic.main.activity_main.*

/**
 * 继承于AppCompatActivity()
 * 继承于View.OnClickListener接口,复写onClick点击事件
 */
class MainActivity : AppCompatActivity(), View.OnClickListener {
    //    对话列表集合
    private var list = ArrayList<Chat>()

    //    recyclerView适配器
    private var recyclerViewAdapter = RecyclerViewAdapter(this, list)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
//       设置控件的回调监听
        init()
//       加载数据
        initData();
        //       设置recyclerView布局管理
        val linearLayoutManager = LinearLayoutManager(this)
//       把布局管理器添加到recyclerView中
        recycler.setLayoutManager(linearLayoutManager)
//       把适配器添加到recyclerView中
        recycler.setAdapter(recyclerViewAdapter)
    }

    /**
     * 设置控件的回调监听
     */
    private fun init() {
        btn_send.setOnClickListener(this)
    }

    /**
     * 重写OnClickListener接口的onClick方法
     */
    override fun onClick(p0: View?) {
        when (p0!!.id) {
            R.id.btn_send -> {
                /**
                 * 1,获取输入框的内容
                 * 2,判断是否为空
                 * 4,发送后清空当前的输入框
                 */
//               1,获取输入框的内容
                val text: String = et_text.text.toString()
//               2,判断是否为空
                if (!TextUtils.isEmpty(text)) {
                    L.d(text + "")
                    addData(text, 1)
                } else {
                    L.d("你的输入为空")
                }
            }
        }
    }

    /**
     * 通过传递进来的test和type创建数据实体类,添加到聊天数据集合list中
     * @param text 文本信息
     * @param type 标示类型
     */
    private fun addData(mtext: String, mtype: Int) {
        L.d(mtext + "" + mtype)
        var c = Chat(mtext, mtype)
        list.add(c)

//      更新适配器,插入新数据
        recyclerViewAdapter.notifyItemInserted(list.size - 1)
//      把显示的位置定位到最后一行
        recycler.scrollToPosition(list.size - 1)
    }

    /**
     * 模拟加载数据
     */
    private fun initData() {
//      传入0到c1的type属性来表示该数据是接受到的数据,然后把实体类c1添加到对话列表集合中
        var c1: Chat = Chat("你好,我叫阿紫", 0)
        list.add(c1)
//      使用1到c2的type属性来表示该数据是输入框的发送数据,然后把实体类c2添加到对话列表集合中
        var c2: Chat = Chat("你好,你现在会些什么呢?", 1)
        list.add(c2)
//      接受数据
        var c3: Chat = Chat("我还在成长中,很多东西还不懂,但是你可以考考我\"", 0)
        list.add(c3)
//      发送数据
        var c4: Chat = Chat("1+1等于几?\"", 1)
        list.add(c4)
    }


}

我们声明了一个集合list来存储我们的聊天数据,在initData()方法中加载数据,然后设置RecyclerView的布局管理器,并将集合数据填充到适配器中。

在按钮点击事件里获取数据框的输入内容,设置标识为1,填充到list集合中,刷新适配器并把显示的地方定位到最后一行,清空输入框。

至此,我们的聊天界面就写好了,让我们来看看目前的效果

img_f2001f2a9a7de6008d1721e7cac1038b.gif
这里写图片描述

后端接口的调试

后端的接口我们使用图灵机器人,打开网站,注册好账号,创建一个机器人,我把机器人命名为阿紫,然后我们先用调试工具进行接口的调试,这里我用的调试工具是PostMan,参照图灵机器人官方文档

img_f625eca7fc97d1cce524f4eac8dc02db.png
这里写图片描述

格式是发送Json数据的Post请求,在Json数据中带着两个参数apikey和userId:

{
    "reqType":0,
    "perception": {
        "inputText": {
            "text": "你叫什么"
        }
    },
    "userInfo": {
        "apiKey": "c00282de107144fb940adab994d9ff98",
        "userId": "225167"
    }
}

1,apikey(在机器人管理页),比如这里我的是:c00282de107144fb940adab994d9ff98,

2,userId(右上角用户头像右边的数字),这里我的是:225167

3,text就是表示我们想要和机器人聊天的具体文字

img_cbf29e16f33ed06def8815cda0d0a44b.png
这里写图片描述

进行调试

img_1357cf3f878c715306379a380323d1d6.png
这里写图片描述

不得已截了大图,缩略图又有点模糊,点击图片就可以放大查看了

 {
    "emotion": {
        "robotEmotion": {
            "a": 0,
            "d": 0,
            "emotionId": 0,
            "p": 0
        },
        "userEmotion": {
            "a": 0,
            "d": 0,
            "emotionId": 0,
            "p": 0
        }
    },
    "intent": {
        "actionName": "",
        "code": 10004,
        "intentName": ""
    },
    "results": [{
        "groupType": 0,
        "resultType": "text",
        "values": {
            "text": "叫我阿紫就可以了"
        }
    }]
}

数据比较多,但是我们关心得的,仅仅只是Json数组result里面的test:我叫阿紫,不要被我的名字所迷倒哦而已,我们可以试试发送不同的文本,看看机器人会怎么回答,比如:

什么是Android? -- Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移...

你喜欢我吗? -- 你喜欢我,我就喜欢你。

至此,我们确定接口调试成功了

对接网络接口:

我们采用Retrofit作为我们网络框架,还没接触过Retrofit的伙伴可以参考这篇文章

添加依赖:

//    retrofit
    compile 'com.squareup.okhttp3:okhttp:3.1.2'
    compile 'com.squareup.retrofit2:retrofit:2.0.1'
    compile 'com.squareup.retrofit2:converter-gson:2.0.1'

别忘了在AndroidManifest.xml文件中添加网络权限

<uses-permission android:name="android.permission.INTERNET"/>

让我们来看看要作为body发送给服务端是Json数据

{
    "reqType":0,
    "perception": {
        "inputText": {
            "text": "你叫什么"
        }
    },
    "userInfo": {
        "apiKey": "c00282de107144fb940adab994d9ff98",
        "userId": "225167"
    }
}

我们在bean包创建实体类Ask,作为发送请求的请求体。

使用Java代码时,生成JavaBean实体类一般使用JsonFormat插件进行生成,但是JsonFormat在Kotlin中是使用不了的,所以我们要使用kotlin的生成JavaBean插件,JSON To Kotlin

打开File-Setting-Plugins-Browse respositories 搜索 JSON To Kotlin Class 安装,然后使用它生成Ask请求体实体类

img_b83873430485c93a060a2603384e1883.gif
这里写图片描述

打开Setting-Other-勾选Enable Inner Class Model 的作用是使得生成的数据都以内部类的形式出现,不勾选的话会默认把所有的类都生成在包内

/**
 * 请求数据请求体实体类
 * reqType 传0就行
 */
data class Ask(val reqType: Int,
        val perception: Perception,
        val userInfo: UserInfo
) {
    data class Perception(
            val inputText: InputText
    ) {
//      要发送的文本消息
        data class InputText(
                val text: String
        )
    }

    data class UserInfo(
//          机器人apiKey
            val apiKey: String,
//          用户id
            val userId: String
    )
}

上面提到加了data关键字,会帮我们默认生成几个方法,比如toString(),格式是例如"User(name=John, age=42)";但是要注意的一点是加了data,类就不能有无参构造方法了,如果我们输入这样的代码,var ask=Ask(),那就会报错了。

再看看响应体的Json数据

{
    "emotion": {
        "robotEmotion": {
            "a": 0,
            "d": 0,
            "emotionId": 0,
            "p": 0
        },
        "userEmotion": {
            "a": 0,
            "d": 0,
            "emotionId": 0,
            "p": 0
        }
    },
    "intent": {
        "actionName": "",
        "code": 10004,
        "intentName": ""
    },
    "results": [{
        "groupType": 0,
        "resultType": "text",
        "values": {
            "text": "叫我阿紫就可以了"
        }
    }]
}

一样,在bean目录下创建接受数据实体类Take(响应体)


/**
 * 返回数据响应体实体类
 */
data class Take(
        val emotion: Emotion,
        val intent: Intent,
        val results: List<Result>
) {
    data class Emotion(
            val robotEmotion: RobotEmotion,
            val userEmotion: UserEmotion
    ) {
        data class UserEmotion(
                val a: Int,
                val d: Int,
                val emotionId: Int,
                val p: Int
        )

        data class RobotEmotion(
                val a: Int,
                val d: Int,
                val emotionId: Int,
                val p: Int
        )
    }

    data class Result(
            val groupType: Int,
            val resultType: String,
            val values: Values
    ) {
        //      返回文本 "叫我阿紫就可以了"
        data class Values(
                val text: String
        )
    }

    data class Intent(
            val actionName: String,
            val code: Int,
            val intentName: String
    )
}

响应体实体类中,我们关注的只有text返回的文本数据

接下来我们创建net包,在net包内写一个Retrofit的实现接口


/**
 * Created by 舍长 on 2018/5/10.
 * 描述: Retrofit接口
 */

public interface Api {
    //发送json数据形式的post请求,把网络请求接口的后半部分openapi/api/v写在里面
    //Get是请求数据实体类,Take接受数据实体类
    @POST("openapi/api/v2")
    Call<Take> request(@Body Ask ask);
}

之后就直接在MainActivity写我们的数据请求方法了


//加了这一行后我们就不需要再findViewById了
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

/**
 * 继承于AppCompatActivity()
 * 继承于View.OnClickListener接口,复写onClick点击事件
 */
class MainActivity : AppCompatActivity(), View.OnClickListener {
    //    对话列表集合
    private var list = ArrayList<Chat>()

    //    recyclerView适配器
    private var recyclerViewAdapter = RecyclerViewAdapter(this, list)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
//       设置控件的回调监听
        init()
//       加载数据
        initData()
        //       设置recyclerView布局管理
        val linearLayoutManager = LinearLayoutManager(this)
//       把布局管理器添加到recyclerView中
        recycler.setLayoutManager(linearLayoutManager)
//       把适配器添加到recyclerView中
        recycler.setAdapter(recyclerViewAdapter)


    }

    /**
     * 设置控件的回调监听
     */
    private fun init() {
        btn_send.setOnClickListener(this)
    }

    /**
     * 重写OnClickListener接口的onClick方法
     */
    override fun onClick(p0: View?) {
        when (p0!!.id) {
            R.id.btn_send -> {
                /**
                 * 1,获取输入框的内容
                 * 2,判断是否为空
                 * 4,发送后清空当前的输入框
                 */
//               1,获取输入框的内容
                val text: String = et_text.text.toString()
//               2,判断是否为空
                if (!TextUtils.isEmpty(text)) {
                    addData(text, 1)
                    request(text)
                } else {
                    L.d("你的输入为空")
                }
            }
        }
    }

    /**
     * 通过传递进来的test和type创建数据实体类,添加到聊天数据集合list中
     * @param text 文本信息
     * @param type 标示类型
     */
    private fun addData(mtext: String, mtype: Int) {
        L.d("addData  mtext:" + mtext + "  mtype" + mtype)
        var c = Chat(mtext, mtype)
        list.add(c)
//      更新适配器,插入新数据
        recyclerViewAdapter.notifyItemInserted(list.size - 1)
//      把显示的位置定位到最后一行
        recycler.scrollToPosition(list.size - 1)
    }

    /**
     * 模拟加载数据
     */
    private fun initData() {
//      传入0到c1的type属性来表示该数据是接受到的数据,然后把实体类c1添加到对话列表集合中
        var c1: Chat = Chat("你好,我叫阿紫", 0)
        list.add(c1)
//      使用1到c2的type属性来表示该数据是输入框的发送数据,然后把实体类c2添加到对话列表集合中
        var c2: Chat = Chat("你好,你现在会些什么呢?", 1)
        list.add(c2)
//      接受数据
        var c3: Chat = Chat("我还在成长中,很多东西还不懂,但是你可以考考我\"", 0)
        list.add(c3)
//      发送数据
        var c4: Chat = Chat("1+1等于几?\"", 1)
        list.add(c4)
        //      接受数据
        var c5: Chat = Chat("1+1=2", 0)
        list.add(c5)
    }


    /**
     * 请求数据
     *
     */
    private fun request(mText: String) {
//      存储要发送的的文本
        var perceotion = Ask.Perception(Ask.Perception.InputText(mText))
//      设置用户id和ApidKey
        val userInfo = Ask.UserInfo("c00282de107144fb940adab994d9ff98", "225167")
//      填充到请求体Ask中
        var ask = Ask(0, perceotion, userInfo)

//      使用retiofit进行请求
        var retrofit = Retrofit.Builder()
                .baseUrl("http://openapi.tuling123.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
//      创建网络请求接口的实例
        val api = retrofit.create(Api::class.java)
//
        val call = api.request(ask)

//
        call.enqueue(object : retrofit2.Callback<Take> {
            //          请求成功
            override fun onResponse(call: retrofit2.Call<Take>, response: Response<Take>) {
                //              接受到的机器人回复的数据
                L.d("返回的全部信息:" + response.body().toString())
                var text = response.body().results.get(0).values.text
                //在这里进行处理,防止接口没有返回数据时抛出异常
                if (text == null) {
                    text = "我还小,不知道这句话的意思"
                    //把接受到的数据传入addData方法中,类型是TYPE_RECEIVED接受数据
                    addData(text, 0)
                } else {
                    //把接受到的数据传入addData方法中,类型是TYPE_RECEIVED接受数据
                    addData(text, 0)
                }
                L.d("接受到的机器人回复的数据: " + text)
            }

            //            请求失败
            override fun onFailure(call: retrofit2.Call<Take>, t: Throwable) {
                L.d("请求失败: " + t.toString())
            }
        })
    }
}

在方法中我们创建了一个Ask请求体对象,把机器人key,userId和要发送的文本传入,注意reqType我们传入0就可以了。使用Retrofit进行网络请求,在回调里接收返回的文本,把返回的文本传入addData中,标识为0,机器人文本。

当然request方法是在点击按钮时调用的,Activty完整代码

//加了这一行后我们就不需要再findViewById了
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

/**
 * 继承于AppCompatActivity()
 * 继承于View.OnClickListener接口,复写onClick点击事件
 */
class MainActivity : AppCompatActivity(), View.OnClickListener {
    //    对话列表集合
    private var list = ArrayList<Chat>()

    //    recyclerView适配器
    private var recyclerViewAdapter = RecyclerViewAdapter(this, list)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
//       设置控件的回调监听
        init()
//       加载数据
        initData()
        //       设置recyclerView布局管理
        val linearLayoutManager = LinearLayoutManager(this)
//       把布局管理器添加到recyclerView中
        recycler.setLayoutManager(linearLayoutManager)
//       把适配器添加到recyclerView中
        recycler.setAdapter(recyclerViewAdapter)


    }

    /**
     * 设置控件的回调监听
     */
    private fun init() {
        btn_send.setOnClickListener(this)
    }

    /**
     * 重写OnClickListener接口的onClick方法
     */
    override fun onClick(p0: View?) {
        when (p0!!.id) {
            R.id.btn_send -> {
                /**
                 * 1,获取输入框的内容
                 * 2,判断是否为空
                 * 4,发送后清空当前的输入框
                 */
//               1,获取输入框的内容
                val text: String = et_text.text.toString()
//               2,判断是否为空
                if (!TextUtils.isEmpty(text)) {
                    addData(text, 1)
                    request(text)
                } else {
                    L.d("你的输入为空")
                }
            }
        }
    }

    /**
     * 通过传递进来的test和type创建数据实体类,添加到聊天数据集合list中
     * @param text 文本信息
     * @param type 标示类型
     */
    private fun addData(mtext: String, mtype: Int) {
        L.d("addData  mtext:" + mtext + "  mtype" + mtype)
        var c = Chat(mtext, mtype)
        list.add(c)
//      更新适配器,插入新数据
        recyclerViewAdapter.notifyItemInserted(list.size - 1)
//      把显示的位置定位到最后一行
        recycler.scrollToPosition(list.size - 1)
    }

    /**
     * 模拟加载数据
     */
    private fun initData() {
//      传入0到c1的type属性来表示该数据是接受到的数据,然后把实体类c1添加到对话列表集合中
        var c1: Chat = Chat("你好,我叫阿紫", 0)
        list.add(c1)
//      使用1到c2的type属性来表示该数据是输入框的发送数据,然后把实体类c2添加到对话列表集合中
        var c2: Chat = Chat("你好,你现在会些什么呢?", 1)
        list.add(c2)
//      接受数据
        var c3: Chat = Chat("我还在成长中,很多东西还不懂,但是你可以考考我\"", 0)
        list.add(c3)
//      发送数据
        var c4: Chat = Chat("1+1等于几?\"", 1)
        list.add(c4)
        //      接受数据
        var c5: Chat = Chat("1+1=2", 0)
        list.add(c5)
    }


    /**
     * 请求数据
     *
     */
    private fun request(mText: String) {
//      存储要发送的的文本
        var perceotion = Ask.Perception(Ask.Perception.InputText(mText))
//      设置用户id和ApidKey
        val userInfo = Ask.UserInfo("c00282de107144fb940adab994d9ff98", "225167")
//      填充到请求体Ask中
        var ask = Ask(0, perceotion, userInfo)

//      使用retiofit进行请求
        var retrofit = Retrofit.Builder()
                .baseUrl("http://openapi.tuling123.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
//      创建网络请求接口的实例
        val api = retrofit.create(Api::class.java)
//
        val call = api.request(ask)

//
        call.enqueue(object : retrofit2.Callback<Take> {
            //          请求成功
            override fun onResponse(call: retrofit2.Call<Take>, response: Response<Take>) {
                //              接受到的机器人回复的数据
                L.d("返回的全部信息:" + response.body().toString())
                var text = response.body().results.get(0).values.text
                //在这里进行处理,防止接口没有返回数据时抛出异常
                if (text == null) {
                    text = "我还小,不知道这句话的意思"
                    //把接受到的数据传入addData方法中,类型是TYPE_RECEIVED接受数据
                    addData(text, 0)
                } else {
                    //把接受到的数据传入addData方法中,类型是TYPE_RECEIVED接受数据
                    addData(text, 0)
                }
                L.d("接受到的机器人回复的数据: " + text)
            }

            //            请求失败
            override fun onFailure(call: retrofit2.Call<Take>, t: Throwable) {
                L.d("请求失败: " + t.toString())
            }
        })
    }
}

这一小节就到这里啦,谢谢您的观看,文中不足之处,希望能得到您的指正,期待您的留言

目录
相关文章
|
12天前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
33 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
4天前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
21 8
|
7天前
|
安全 Java Android开发
探索Android应用开发中的Kotlin语言
【7月更文挑战第19天】在移动应用开发的浩瀚宇宙中,Kotlin这颗新星以其简洁、安全与现代化的特性,正迅速在Android开发者之间获得青睐。从基本的语法结构到高级的编程技巧,本文将引导读者穿梭于Kotlin的世界,揭示其如何优化Android应用的开发流程并提升代码的可读性与维护性。我们将一起探究Kotlin的核心概念,包括它的数据类型、类和接口、可见性修饰符以及高阶函数等特性,并了解这些特性是如何在实际项目中得以应用的。无论你是刚入门的新手还是寻求进阶的开发者,这篇文章都将为你提供有价值的见解和实践指导。
|
9天前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
19 6
|
8天前
|
存储 前端开发 测试技术
Android Kotlin中使用 LiveData、ViewModel快速实现MVVM模式
使用Kotlin实现MVVM模式是Android开发的现代实践。该模式分离UI和业务逻辑,借助LiveData、ViewModel和DataBinding增强代码可维护性。步骤包括创建Model层处理数据,ViewModel层作为数据桥梁,以及View层展示UI。添加相关依赖后,Model类存储数据,ViewModel类通过LiveData管理变化,而View层使用DataBinding实时更新UI。这种架构提升代码可测试性和模块化。
38 2
|
15天前
|
Android开发 Kotlin
Android面试题之kotlin中怎么限制一个函数参数的取值范围和取值类型等
在Kotlin中,限制函数参数可通过类型系统、泛型、条件检查、数据类、密封类和注解实现。例如,使用枚举限制参数为特定值,泛型约束确保参数为Number子类,条件检查如`require`确保参数在特定范围内,数据类封装可添加验证,密封类限制为一组预定义值,注解结合第三方库如Bean Validation进行校验。
27 6
|
16天前
|
Android开发 Kotlin
Android面试题之 Kotlin中退出迭代器的方式有哪些
在Android和Kotlin中,遍历集合时可使用迭代器结合`break`提前终止循环。例如,使用`while`和迭代器,或用`forEach`配合`return@forEach`来中断遍历。若需退出外层函数,可定义自定义标签。在遍历并删除元素时,这些技巧尤其有用。
19 3
|
17天前
|
开发者 Kotlin Android开发
Kotlin协程在Android开发中的应用
【7月更文挑战第10天】Kotlin协程简化了Android异步编程,提供轻量级并发。挂起函数让异步代码看起来同步,不阻塞线程,便于管理。在项目中,添加Kotlin和协程依赖,如`kotlinx.coroutines-core`和`kotlinx-coroutines-android`。使用`CoroutineScope`和`launch`处理耗时任务,如网络请求,避免主线程阻塞。挂起函数和调度器控制执行上下文,适应不同任务需求。
|
1月前
|
安全 Java 编译器
Android面试题之Java 泛型和Kotlin泛型
**Java泛型是JDK5引入的特性,用于编译时类型检查和安全。泛型擦除会在运行时移除类型参数,用Object或边界类型替换。这导致几个限制:不能直接创建泛型实例,不能使用instanceof,泛型数组与协变冲突,以及在静态上下文中的限制。通配符如<?>用于增强灵活性,<? extends T>只读,<? super T>只写。面试题涉及泛型原理和擦除机制。
24 3
Android面试题之Java 泛型和Kotlin泛型
|
20小时前
|
Android开发 数据安全/隐私保护 iOS开发
探索未来:安卓与iOS在智能穿戴设备领域的较量
随着科技的飞速发展,智能穿戴设备已逐渐成为我们日常生活的一部分。从健康监测到通讯交流,它们正以惊人的速度改变着我们的生活方式。本文将深入探讨安卓和iOS这两大操作系统在智能穿戴领域的现状、竞争以及未来发展趋势,揭示它们如何通过创新技术满足用户需求,并预测未来可能的发展方向。
3 0