六月在忙公司的项目和自己的一些事情,差点这个小系列又要夭折了,还是抽空把它写完,希望能给新手和菜鸟一些启示,下面看下最终实现的效果。
作为最后的一个部分,想跟大家分享下这个聊天机器人的实现,这部分呢也是参考网上hyman(鸿洋大神)视频来实现的。下面是链接地址《Android智能机器人“小慕”的实现-慕课网》 http://www.imooc.com/learn/217
如果想要实现聊天机器人的功能,需要去图灵官网注册号,http://www.tuling123.com/
下面直接撸代码,讲解下我认为值得新手借鉴学习的部分。
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main , menu); // 加载菜单XML return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.menu_chat: startActivity(new Intent(MainActivity.this , ChatActivity.class)); return true ; default: return super.onOptionsItemSelected(item); } }这是点击ActionBar右侧的机器人图标的事件,其实就是加载一个菜单布局,很多新手可能没用过这个,大家要用的话可以借鉴。
对应的R.menu.main , 注意该xml文件位于res/menu/main.xml中,所以可以直接调用R.menu.main加载进来,当然你可以添加更多的item。
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_chat" app:showAsAction="always" android:title="@string/chat" android:icon="@drawable/icon"/> </menu>
点击之后进入了聊天界面,就是上面看到的那个界面,主要是一个ListView作为容器,根据itmType的类型不同加载不同的item,也就是看到的左右布局。
public class ChatActivity extends AppCompatActivity { private TextView titleTv ; private ImageView backImg ; private ListView mMsgListView ; private EditText mMsgEditTv ; private Button mSendBtn ; private List<ChatMessage> mMsgDatas ; private ChatMsgAdapter mMsgAdapter ; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); ChatMessage chat = (ChatMessage) msg.obj; mMsgDatas.add(chat); mMsgAdapter.notifyDataChanged(mMsgDatas); // 聊天列表数据刷新 mMsgListView.setSelection(mMsgDatas.size()-1); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); getSupportActionBar().hide(); initView(); initDatas(); initSendListener(); // 监听发送按钮事件 } private void initSendListener(){ mSendBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String sendMsg = mMsgEditTv.getText().toString(); if(TextUtils.isEmpty(sendMsg)){ Toast.makeText(ChatActivity.this,"输入消息不能为空!",Toast.LENGTH_SHORT).show(); } ChatMessage chatMsg = new ChatMessage(); chatMsg.setDate(new Date()); chatMsg.setMsg(sendMsg); chatMsg.setMsgType(ChatMessage.MsgType.SEND_MSG); mMsgDatas.add(chatMsg); mMsgAdapter.notifyDataChanged(mMsgDatas); // 数据集合刷新 mMsgListView.setSelection(mMsgDatas.size()-1); // 选中最后的一项 mMsgEditTv.setText(""); new Thread(new Runnable() { @Override public void run() { ChatMessage receivedMsg = HttpUtil.sendMessage(sendMsg); Message m = Message.obtain(); m.obj = receivedMsg; mHandler.sendMessage(m); } }).start(); } }); } private void initView() { titleTv = (TextView) this.findViewById(R.id.chat_title_tv); titleTv.setText("与小琳聊天中……"); backImg = (ImageView) this.findViewById(R.id.chat_back_img); mMsgListView = (ListView) this.findViewById(R.id.content_msg_listView); mMsgEditTv = (EditText) this.findViewById(R.id.edit_msg_text); mSendBtn = (Button) this.findViewById(R.id.send_button); backImg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ChatActivity.this.finish(); } }); } private void initDatas(){ // 初始化数据集合 mMsgDatas = new ArrayList<>(); // 初始化适配器 mMsgAdapter = new ChatMsgAdapter(); // 直接给列表设置适配器 mMsgListView.setAdapter(mMsgAdapter); // 添加一条新数据 mMsgDatas.add(new ChatMessage("你好,小琳为你服务!", ChatMessage.MsgType.RECEIVE_MSG,new Date())); // 然后调用数据刷新的方法刷新数据集合 mMsgAdapter.notifyDataChanged(mMsgDatas); // 第一次刷新数据 } }
这个也没什么新奇的地方,看下适配器Adapter的写法。
public class ChatMsgAdapter extends BaseAdapter { private List<ChatMessage> chatMassageList = new ArrayList<>(); @Override public int getCount() { return chatMassageList.size(); } @Override public ChatMessage getItem(int position) { return chatMassageList.get(position); } // 获取当前条目的属性值 @Override public int getItemViewType(int position) { // 通过当前的条目的类型,返回0(我的消息)或者1(对方的消息), return getItem(position).getMsgType() == ChatMessage.MsgType.RECEIVE_MSG ? 0 : 1 ; } @Override public int getViewTypeCount() { return 2; // 只有两种属性情况 } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null ; // 通过父容器(parent)来获取上下问(Context) LayoutInflater mInflater = LayoutInflater.from(parent.getContext()); if (convertView == null){ // 根据类型来加载左右不同的布局界面 if(getItemViewType(position) == 0){ convertView = mInflater.inflate(R.layout.item_from_msg,null); holder = new ViewHolder(); holder.mDateTv = (TextView) convertView.findViewById(R.id.id_form_msg_date); holder.mMsgTv = (TextView) convertView.findViewById(R.id.id_from_msg_info); }else{ holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.item_to_msg,null); holder.mDateTv = (TextView) convertView.findViewById(R.id.id_to_msg_date); holder.mMsgTv = (TextView) convertView.findViewById(R.id.id_to_msg_info); } convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } // 设置数据 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss"); holder.mDateTv.setText(sdf.format(new Date())); holder.mMsgTv.setText(getItem(position).getMsg()); return convertView; } private final class ViewHolder { TextView mDateTv ; TextView mMsgTv ; } /** * 提供给外部调用,数据集合改变并且刷新列表的方法 * @param chatMassageList */ public void notifyDataChanged(List<ChatMessage> chatMassageList){ this.chatMassageList = chatMassageList ; this.notifyDataSetChanged(); } }
这个我要叨逼叨逼
传统的Adapter写法如下
public class DemoAdapter extends BaseAdapter { private Context context ; private List<ChatMessage> chatList ; public DemoAdapter() { } public DemoAdapter(Context context, List<ChatMessage> chatList) { this.context = context; this.chatList = chatList; } @Override public int getCount() { return chatList.size(); } @Override public Object getItem(int position) { return chatList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // 使用ViewHolder进行布局加载和渲染 return null; } }
1。对比一下发现,上面那种写法并没有传入Context,而是直接通过getView中的parent.getContext()获取的上下文,这种写法看起来更美观,少了一个参数,而且从parent中获取的Context不会出错。
2。如果用下面一种Adapter写法,在给ListView设置了Adapter之后,我们常常可能会忘了要去调用一下notifyDataSetChanged方法,导致有时候列表刷新出现错误,换成第一种写法之后,数据集合在要使用的地方(Activity或Fragment中初始化一次之后),我们先给Adapter设置数据,然后将该Apapter设置给ListView,等添加了数据之后我们在直接调用自己定义的notifyData方法就可以了,感觉思路更清晰,不会忘。每次数据集合发生变化的时候就记得要通知刷新列表。
3。另外比较值得借鉴的地方是Adapter两个重写的方法
这两个方法可以设置多个属性,然后根据当前加载item的属性去做各种显示处理,比如我这里是根据属性的不同设置加载左布局还是右布局,当初为了这个的实现,我硬生生的去琢磨了一个方法,结果效果并不好,现在回过头来看,还是这个比较简单实用。
这个是我原来的方式实现的左右布局,《竖直时间轴之左右交叉布局》
http://blog.csdn.net/u010898329/article/details/51763195
为了实现上述类似的功能,写了好几十行代码做判断,结果还是有BUG,截个图大家看下
要是早知道这个方法,就不用这么费神了,所以我觉得新手或者菜鸟都可以借鉴下!
源码下载 http://download.csdn.net/detail/u010898329/9883518
-----------------------结束语-----------------------
没有谁生来就是强大的,什么都懂,做技术唯有不断的练习和思考才会熟能生巧
就像卖油翁说的:无他,唯手熟尔!
祝愿每一个菜鸟(包括我)都能成为大神