实现一个智能聊天机器人「Retrofit,recyclerView案例」

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

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

先看一下实现后的效果图

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

文参考自带领新手快速开发APP ,

郭神的书籍第一行代码第二版第三章3.7节

布局文件

创建项目,在build.gradle下填入依赖

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

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


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




    

    

        

        

回到Activity声明控件,并且给按钮设置点击事件,在按钮的点击事件里面进行输入框的处理,依次是:

1,获取输入框的内容

2,判断是否为空

3,发送后清空当前的输入框

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //  聊天消息列表
    private RecyclerView recyclerView;

    //  输入框
    private EditText editText;

    //  发送按钮
    private Button btn_send;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        recyclerView = findViewById(R.id.recycler);
        editText = findViewById(R.id.et_text);
        btn_send = findViewById(R.id.btn_send);
        btn_send.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_send:
                /**
                 * 1,获取输入框的内容
                 * 2,判断是否为空
                 * 3,发送后清空当前的输入框
                 */
//              1,获取输入框的内容
                String text = editText.getText().toString();
//              2,判断是否为空
                if (!TextUtils.isEmpty(text)) {
//              3,这里假装已经发送了数据,清空输入框
                    editText.setText("");
                }
                break;
        }
    }
}

后端接口的调试:

后端的接口数据第三方:图灵机器人,我们创建一个机器人,把机器人命名为阿紫,在正式卡死然后我们先用接口调试工具进行接口的调试,确认一下接口没有任何问题

这里我用的调试工具是PostMan,参照图灵机器人官方文档

实现一个智能聊天机器人「Retrofit,recyclerView案例」_第1张图片
图灵机器人页面

Post请求接口地址:http://openapi.tuling123.com/openapi/api/v2

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

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

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

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

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

进行调试

实现一个智能聊天机器人「Retrofit,recyclerView案例」_第2张图片
PostMan调试

缩略图又有点模糊,点击图片就可以放大查看

 {
    "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集合中resultd集合的第一个对象的values.test,这是机器人回复的回答

我们可以试试发送不同的文本,看看机器人会怎么回答,比如:

问题:什么是Android?

回答: Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移(省略)

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

聊天界面的书写

我们使用RecyclerView来作为聊天的列表,「基础(随风飘扬的微笑)」

在bean文件夹下创建聊天消息实体类chat,text属性就是我们要显示在页面上的文本,而type属性是我们对数据类型的一次标记

这个标记的作用是让聊天时分为两种数据。

1,接受到的数据,这样的数据会显示在我们页面的左边

2,发送的数据,这样的数据会显示在我们页面的右边

后面再根据标记来决定单条聊天数据时要显示在左边还是右边

/**
 * Created by 舍长 on 2018/5/10.
 * 描述: 聊天消息实体类
 */

public class Chat {
    //收到的数据
    public static final int TYPE_RECEIVED = 0;
    //发送的数据
    public static final int TYPE_SENT = 1;
    //    对话文本
    private String text;
    //    标示
    private int type;

    public Chat(String text, int type) {
        this.text = text;
        this.type = type;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "ChatLiatData{" +
                "text='" + text + '\'' +
                ", type=" + type +
                '}';
    }
}

如果是自己的话(右边),type设置为TYPE_SENT,1

而如果是机器人的话,type设置为TYPE_RECEIVED,0

在build.gradle下添加圆形化图片的框架CircleImageView,用于圆形头像处理

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

然后开始写我们的RecyclerView的item布局,命名为chat_item。

我们先把左右的消息都写出来,在后面加载消息的时候,根据数据的标识来判断,我们不需要那边,就把那边隐藏起来就可以啦!




    

        

        
    

    

        

        
    


接下来就开始写RecyclerView的Adapter

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

public class RecyclerViewAdapter extends RecyclerView.Adapter {


    //    上下文
    private Context context;

    //    对话列表
    private List mlist;

    public RecyclerViewAdapter() {

    }

    public RecyclerViewAdapter(Context context, List list) {
        this.context = context;
        this.mlist = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Chat chat = mlist.get(position);
        if (chat.getType() == Chat.TYPE_RECEIVED) {
//           如果收的的数据是左边,就显示左边的消息布局,将右边的消息布局隐藏
            holder.leftLayout.setVisibility(View.VISIBLE);
            holder.rightLayout.setVisibility(View.GONE);
            holder.leftChat.setText(chat.getText());
//
        } else if (chat.getType() == chat.TYPE_SENT) {
//           如果发出的消息是右边,就显示右边的消息布局,将左边的消息布局隐藏
            holder.rightLayout.setVisibility(View.VISIBLE);
            holder.leftLayout.setVisibility(View.GONE);
            holder.rightChat.setText(chat.getText());
        }
    }

    @Override
    public int getItemCount() {
        return mlist.size();
    }

    /**
     * 声明控件
     */
    class ViewHolder extends RecyclerView.ViewHolder {
        LinearLayout leftLayout;
        LinearLayout rightLayout;
        TextView leftChat;
        TextView rightChat;

        public ViewHolder(View itemView) {
            super(itemView);
            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);
        }
    }
}

