Xamarin.Forms ChatKit 的使用 快速搭建即时通讯 UI
大家完成好,本期为大家实现 Android 端 SocketChat 的实现(需要源码请滑到最下面有博主通讯方式)
项目介绍:
ChatKit 控件来源:
https://github.com/jingliancui/XamarinFormsChatKitSample
通信组件:Socket
组成部分:服务端(PC,Winforms) 客户端(PC,Winfrom,Xamarin.Forms Android)
画个简单的实现草图:
看过这张图相信会有点轻微的理解:
下面看代码实现:
第一步安装 依赖包
安装 NewGet 依赖包:
Install-Package XamarinLibrary.Xamarin.Android.ChatKit -Version 0.3.3
这个是 类似 QQ的聊天 UI
第二步 编写UI
代码量太多 就不一一展示
贴上重要代码:
Socket Client Code:
创建 一个单例XamarinSockectClient 类 防止客户端重复创建
0000100020003 在线列表协议头 ,
000200030006 好友消息协议头
/// <summary>
/// Sockect通讯客户端
/// </summary>
public class XamarinSockectClient
{
private static XamarinSockectClient _Singleton = null;
public static XamarinSockectClient CreateInstance()
{
if (_Singleton == null)
{
_Singleton = new XamarinSockectClient();
}
return _Singleton;
}
public static XamarinSockectClient CreateInstance(string Ip, string Port)
{
if (_Singleton == null)
{
_Singleton = new XamarinSockectClient(Ip, Port);
}
return _Singleton;
}
public string ServerIp = "192.168.1.105";
public string ServerPort = "9000";
//public XamarinSockectClient()
//{
// XamarinConnectToServer();
//}
public XamarinSockectClient(string Ip = "192.168.1.105", string Port = "9000")
{
ServerIp = Ip;
ServerPort = Port;
XamarinConnectToServer();
}
Socket socketClient = null;
Thread threadClient = null;
public const int SendBufferSize = 2 * 1024;
public const int ReceiveBufferSize = 8 * 1024;
private void XamarinConnectToServer()
{
//定义一个套字节监听 包含3个参数(IP4寻址协议,流式连接,TCP协议)
socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//获取文本框输入的服务端IP和Port
IPAddress serverIPAddress = IPAddress.Parse(ServerIp);
int serverPort = int.Parse(ServerPort);
IPEndPoint endpoint = new IPEndPoint(serverIPAddress, serverPort);
//向指定的ip和端口号的服务端发送连接请求 用的方法是Connect 不是Bind
socketClient.Connect(endpoint);
//创建一个新线程 用于监听服务端发来的信息
threadClient = new Thread(RecMsg);
//将窗体线程设置为与后台同步
threadClient.IsBackground = true;
//启动线程
threadClient.Start();
//登录成功
//txtMsg.AppendText("已与服务端建立连接,可以开始通信...\r\n");
// btnConnectToServer.Enabled = false;
}
/// <summary>
/// 接受服务端发来信息的方法
/// </summary>
private void RecMsg()
{
while (true) //持续监听服务端发来的消息
{
string strRecMsg = null;
int length = 0;
byte[] buffer = new byte[SendBufferSize];
try
{
//将客户端套接字接收到的字节数组存入内存缓冲区, 并获取其长度
length = socketClient.Receive(buffer);
}
catch (SocketException ex)
{
// txtMsg.AppendText("套接字异常消息:" + ex.Message + "\r\n");
// txtMsg.AppendText("服务端已断开连接\r\n");
break;
}
catch (Exception ex)
{
// txtMsg.AppendText("系统异常消息: " + ex.Message + "\r\n");
break;
}
//将套接字获取到的字节数组转换为人可以看懂的字符串
strRecMsg = Encoding.UTF8.GetString(buffer, 0, length);
string[] ServerValue= strRecMsg.Split('*');
switch (ServerValue[0])
{
case "0000100020003":
if (DevHelp.DeviceHelp.devDelegateUpdateUi != null)
{
DevHelp.DeviceHelp.devDelegateUpdateUi.InitUI(ServerValue[1]);
}
break;
case "000200030006":
if (DevHelp.DeviceHelp.devDelegateMessagesList != null)
{
DevHelp.DeviceHelp.devDelegateMessagesList.AddMsg(ServerValue[1]);
}
break;
default:
break;
}
//JsonConvert.DeserializeObject<MessageAmanager>()
//将文本框输入的信息附加到txtMsg中 并显示 谁,什么时间,换行,发送了什么信息 再换行
// txtMsg.AppendText("服务端在 " + GetCurrentTime() + " 给您发送了:\r\n" + strRecMsg + "\r\n");
}
}
/// <summary>
/// 发送字符串信息到服务端的方法
/// </summary>
private void ClientSendMsg(string sendMsg, byte symbol)
{
byte[] arrClientMsg = Encoding.UTF8.GetBytes(sendMsg);
//实际发送的字节数组比实际输入的长度多1 用于存取标识符
byte[] arrClientSendMsg = new byte[arrClientMsg.Length + 1];
arrClientSendMsg[0] = symbol; //在索引为0的位置上添加一个标识符
Buffer.BlockCopy(arrClientMsg, 0, arrClientSendMsg, 1, arrClientMsg.Length);
socketClient.Send(arrClientSendMsg);
// txtMsg.AppendText("SoFlash:" + GetCurrentTime() + "\r\n" + sendMsg + "\r\n");
}
//向服务端发送信息
public void XamarinSend(string Message)
{
ClientSendMsg(Message, 0);
}
//获取在线用户信息
public void XamarinGetUserInfo()
{
ClientSendMsg("001001001", 5);
}
//发送登录信息
public void XamarinSendUserLogin()
{
string MyLogin = XamarinMessageAmanager("", $"{DeviceHelp.MyUserID}@{DevHelp.DeviceHelp.MyUserName}");
ClientSendMsg($"002002002#{MyLogin}", 4);
}
快捷键 Enter 发送信息
//private void txtCMsg_KeyDown(object sender, KeyEventArgs e)
//{ //当光标位于输入文本框上的情况下 发送信息的热键为回车键Enter
// if (e.KeyCode == Keys.Enter)
// {
// //则调用客户端向服务端发送信息的方法
// ClientSendMsg(txtCMsg.Text, 0);
// }
//}
string filePath = null; //文件的全路径
string fileName = null; //文件名称(不包含路径)
选择要发送的文件
//private void XamarinSelectFile(object sender, EventArgs e)
//{
// OpenFileDialog ofDialog = new OpenFileDialog();
// if (ofDialog.ShowDialog(this) == DialogResult.OK)
// {
// fileName = ofDialog.SafeFileName; //获取选取文件的文件名
// txtFileName.Text = fileName; //将文件名显示在文本框上
// filePath = ofDialog.FileName; //获取包含文件名的全路径
// }
//}
/// <summary>
/// 发送文件的方法
/// </summary>
/// <param name="fileFullPath">文件全路径(包含文件名称)</param>
private void XamarinSendFile(string fileFullPath)
{
if (string.IsNullOrEmpty(fileFullPath))
{
// MessageBox.Show(@"请选择需要发送的文件!");
return;
}
//发送文件之前 将文件名字和长度发送过去
long fileLength = new FileInfo(fileFullPath).Length;
string totalMsg = string.Format("{0}-{1}", fileName, fileLength);
ClientSendMsg(totalMsg, 2);
//发送文件
byte[] buffer = new byte[SendBufferSize];
using (FileStream fs = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read))
{
int readLength = 0;
bool firstRead = true;
long sentFileLength = 0;
while ((readLength = fs.Read(buffer, 0, buffer.Length)) > 0 && sentFileLength < fileLength)
{
sentFileLength += readLength;
//在第一次发送的字节流上加个前缀1
if (firstRead)
{
byte[] firstBuffer = new byte[readLength + 1];
firstBuffer[0] = 1; //告诉机器该发送的字节数组为文件
Buffer.BlockCopy(buffer, 0, firstBuffer, 1, readLength);
socketClient.Send(firstBuffer, 0, readLength + 1, SocketFlags.None);
firstRead = false;
continue;
}
//之后发送的均为直接读取的字节流
socketClient.Send(buffer, 0, readLength, SocketFlags.None);
}
fs.Close();
}
// txtMsg.AppendText("SoFlash:" + GetCurrentTime() + "\r\n您发送了文件:" + fileName + "\r\n");
}
//点击文件发送按钮 发送文件
private void btnSendFile_Click(object sender, EventArgs e)
{
XamarinSendFile(filePath);
}
/// <summary>
/// 获取当前系统时间
/// </summary>
public DateTime GetCurrentTime()
{
DateTime currentTime = new DateTime();
currentTime = DateTime.Now;
return currentTime;
}
//关闭客户端
private void btnExit_Click(object sender, EventArgs e)
{
//Application.Exit();
}
/// <summary>
/// 封装发送消息
/// </summary>
/// <param name="SReceiveUserID"></param>
/// <param name="SMessage"></param>
/// <returns></returns>
public string XamarinMessageAmanager(string SReceiveUserID, string SMessage)
{
MessageAmanager message = new MessageAmanager() { Message = SMessage, ReceiveUserID = SReceiveUserID, SendUserID = DeviceHelp.MyUserID };
return JsonConvert.SerializeObject(message);
}
}
/// <summary>
///消息管理
/// </summary>
public class MessageAmanager
{
/// <summary>
/// 发送者ID
/// </summary>
public string SendUserID { get; set; }
/// <summary>
/// 消息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 接收者ID
/// </summary>
public string ReceiveUserID { get; set; }
}
NavigationClass UI 页面跳出工具类
public static class NavigationClass
{
/// <summary>
/// 创建副本
/// </summary>
public static INavigation Navigation { get; set; }
/// <summary>
/// 跳转页面
/// </summary>
/// <param name="obj"></param>
public static void PushAsync(this Page obj)
{
Navigation.PushAsync(obj, true);
}
/// <summary>
/// 关闭窗口
/// </summary>
/// <param name="obj"></param>
public static void PopAsync(this Page obj)
{
Navigation.PopAsync(true);
}
public static void RemovePage(this Page obj)
{
Navigation.RemovePage(obj);
}
}
DeviceHelp 设备参数 UI委托 文件读写工具类
public class DeviceHelp
{
/// <summary>
/// 客户端唯一ID
/// </summary>
public static string MyUserID = Guid.NewGuid().ToString("N");
/// <summary>
/// 宽度
/// </summary>
public static double ScreenWidth { get; set; }
/// <summary>
/// 高度
/// </summary>
public static double ScreenHeight { get; set; }
/// <summary>
/// 密度
/// </summary>
public static double Density { get; set; }
/// <summary>
/// 上下文对象
/// </summary>
public static Context Context { get; set; }
/// <summary>
/// Sockect 实例化对象
/// </summary>
public static XamarinSockectClient xamarinSockectClient { get; set; }
/// <summary>
/// UI委托
/// </summary>
public static DevDelegateUpdateUi devDelegateUpdateUi { get; set; }
/// <summary>
/// 消息列表
/// </summary>
public static DevDelegateMessagesList devDelegateMessagesList { get; set; }
/// <summary>
/// 用户名
/// </summary>
public static string MyUserName { get; set; }
/// <summary>
/// 更新标题
/// </summary>
public static DevDelegateTitle devDelegateTitle { get; set; }
//var width = Resources.DisplayMetrics.WidthPixels;
//var height = Resources.DisplayMetrics.HeightPixels;
//var density = Resources.DisplayMetrics.Density; //
public void SaveImage(string filepath)
{
var imageData = System.IO.File.ReadAllBytes(filepath);
var dir = Android.OS.Environment.GetExternalStoragePublicDirectory(
Android.OS.Environment.DirectoryDcim);
var pictures = dir.AbsolutePath;
var filename = System.DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".jpg";
var newFilepath = System.IO.Path.Combine(pictures, filename);
System.IO.File.WriteAllBytes(newFilepath, imageData);
//mediascan adds the saved image into the gallery
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new Java.IO.File(newFilepath)));
DevHelp.DeviceHelp.Context.SendBroadcast(mediaScanIntent);
}
/// <summary>
/// 保存文件
/// </summary>
/// <param name="filepath"></param>
/// <param name="Data"></param>
public static void SaveFile(string filepath, byte[] Data)
{
// var imageData = System.IO.File.ReadAllBytes(filepath);
var dir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim);
var pictures = dir.AbsolutePath;
// var filename = System.DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".jpg";
var newFilepath = System.IO.Path.Combine(pictures, filepath);
System.IO.File.WriteAllBytes(newFilepath, Data);
//mediascan adds the saved image into the gallery
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new Java.IO.File(newFilepath)));
DevHelp.DeviceHelp.Context.SendBroadcast(mediaScanIntent);
}
/// <summary>
/// 读取指定文件
/// </summary>
/// <param name="filepath"></param>
/// <returns></returns>
public static byte[] ReadFile(string filepath)
{
try
{
var Data = System.IO.File.ReadAllBytes(filepath);
return Data;
}
catch (Exception ex)
{
return null;
}
}
}
public class DevDelegateUpdateUi
{
public delegate void InitUi(string message);
public InitUi InitUI;
}
public class DevDelegateMessagesList
{
public delegate void InitUi(string message);
public InitUi AddMsg;
}
public class DevDelegateTitle
{
public delegate void UpdateTitle(string Title);
public UpdateTitle updateTitle;
}
DialogsListControlRenderer 在线列表数据更新实现
IOnDialogClickListener 列表单击事件接口
必须注册: dialogsListAdapter.OnDialogClickListener = this;
public class DialogsListControlRenderer : ViewRenderer<DialogsListControl, LinearLayout>, IOnDialogClickListener
{
public DialogsListControlRenderer(Context context) : base(context)
{
}
private Com.Stfalcon.Chatkit.Dialogs.DialogsList dialogsList;
private LinearLayout linearLayout;
private Com.Stfalcon.Chatkit.Dialogs.DialogsListAdapter dialogsListAdapter;
protected override void OnElementChanged(ElementChangedEventArgs<DialogsListControl> e)
{
DevDelegateUpdateUi devDelegateUpdateUi = new DevDelegateUpdateUi();
devDelegateUpdateUi.InitUI += new DevDelegateUpdateUi.InitUi(AddItem);
DeviceHelp.devDelegateUpdateUi = devDelegateUpdateUi;
var x = Inflate(Context, Resource.Layout.DialogsListLayout, null);
x.Click += DialogsListView_Click;
if (linearLayout == null)
{
linearLayout = x as LinearLayout;
}
if (dialogsList == null)
{
var dialogsListView = linearLayout.FindViewById<Com.Stfalcon.Chatkit.Dialogs.DialogsList>(Resource.Id.dialogsList1);
dialogsList = dialogsListView;
}
dialogsList.Click += DialogsListView_Click;
dialogsList.LongClick += DialogsListView_Click;
dialogsListAdapter = new Com.Stfalcon.Chatkit.Dialogs.DialogsListAdapter(new SampleImageLoader(Context));
dialogsList.SetAdapter(dialogsListAdapter);
DevHelp.DeviceHelp.xamarinSockectClient.XamarinGetUserInfo();
//JsonConvert.DeserializeObject<MessageAmanager>()
linearLayout.Click += DialogsListView_Click;
dialogsListAdapter.OnDialogClickListener = this;
SetNativeControl(linearLayout);
}
public void AddItem(string message)
{
Device.BeginInvokeOnMainThread(() =>
{
string[] strUser = message.Split("#");
dialogsListAdapter.Clear();
foreach (var item in strUser)
{
var itValue = item.Split('@');
SampleUser sample1 = new SampleUser();
sample1.Avatar = itValue[1];
sample1.Id = itValue[0];
sample1.Name = itValue[1];
SampleMessage message1 = new SampleMessage {Id= itValue[0],Text="您有新的消息!!!", User= sample1 };
SampleDialog sample = new SampleDialog(sample1, message1) { Id= itValue[0] ,DialogName= itValue[1] };
dialogsListAdapter.AddItem(sample);
}
});
}
///列表单击事件
public void OnDialogClick(Java.Lang.Object p0)
{
MessagesListPage messagesListPage = new MessagesListPage();
UserInfoHelp.SampleDialog = p0 as SampleDialog;
messagesListPage.PushAsync();
}
///列表长按事件
public void OnDialogLongClick(Java.Lang.Object p0)
{
}
}
MessagesListControlRenderer 消息动态更新实现
IOnMessageClickListener 消息单击事件接口
IOnMessageLongClickListener 消息长按事件接口
接口注册不然不会触发 messagesListAdapter.SetOnMessageClickListener(this);
messagesListAdapter.SetOnMessageLongClickListener(this);
//好友聊天页面标题 方法注册
DeviceHelp.devDelegateTitle.updateTitle(UserInfoHelp.SampleDialog.DialogName);
//好友消息动态更新方法注册
DevDelegateMessagesList devDelegateMessages = new DevDelegateMessagesList();
devDelegateMessages.AddMsg += new DevDelegateMessagesList.InitUi(AddFendMsg);
DeviceHelp.devDelegateMessagesList = devDelegateMessages;
public class MessagesListControlRenderer : ViewRenderer<MessagesListControl, LinearLayout>, IOnMessageClickListener, IOnMessageLongClickListener
{
public MessagesListControlRenderer(Context context) : base(context)
{
DevHelp.DeviceHelp.Context = context;
}
private LinearLayout linearLayout;
private Com.Stfalcon.Chatkit.Messages.MessagesList messagesList;
private Com.Stfalcon.Chatkit.Messages.MessagesListAdapter messagesListAdapter;
protected override void OnElementChanged(ElementChangedEventArgs<MessagesListControl> e)
{
var x = Inflate(Context, Resource.Layout.MessagesListLayout, null);
// MessagesListControl messagesListControl= e.NewElement;
// messagesListControl.AddMsg += AddMsg;
MessageManager message = new MessageManager();
message.AddMsg += AddMsg;
MessageHelpObj.messageManager = message;
DevDelegateMessagesList devDelegateMessages = new DevDelegateMessagesList();
devDelegateMessages.AddMsg += new DevDelegateMessagesList.InitUi(AddFendMsg);
DeviceHelp.devDelegateMessagesList = devDelegateMessages;
if (linearLayout == null)
{
linearLayout = x as LinearLayout;
}
if (messagesList == null)
{
var messagesListView = linearLayout.FindViewById<Com.Stfalcon.Chatkit.Messages.MessagesList>(Resource.Id.messagesList);
messagesList = messagesListView;
}
//Guid.NewGuid().ToString()
messagesListAdapter = new Com.Stfalcon.Chatkit.Messages.MessagesListAdapter(DeviceHelp.MyUserID, new SampleImageLoader(Context));
messagesList.SetAdapter(messagesListAdapter);
messagesListAdapter.SetOnMessageClickListener(this);
messagesListAdapter.SetOnMessageLongClickListener(this);
SetNativeControl(linearLayout);
DeviceHelp.devDelegateTitle.updateTitle(UserInfoHelp.SampleDialog.DialogName);
}
//public string guid = Guid.NewGuid().ToString();
public string xAvatar = "xiaoming";
public string xName = "张三";
/// <summary>
/// 增加消息
/// </summary>
/// <param name="message"></param>
public void AddMsg(SampleMessageControl message)
{
SampleMessage sample = new SampleMessage { Text = message.Text, Id = DeviceHelp.MyUserID };
sample.User = new SampleUser() { Id = DeviceHelp.MyUserID, Avatar = xAvatar, Name = xName };
messagesListAdapter.AddToStart(sample, true);
}
/// <summary>
/// 增加消息
/// </summary>
/// <param name="message"></param>
public void AddFendMsg(string message)
{
Device.BeginInvokeOnMainThread(() =>
{
MessageBox box = JsonConvert.DeserializeObject<MessageBox>(message);
SampleMessage sample = new SampleMessage { Text = box.Message, Id = box.SendID };
sample.User = new SampleUser() { Id = box.SendID, Avatar = box.Name, Name = box.Name };
messagesListAdapter.AddToStart(sample, true);
});
}
public void OnMessageClick(Java.Lang.Object p0)
{
}
public void OnMessageLongClick(Java.Lang.Object p0)
{
p0.NotifyAll();
}
}
public class MessageBox
{
/// <summary>
/// 接收人
/// </summary>
public string RID { get; set; }
public string Message { get; set; }
public string Name { get; set; }
/// <summary>
/// 发送人
/// </summary>
public string SendID { get; set; }
}
MessageInputControlRenderer 发送消息实现
IInputListener 消息提交 接口
//OnSubmit 发送信息
接口注册 messageInput.SetInputListener(this);
// 发送消息到服务器方法实现
DevHelp.DeviceHelp.xamarinSockectClient.XamarinSend(Message);
public class MessageInputControlRenderer : ViewRenderer<MessageInputControl, LinearLayout>, IInputListener
{
public MessageInputControlRenderer(Context context) : base(context)
{
}
private Com.Stfalcon.Chatkit.Messages.MessageInput messageInput;
private LinearLayout linearLayout;
protected override void OnElementChanged(ElementChangedEventArgs<MessageInputControl> e)
{
var x = Inflate(Context, Resource.Layout.MessageInputLayout, null);
if (linearLayout == null)
{
linearLayout = x as LinearLayout;
}
if (messageInput == null)
{
var messageInputView = linearLayout.FindViewById<Com.Stfalcon.Chatkit.Messages.MessageInput>(Resource.Id.theMessageInput);
messageInput = messageInputView;
}
messageInput.SetInputListener(this);
SetNativeControl(linearLayout);
}
public bool OnSubmit(ICharSequence p0)
{
var msg = p0.ToString();
SampleMessageControl sample = new SampleMessageControl { Text = msg };
MessageHelpObj.messageManager.AddMsg(sample);
MessageBox box = new MessageBox { RID = UserInfoHelp.SampleDialog.Id, SendID = DevHelp.DeviceHelp.MyUserID, Message = p0.ToString(), Name = DevHelp.DeviceHelp.MyUserName };
var Sedmsg = JsonConvert.SerializeObject(box);
string Message = "000200030006#"+ Sedmsg;
DevHelp.DeviceHelp.xamarinSockectClient.XamarinSend(Message);
return true;
}
}
##获取设备宽度,高度 屏幕 密度
代码实现:
var width = Resources.DisplayMetrics.WidthPixels; //高度
var height = Resources.DisplayMetrics.HeightPixels;//宽度
var density = Resources.DisplayMetrics.Density; //屏幕密度
DeviceHelp.ScreenWidth = width / density; //屏幕宽度
DeviceHelp.ScreenHeight = height / density; //屏幕高度 含24个单位的标题栏高度 通过OnSizeAllocated获取的高度不含标题栏高度
DeviceHelp.Density = density;
以上是代码实现:
下面展示实际效果:
好友列表
好友Caozhen001 与好友Caozhen002 对话实现
好友Caozhen002与好友Caozhen001 对话实现
这里是客户端的实现 下期介绍 服务端的实现
本期就到这里 谢谢大家,不足之处请指点一二;