推荐一部关于Ai的系列漫画,叫做代码的深渊
相关代码已经上传到Github的仓库Robot
先看一下实现后的效果图
文参考自带领新手快速开发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,参照图灵机器人官方文档
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就是表示我们想要和机器人聊天的具体文字
进行调试
缩略图又有点模糊,点击图片就可以放大查看
{
"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());
}
});
}
}
大结局