布局和平常的RecyclerView的适配器的区别在于在onBindViewHolder()方法中对请求到的数据实体chat.getType()进行了判断是要显示哪边的数据

在Activtiy进行数据的填充,还有初始化RecyclerView

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //  聊天消息列表
    private RecyclerView recyclerView;

    //  输入框
    private EditText editText;

    //  发送按钮
    private Button mButton;

    //    对话信息集合
    private List list = new ArrayList<>();

    //    适配器
    private RecyclerViewAdapter recyclerViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//      初始化数据
        initView();
//       加载数据
        initData();
//      设置布局管理
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerViewAdapter = new RecyclerViewAdapter(this, list);
        recyclerView.setAdapter(recyclerViewAdapter);
    }

    /**
     * 加载列表布局数据
     */
    private void initData() {
        Chat c1 = new Chat("你好,我叫阿紫", Chat.TYPE_RECEIVED);
        list.add(c1);
        Chat c2 = new Chat("你好,你现在会些什么呢?", Chat.TYPE_SENT);
        list.add(c2);
        Chat c3 = new Chat("我还在成长中,很多东西还不懂,但是你可以考考我", Chat.TYPE_RECEIVED);
        list.add(c3);
        Chat c4 = new Chat("1+1等于几?", Chat.TYPE_RECEIVED);
        list.add(c4);
    }

    /**
     * 初始化控件
     */
    private void initView() {
        recyclerView = findViewById(R.id.recycler);
        editText = findViewById(R.id.et_text);
        mButton = findViewById(R.id.btn_send);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_send:
                /**
                 * 1,获取输入框的内容
                 * 2,判断是否为空
                 * 3,发送后清空当前的输入框
                 */
//              1,获取输入框的内容
                String text = editText.getText().toString();
//              2,判断是否为空
                if (!TextUtils.isEmpty(text)) {
//                  把要发送的数据添加到addData方法中,并把数据类型也填入,这里我们的类型是TYPE_SENT,发送数据类型
                    addData(text, Chat.TYPE_SENT);
//                  清空输入框
                    editText.setText("");
//              3,清空发送输入框
                    editText.setText("");

                }
                break;
        }
    }
        /**
     * 通过传递进来的test和type创建数据实体类,添加到聊天数据集合list中
     * @param text
     * @param type
     */
    private void addData(String text, int type) {
        Chat c = new Chat(text, type);
        list.add(c);
        //当有新消息时,刷新显示
        recyclerViewAdapter.notifyItemInserted(list.size() - 1);
        //定位的最后一行
        recyclerView.scrollToPosition(list.size() - 1);
    }

}

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

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

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

这里写图片描述

对接网络接口:

我们采用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文件中添加网络权限


要发送的Json数据格式:

{

    "reqType":0,

    "perception": {

        "inputText": {

            "text": "你叫什么"

        }

    },

    "userInfo": {

        "apiKey": "c00282de107144fb940adab994d9ff98",

        "userId": "225167"

    }

}

创建实体类Ask,使用GsonFormat工具生成请求数据实体类Ask(请求体)


/**
 * Created by 舍长 on 2018/5/10.
 * 描述: 发送数据实体类
 */

public class Ask {
    /**
     * reqType : 0
     * perception : {"inputText":{"text":"你叫什么"}}
     * userInfo : {"apiKey":"c00282de107144fb940adab994d9ff98","userId":"225167"}
     */

