第5节 界面使用ConnectionManager
ConnectionManager
已经设计完成了,它的价值需要在ChatActivity
中体现出来。
5.1 监听ConnectionManager
实现对ConnectionManager
各个状态的监听,当ConnectionManager
的状态有变化、收到发送的数据时,需要让ChatActivity
知道,它才能将各种变化反应到用户界面上。
5.1.1 创建监听器
ConnectionManager
定义了ConnectionListener
接口,状态变化、数据的接收可以通过这个接口获得。创建一个ConnectionListener
监听器,
public class ChatActivity extends AppCompatActivity { ...... private ConnectionManager.ConnectionListener mConnectionListener = new ConnectionManager.ConnectionListener() { @Override public void onConnectStateChange(int oldState, int State) { } @Override public void onListenStateChange(int oldState, int State) { } @Override public void onSendData(boolean suc, byte[] data) { } @Override public void onReadData(byte[] data) { } }; ......}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
5.1.2 创建Handler
因为监听器触发的函数不一定是在UI线程被调用的,例如onConnectStateChange()
,所以不能在监听器当中对界面做修改,必须把界面更新的任务交给UI线程进行。
安卓系统提供了Handler
的机制,让其它非UI线程能通过Handler
把界面更新的操作,从工作线程布置给主线程完成。
创建一个能在主线程当中工作的Handler,
public class ChatActivity extends AppCompatActivity { ...... private final static int MSG_SENT_DATA = 0; private final static int MSG_RECEIVE_DATA = 1; private final static int MSG_UPDATE_UI = 2; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SENT_DATA: { } break; case MSG_RECEIVE_DATA: { } break; case MSG_UPDATE_UI: { } break; } } }; ......}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
将ConnectionManager
通知的内容,转交给主线程的Handler
处理,
private ConnectionManager.ConnectionListener mConnectionListener = new ConnectionManager.ConnectionListener() { @Override public void onConnectStateChange(int oldState, int State) { mHandler.obtainMessage(MSG_UPDATE_UI).sendToTarget(); } @Override public void onListenStateChange(int oldState, int State) { mHandler.obtainMessage(MSG_UPDATE_UI).sendToTarget(); } @Override public void onSendData(boolean suc, byte[] data) { mHandler.obtainMessage(MSG_SENT_DATA, suc?1:0, 0, data).sendToTarget(); } @Override public void onReadData(byte[] data) { mHandler.obtainMessage(MSG_RECEIVE_DATA, data).sendToTarget(); }};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
创建ConnectionManager
,添加监听,
private ConnectionManager mConnectionManager;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); ...... mConnectionManager = new ConnectionManager(mConnectionListener); ......}
5.2 启动与停止监听
当聊天应用运行起来的时候,需要开启对其它蓝牙设备可能接入的监听,
private ConnectionManager mConnectionManager;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); ...... mConnectionManager.startListen(); ......}
当应用退出的时候,要断开可能存在的连接并停止监听,
@Overrideprotected void onDestroy() { super.onDestroy(); mHandler.removeMessages(MSG_UPDATE_UI); mHandler.removeMessages(MSG_SENT_DATA); mHandler.removeMessages(MSG_RECEIVE_DATA); if(mConnectionManager != null) { mConnectionManager.disconnect(); mConnectionManager.stopListen(); }}
5.3 启动连接
5.3.1 通过选择设备主动连接
当用户点击菜单栏的启动连接
菜单项时,会启动DeviceListActivity
,让用户从刷新的列表中,选取一个希望连接的设备。用户选择后,会把选中设备的地址返回给ChatActivity
。
这样,就可以利用ConnectionManager
发起主动连接的请求了,
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == RESULT_CODE_BTDEVICE && resultCode == RESULT_OK) { String deviceAddr = data.getStringExtra("DEVICE_ADDR"); mConnectionManager.connect(deviceAddr); }}
之后,ConnectionManager
的各种状态变化,就会通过监听器ConnectionListener
,传递到ChatActivity
当中,据此更新界面就好了。
5.3.2 通过监听被动连接
这种情况,并不需要用户去做任何点击的操作。
之后,ConnectionManager
的各种状态变化,就会通过监听器ConnectionListener
,传递到ChatActivity
当中,据此更新界面就好了。
5.3.3 菜单项的改变
我们之前已经通过ChatActivity
的onCreateOptionsMenu()
方法,把菜单项添加到了菜单栏。现在需要菜单项随着ConnectionManager
状态的变化,跟着做变化了。
当监听器的onConnectStateChange()
或者onListenStateChange
被触发后,我们将变化通过Handler
通知到了UI线程,
private ConnectionManager.ConnectionListener mConnectionListener = new ConnectionManager.ConnectionListener() { @Override public void onConnectStateChange(int oldState, int State) { mHandler.obtainMessage(MSG_UPDATE_UI).sendToTarget(); } @Override public void onListenStateChange(int oldState, int State) { mHandler.obtainMessage(MSG_UPDATE_UI).sendToTarget(); } ......};---------------------------------------------private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { ...... case MSG_UPDATE_UI: { updateUI(); } break; } }};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
在更新UI到方法updateUI()
中,修改菜单项的显示,
private void updateUI(){ if(mConnectionManager == null) { return; } if(mConnectionMenuItem == null) { mMessageEditor.setEnabled(false); mSendBtn.setEnabled(false); return; } if(mConnectionManager.getCurrentConnectState() == ConnectionManager.CONNECT_STATE_CONNECTED) { mConnectionMenuItem.setTitle(R.string.disconnect); mMessageEditor.setEnabled(true); mSendBtn.setEnabled(true); } else if(mConnectionManager.getCurrentConnectState() == ConnectionManager.CONNECT_STATE_CONNECTING) { mConnectionMenuItem.setTitle(R.string.cancel); mMessageEditor.setEnabled(false); mSendBtn.setEnabled(false); } else if(mConnectionManager.getCurrentConnectState() == ConnectionManager.CONNECT_STATE_IDLE) { mConnectionMenuItem.setTitle(R.string.connect); mMessageEditor.setEnabled(false); mSendBtn.setEnabled(false); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
菜单项的响应也需要根据当前的连接状态,做进一步的修改,
@Overridepublic boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.connect_menu: { if(mConnectionManager.getCurrentConnectState() == ConnectionManager.CONNECT_STATE_CONNECTED) { mConnectionManager.disconnect(); } else if(mConnectionManager.getCurrentConnectState() == ConnectionManager.CONNECT_STATE_CONNECTING) { mConnectionManager.disconnect(); } else if(mConnectionManager.getCurrentConnectState() == ConnectionManager.CONNECT_STATE_IDLE) { Intent i = new Intent(ChatActivity.this, DeviceListActivity.class); startActivityForResult(i, RESULT_CODE_BTDEVICE); } } return true; ...... }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
5.4 发送与显示数据
聊天文字发送成功或者接收到对方发来的文字时,要显示到列表中。为此,我们需要专门设计一个Adapter
来展示它们。
5.4.1 文字信息的数据结构
首先定义一个记录每条信息的数据结构ChatMessage
,每一条消息要注明是由谁发来的,是自己还是对方,
public class ChatMessage { static public final int MSG_SENDER_ME = 0; static public final int MSG_SENDER_OTHERS = 1; public int messageSender; public String messageContent;}
5.4.2 信息展示的Adapter
我们采用类似微信聊天的样子来展示聊天内容。每条消息的背景图片是9patch形式的PNG图片,将它们放在res\drawable
目录中。针对不同的屏幕像素密度,设计了对应的图片,放到对应的drawable
目录下就行了。例如为xxhdip设计的背景图片就放在res\drawable-xxhdip
目录中。
这些图片可以在示例代码中获得。
定义展示对方发来信息的布局-others_list_item.xml
,
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="58dp" android:padding="5dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_device_bluetooth"/> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"> ---设置为1,让文字显示尽情利用右边区域 <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:id="@+id/message_content" android:padding="5dp" android:textSize="16sp" android:gravity="center_vertical" android:background="@drawable/others"/> </FrameLayout></LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
定义展示自己发送信息的布局-me_list_item.xml
,
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="58dp" android:padding="5dp" android:gravity="right"> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"> ---设置为1,让文字显示尽情利用左边区域 <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/message_content" android:gravity="center_vertical" android:layout_gravity="right" android:padding="5dp" android:textSize="16sp" android:background="@drawable/me"/> </FrameLayout> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_device_bluetooth"/></LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
定义Adapter
-MessageAdapter
,
public class MessageAdapter extends ArrayAdapter<ChatMessage> { private final LayoutInflater mInflater; private int mResourceMe; private int mResourceOthers; public MessageAdapter(Context context, int resourceMe, int resourceOthers) { super(context, 0); mInflater = LayoutInflater.from(context); mResourceMe = resourceMe; mResourceOthers = resourceOthers; } @Override public View getView(int position, View convertView, ViewGroup parent) { ChatMessage message = getItem(position); convertView = mInflater.inflate(message.messageSender == ChatMessage.MSG_SENDER_ME ? mResourceMe : mResourceOthers, parent, false); TextView name = (TextView) convertView.findViewById(R.id.message_content); name.setText(message.messageContent); return convertView; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
使用聊天列表,
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); ...... mMessageListView = (ListView) findViewById(R.id.message_list); MessageAdapter adapter = new MessageAdapter(this, R.layout.me_list_item, R.layout.others_list_item); mMessageListView.setAdapter(adapter); ......}
5.4.3 文字的发送
当连接建立以后,禁止点击的发送按钮和文字编辑框将被解禁。再文字编辑框中编辑好文字,点击发送按钮,就能将文字发送出去了。
为按钮创建监听器,当点击后,获取文字编辑框中的数据,再使用ConnectionManager
提供的接口,把数据发送出去,
private View.OnClickListener mSendClickListener = new View.OnClickListener() { @Override public void onClick(View v) { String content = mMessageEditor.getText().toString(); if(content != null) { content = content.trim(); if(content.length() > 0) { boolean ret = mConnectionManager.sendData(content.getBytes()); if(!ret) { Toast.makeText(ChatActivity.this, R.string.send_fail, Toast.LENGTH_SHORT).show(); } } } }};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
注册监听函数,
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); ...... mSendBtn = (ImageButton) findViewById(R.id.send_btn); mSendBtn.setOnClickListener(mSendClickListener); ......}
在ConnectionListener
的onSendData
回调方法中,将发送结果传递给UI线程,让UI线程把聊天内容更新到消息列表中,
private ConnectionManager.ConnectionListener mConnectionListener = new ConnectionManager.ConnectionListener() { ...... @Override public void onSendData(boolean suc, byte[] data) { mHandler.obtainMessage(MSG_SENT_DATA, suc?1:0, 0, data).sendToTarget(); } ......};---------------------------------------------private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SENT_DATA: { byte [] data = (byte []) msg.obj; boolean suc = msg.arg1 == 1; if(data != null && suc) { ChatMessage chatMsg = new ChatMessage(); chatMsg.messageSender = ChatMessage.MSG_SENDER_ME; chatMsg.messageContent = new String(data); MessageAdapter adapter = (MessageAdapter) mMessageListView.getAdapter(); adapter.add(chatMsg); adapter.notifyDataSetChanged(); mMessageEditor.setText(""); } } break; ...... } }};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
5.4.4 文字的接收
当接收到对方发来的消息时,ConnectionListener
的onReadData
回调方法,将发送结果传递给UI线程,让UI线程把聊天内容更新到消息列表中,
private ConnectionManager.ConnectionListener mConnectionListener = new ConnectionManager.ConnectionListener() { ...... @Override public void onReadData(byte[] data) { mHandler.obtainMessage(MSG_RECEIVE_DATA, data).sendToTarget(); }};-------------------------------------------------private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { ...... case MSG_RECEIVE_DATA: { byte [] data = (byte []) msg.obj; if(data != null) { ChatMessage chatMsg = new ChatMessage(); chatMsg.messageSender = ChatMessage.MSG_SENDER_OTHERS; chatMsg.messageContent = new String(data); MessageAdapter adapter = (MessageAdapter) mMessageListView.getAdapter(); adapter.add(chatMsg); adapter.notifyDataSetChanged(); } } break; ...... } }};
0 0