转载请带上原著连接哦~~
http://blog.youkuaiyun.com/lhk147852369/article/details/78616341
弄了2天做出来的聊天,真的是心痛,也没什么难得东西,主要还是不熟悉,就会出错,很尴尬!
先放松一下:来段舞蹈吧!
WebSocket简介
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。
话不多说,先上图:
不过图片的话也没有多少张,简单聊天而已嘛
对了,代码写的有点粗糙,咱就这水平,不喜勿喷啊
这次尽量发全点大家可以直接使用,是我所希望的
这是采用recycleview绘制的聊天界面:
我们发送文字传递到后台,后台会收到我们穿的json去进行解析,进行记录
好了图片也就这样了,没什么毛病,后续我们可以自行添加动态效果。
ok进入正题,走着您呢~
主界面如下:
两张.9图 放在mipmap的xhdpi中,虽然会报红,但是没关系,继续用就可以了
在Drawable图片会被放大的
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F6F6"
tools:context="com.lgoutech.chatuidesigndemo.MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:gravity="center_vertical"
android:background="#00A9FF"
android:layout_alignParentTop="true"
android:id="@+id/tab_top"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="客户"
android:textSize="18sp"
android:layout_centerInParent="true"
android:textColor="#FFF"
/>
</RelativeLayout>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/chat_recy"
android:layout_below="@+id/tab_top"
android:layout_above="@+id/chat_lay"
>
</android.support.v7.widget.RecyclerView>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#B2B2B2"
android:layout_above="@+id/chat_lay"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:background="#F9F9F9"
android:gravity="center_vertical"
android:padding="3dp"
android:id="@+id/chat_lay"
>
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:src="@mipmap/icon_chat_add"
android:layout_weight="1"
android:id="@+id/img_add"
/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="8"
android:background="@drawable/chat_edit_bg"
android:layout_margin="6dp"
android:gravity="center"
>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:padding="3dp"
android:textSize="14sp"
android:id="@+id/edit_context"
android:maxLines="3"
/>
</LinearLayout>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="发送"
android:layout_weight="1.5"
android:textSize="18sp"
android:gravity="center"
android:id="@+id/txt_send"
android:textColor="@color/text_send_press"
/>
</LinearLayout>
</RelativeLayout>
记得添加网络权限哦!!!
所需要用的第三方框架
compile 'com.android.support:recyclerview-v7:26.1.0'
compile "org.java-websocket:Java-WebSocket:1.3.6"
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
主界面MainActivity: 已经做了相关的注释
public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
private static final int STATUS_MESSAGE = 0x00;
private RecyclerView chatRecy;
private ContextMsg msg;
private List<ContextMsg> msgList;
private ChatRecyAdapter chatRecyAdapter;
private EditText editContext;
private TextView txtSend;
//大家自己找吧,我用的内网,大家也用不了的,哈哈
private static final String url = "ws://服务端地址:端口97/";
private URI uri;
JWebSClient client;
private Handler mhandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String mess = (String) msg.obj;
switch (msg.what) {
case STATUS_MESSAGE:
setListSent(mess, ContextMsg.TYPE_RECEIVED);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initSocketClient();
initView();
initCharRecy();
}
@Override
protected void onDestroy() {
super.onDestroy();
//activity 销毁时 关闭连接
closeConnect();
}
private void initView() {
chatRecy = findViewById(R.id.chat_recy);
editContext = findViewById(R.id.edit_context);
txtSend = findViewById(R.id.txt_send);
txtSend.setOnClickListener(this);
chatRecy.setOnTouchListener(this);
//这里是对发送按钮进行了处理,用户体验吗 ,你们懂得
editContext.addTextChangedListener(new MyTxtSendWatcher());
}
private void initCharRecy() {
chatRecyAdapter = new ChatRecyAdapter(MainActivity.this, getList());
chatRecy.setLayoutManager(new LinearLayoutManager(this));
chatRecy.setAdapter(chatRecyAdapter);
}
//模拟数据
private List<ContextMsg> getList() {
msgList = new ArrayList<>();
msgList.add(new ContextMsg("你好", ContextMsg.TYPE_RECEIVED));
msgList.add(new ContextMsg("hello", ContextMsg.TYPE_SENT));
msgList.add(new ContextMsg("见到你很高兴", ContextMsg.TYPE_RECEIVED));
msgList.add(new ContextMsg("me to", ContextMsg.TYPE_SENT));
return msgList;
}
//这里设置文字显示在recycle
private void setListSent(String send, int type) {
if (type == ContextMsg.TYPE_SENT) {
msgList.add(new ContextMsg(send, ContextMsg.TYPE_SENT));
editContext.setText("");
} else {
msgList.add(new ContextMsg(send, ContextMsg.TYPE_RECEIVED));
}
chatRecyAdapter = new ChatRecyAdapter(MainActivity.this, msgList);
chatRecyAdapter.notifyDataSetChanged();
chatRecy.scrollToPosition(chatRecyAdapter.getItemCount() - 1);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.txt_send:
String editText = editContext.getText().toString();
setListSent(editText, ContextMsg.TYPE_SENT);
sendJsonObject(editText);
break;
}
}
private void senJsonInit() {
try {
JSONObject msg = new JSONObject();
msg.put("from", 2);
JSONObject init = new JSONObject();
init.put("type", "init");
init.put("msg", msg);
sendMsg(init.toString());
Log.e("init", "Init初始化成功");
} catch (JSONException e) {
e.printStackTrace();
}
}
private void sendJsonObject(String context) {
try {
JSONObject msg = new JSONObject();
msg.put("to", 1);
msg.put("content", context);
msg.put("from", 2);
msg.put("headimg", "");
JSONObject toOne = new JSONObject();
toOne.put("type", "msg");
toOne.put("msg", msg);
sendMsg(toOne.toString());
} catch (JSONException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()) {
case R.id.chat_recy:
//在我们的recycleview中 触摸到后 会隐藏输入法键盘的
hideSoftInput(MainActivity.this, v);
break;
}
return false;
}
//显示输入法
public static void showSoftInput(Context context, View view) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
}
//隐藏输入法
public static void hideSoftInput(Context context, View view) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
//打开socket连接
private void initSocketClient() {
uri = URI.create(url);
client = new JWebSClient(uri) {
@Override
public void onMessage(String message) {
Log.e("onMessage", message);
ReceivedMsg receivedMsg = GsonManager.getGson(message, ReceivedMsg.class);
Message msg = new Message();
msg.what = STATUS_MESSAGE;
msg.obj = receivedMsg.getMsg().getContent();
mhandler.sendMessage(msg);
}
};
connect();
}
//连接
private void connect() {
new Thread() {
@Override
public void run() {
try {
client.connectBlocking();
Log.e("connectBlocking", "连接成功");
if(client.isOpen()){
senJsonInit();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
//断开连接
private void closeConnect() {
try {
if (null != client) {
client.close();
}
} catch (Exception e) {
e.printStackTrace();
Log.e("Socket", "断开连接异常");
} finally {
client = null;
}
}
//发送消息
/**
*
*/
private void sendMsg(String msg) {
if (null != client) {
client.send(msg);
Log.e("发送的消息", msg);
}
}
private class MyTxtSendWatcher implements TextWatcher{
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(TextUtils.isEmpty(editContext.getText())){
txtSend.setClickable(false);
txtSend.setTextColor(getResources().getColor(R.color.text_send_press));
}else {
txtSend.setClickable(true);
txtSend.setTextColor(getResources().getColor(R.color.text_send_normal));
}
}
@Override
public void afterTextChanged(Editable s) {
}
}
}
接下来使我们的websocket,因为我们使用的是java_websocket所以使用方法如下:
public class JWebSClient extends WebSocketClient {
public JWebSClient(URI serverUri) {
super(serverUri,new Draft_6455());
}
@Override
public void onOpen(ServerHandshake handshakedata) {
Log.e("JWebSClient", "连接打开onOpen");
}
@Override
public void onMessage(String message) {
Log.e("JWebSClient", message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
Log.e("JWebSClient", "关闭 断开连接onClose");
}
@Override
public void onError(Exception ex) {
Log.e("JWebSClient", "错误 onError");
}
}
很简单。也有了注释的,在mainActivity中使用的
对于recycleview的anapter:
public class ChatRecyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<ContextMsg> mList;
private Context mContext;
private LayoutInflater inflater;
public ChatRecyAdapter(Context context, List<ContextMsg> list) {
mList = list;
mContext = context;
inflater = LayoutInflater.from(context);
}
//onCreateViewHolder()用于创建ViewHolder实例
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ContextMsg.TYPE_SENT) {
View view = inflater.inflate(R.layout.item_right_chat, parent, false);
RightHolder rightHolder = new RightHolder(view);
return rightHolder;
}else if (viewType == ContextMsg.TYPE_RECEIVED){
View view = inflater.inflate(R.layout.item_left_chat,parent,false);
LeftHolder leftHolder = new LeftHolder(view);
return leftHolder;
}
return null;
}
//onBindViewHolder()用于对RecyclerView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof RightHolder) {
RightHolder rightHolder = (RightHolder) holder;
if (mList != null) {
rightHolder.tvChatContent.setText(mList.get(position).getContent());
rightHolder.imgChatHead.setImageResource(R.mipmap.head_img);
}
}else if (holder instanceof LeftHolder){
LeftHolder leftHolder = (LeftHolder) holder;
if (mList!=null){
leftHolder.tvChatContent.setText(mList.get(position).getContent());
leftHolder.imgChatHead.setImageResource(R.mipmap.head_img);
}
}
}
@Override
public int getItemCount() {
if (mList!=null)
return mList.size();
else
return 0;
}
@Override
public int getItemViewType(int position) {
return mList.get(position).getType();
}
//左布局
public class LeftHolder extends RecyclerView.ViewHolder {
private TextView tvChatContent;
private ImageView imgChatHead;
public LeftHolder(View itemView) {
super(itemView);
tvChatContent = itemView.findViewById(R.id.chat_content_text);
imgChatHead = itemView.findViewById(R.id.chat_item_header);
}
}
//右布局
public class RightHolder extends RecyclerView.ViewHolder {
private TextView tvChatContent;
private ImageView imgChatHead;
public RightHolder(View itemView) {
super(itemView);
tvChatContent = itemView.findViewById(R.id.chat_content_text);
imgChatHead = itemView.findViewById(R.id.chat_item_header);
}
}
}
看到这里有人问了,两个布局,一样为什么要这么写呢?
为什么,我哪知道~
写别的方式,holder找不到你的控件,怎么办,有大神可以给一下解决方式吗?谢谢了
接下来就是从后台传递过来的数据,实体:
public class ReceivedMsg {
private String type;
private ContextMsg msg;
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setMsg(ContextMsg msg) {
this.msg = msg;
}
public ContextMsg getMsg() {
return msg;
}
public class Msg {
private int to;
private String content;
private int from;
private String headimg;
public void setTo(int to) {
this.to = to;
}
public int getTo() {
return to;
}
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setFrom(int from) {
this.from = from;
}
public int getFrom() {
return from;
}
public void setHeadimg(String headimg) {
this.headimg = headimg;
}
public String getHeadimg() {
return headimg;
}
}
}
Gson工具解析json数据,已封装,使用方法就在代码中
public class GsonManager<T>{
public static <T>T getGson(String json,Class<T> tClass){
Gson gson=new Gson();
return gson.fromJson(json,tClass);
}
public static <T> String mapToJson(Map<String, T> map) {
Gson gson = new Gson();
String jsonStr = gson.toJson(map);
return jsonStr;
}
}
大家根据自己需要,适当修改即可!
注意:
client.connectBlocking();
Log.e("connectBlocking", "连接成功");
if(client.isOpen()){
senJsonInit();
}
跟大家提一下,这里用到了connectBlocking 而没有用connect为什么呢?
1.因为后台的原因,我是需要在连接成功后传递一下我是哪位用户的,所以在连接成功后我会想后台发送一个json
2.所以这里Blocking 会多出一个等待操作,然后我判断了连接打没打开,进行发送数据,否则会报错的哦,
3.错误原因在于未连接就发送数据,这当然会报错。。。。。
public boolean connectBlocking() throws InterruptedException {
connect();
connectLatch.await();
return engine.isOpen();
}
感谢各位!请认准中国驰名品牌哦~
谢谢各位老铁支持~