    private int reqType;
    private PerceptionBean perception;
    private UserInfoBean userInfo;

    public int getReqType() {
        return reqType;
    }

    public void setReqType(int reqType) {
        this.reqType = reqType;
    }

    public PerceptionBean getPerception() {
        return perception;
    }

    public void setPerception(PerceptionBean perception) {
        this.perception = perception;
    }

    public UserInfoBean getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(UserInfoBean userInfo) {
        this.userInfo = userInfo;
    }

    public static class PerceptionBean {
        /**
         * inputText : {"text":"你叫什么"}
         */

        private InputTextBean inputText;

        public InputTextBean getInputText() {
            return inputText;
        }

        public void setInputText(InputTextBean inputText) {
            this.inputText = inputText;
        }

        public static class InputTextBean {
            /**
             * text : 你叫什么
             */

            private String text;

            public String getText() {
                return text;
            }

            public void setText(String text) {
                this.text = text;
            }
        }
    }

    public static class UserInfoBean {
        /**
         * apiKey : c00282de107144fb940adab994d9ff98
         * userId : 225167
         */

        private String apiKey;
        private String userId;

        public String getApiKey() {
            return apiKey;
        }

        public void setApiKey(String apiKey) {
            this.apiKey = apiKey;
        }

        public String getUserId() {
            return userId;
        }

        public void setUserId(String userId) {
            this.userId = userId;
        }
    }
}

接受的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": "叫我阿紫就可以了"
        }
    }]
}

如果使用GsonFormat去生成响应实体类会很长,我们可以自己筛选一下,删除暂时用不到的属性,筛选后的相应实体如下

Take(响应体)

/**
 * Created by 舍长 on 2018/5/10.
 * 描述: 接受的Json数据实体类
 */

public class Take {

    private List results;


    public List getResults() {
        return results;
    }


    public static class ResultsBean {


        private ValuesBean values;


        public ValuesBean getValues() {
            return values;
        }


        public static class ValuesBean {
            /**
             * text : 叫我阿紫就可以了
             */

            private String text;

            public String getText() {
                return text;
            }

            public void setText(String text) {
                this.text = text;
            }
        }
    }
}

两个实体类看起来有点复杂,但其实我们这个小例子只是需要其中的一个文本数据而已。然后我们创建net文件夹,在net文件夹类创建Api接口来写Retrofit的实现接口

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

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

然后写我们的数据请求方法

/**
     * 请求数据
     *
     * @param text 输入框的发送数据
     */
    private void request(String text) {
//      把输入的文本数据存储在请求实体类中
        Ask ask = new Ask();
        Ask.UserInfoBean info = new Ask.UserInfoBean();
        info.setApiKey("c00282de107144fb940adab994d9ff98");//将机器人的key值填入
        info.setUserId("225167");//将用户id填入
        ask.setUserInfo(info);
        Ask.PerceptionBean.InputTextBean pre = new Ask.PerceptionBean.InputTextBean(text);//将要发送给机器人书文本天趣
        ask.setPerception(new Ask.PerceptionBean(pre));

//       创建Retrofit对象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://openapi.tuling123.com/")//设置网络请求url,后面一段写在网络请求接口里面
                .addConverterFactory(GsonConverterFactory.create())//Gson解析
                .build();
//       创建网络请求接口的实例
        Api api = retrofit.create(Api.class);
//      Take为响应实体类,用来接受机器人返回的回复数据
        Call call = api.request(ask);
//
        call.enqueue(new Callback() {
//          请求成功
            @Override
            public void onResponse(Call call, Response response) {
//              接受到的机器人回复的数据
                String mText= response.body().getResults().get(0).getValues().getText();
//              把接受到的数据传入addData方法中,类型是TYPE_RECEIVED接受数据
                addData(mText, Chat.TYPE_RECEIVED);
                L.d("接受到的机器人回复的数据: "+mText);
            }
//            请求失败
            @Override
            public void onFailure(Call call, Throwable t) {
                L.d("请求失败: "+t.toString());
            }
        });
    }

