六月在忙公司的項目和自己的一些事情,差點這個小系列又要夭折了,還是抽空把它寫完,希望能給新手和菜鳥一些啓示,下面看下最終實現的效果。
作爲最後的一個部分,想跟大家分享下這個聊天機器人的實現,這部分呢也是參考網上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
-----------------------結束語-----------------------
沒有誰生來就是強大的,什麼都懂,做技術唯有不斷的練習和思考纔會熟能生巧
就像賣油翁說的:無他,唯手熟爾!
祝願每一個菜鳥(包括我)都能成爲大神