创建一个请求实体类,把机器人key,userId以及要发送的文本数据传入,最后使用Retrofit进行网络请求后,把返回的数据和数据类型传入到我们的addData方法中,显示到RecyclerView界面上

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //  聊天消息列表
    private RecyclerView recyclerView;

    //  输入框
    private EditText editText;

    //  发送按钮
    private Button mButton;

    //    对话信息集合
    private List list = new ArrayList<>();

    //    适配器
    private RecyclerViewAdapter recyclerViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//      初始化数据
        initView();
//       加载数据
        initData();
//      设置布局管理
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerViewAdapter = new RecyclerViewAdapter(this, list);
        recyclerView.setAdapter(recyclerViewAdapter);
    }

    /**
     * 加载列表布局数据
     */
    private void initData() {
        Chat c1 = new Chat("你好,我叫阿紫", Chat.TYPE_RECEIVED);
        list.add(c1);
        Chat c2 = new Chat("你好,你现在会些什么呢?", Chat.TYPE_SENT);
        list.add(c2);
        Chat c3 = new Chat("我还在成长中,很多东西还不懂,但是你可以考考我", Chat.TYPE_RECEIVED);
        list.add(c3);
        Chat c4 = new Chat("1+1等于几?", Chat.TYPE_RECEIVED);
        list.add(c4);
    }

    /**
     * 初始化控件
     */
    private void initView() {
        recyclerView = findViewById(R.id.recycler);
        editText = findViewById(R.id.et_text);
        mButton = findViewById(R.id.btn_send);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_send:
                /**
                 * 1,获取输入框的内容
                 * 2,判断是否为空
                 * 3,发送后清空当前的输入框
                 */
//              1,获取输入框的内容
                String text = editText.getText().toString();
//              2,判断是否为空
                if (!TextUtils.isEmpty(text)) {
//                  把要发送的数据添加到addData方法中,并把数据类型也填入,这里我们的类型是TYPE_SENT,发送数据类型
                    addData(text, Chat.TYPE_SENT);
//                  清空输入框
                    editText.setText("");
//                  把发送的文本数据传递到request方法中,请求数据
                    request(text);
                }
                break;
        }
    }

    /**
     * 通过传递进来的test和type创建数据实体类,添加到聊天数据集合list中
     * @param text
     * @param type
     */
    private void addData(String text, int type) {
        Chat c = new Chat(text, type);
        list.add(c);
        //当有新消息时,刷新显示
        recyclerViewAdapter.notifyItemInserted(list.size() - 1);
        //定位的最后一行
        recyclerView.scrollToPosition(list.size() - 1);
    }

    /**
     * 请求数据
     *
     * @param text 输入框的发送数据
     */
    private void request(String text) {
//      把输入的文本数据存储在请求实体类中
        Ask ask = new Ask();
        Ask.UserInfoBean info = new Ask.UserInfoBean();
        info.setApiKey("c00282de107144fb940adab994d9ff98");//将机器人的key值填入
        info.setUserId("225167");//将用户id填入
        ask.setUserInfo(info);
        Ask.PerceptionBean.InputTextBean pre = new Ask.PerceptionBean.InputTextBean(text);//将要发送给机器人书文本天趣
        ask.setPerception(new Ask.PerceptionBean(pre));

//       创建Retrofit对象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://openapi.tuling123.com/")//设置网络请求url,后面一段写在网络请求接口里面
                .addConverterFactory(GsonConverterFactory.create())//Gson解析
                .build();
//       创建网络请求接口的实例
        Api api = retrofit.create(Api.class);
//      Take为响应实体类,用来接受机器人返回的回复数据
        Call call = api.request(ask);
//
        call.enqueue(new Callback() {
//          请求成功
            @Override
            public void onResponse(Call call, Response response) {
//              接受到的机器人回复的数据
                String mText= response.body().getResults().get(0).getValues().getText();
//              把接受到的数据传入addData方法中,类型是TYPE_RECEIVED接受数据
                addData(mText, Chat.TYPE_RECEIVED);
                L.d("接受到的机器人回复的数据: "+mText);
            }
//            请求失败
            @Override
            public void onFailure(Call call, Throwable t) {
                L.d("请求失败: "+t.toString());
            }
        });
    }
}

大结局

你可能感兴趣的:(实现一个智能聊天机器人「Retrofit,recyclerView案例」)