Android 极光IM简单的聊天界面全手动实现

说到实时通讯,很多人都想到融云,极光,环信,网易啊等等一系列,

因为需求原因,我们最近的项目呢是用的极光.

由于极光的界面Demo十分繁琐,很多功能我们用不到,所以我干脆自己写了会话列表和聊天界面

首先呢,消息展示和踏板是这样的:

接下来说一下实现的过程:

极光IM的集成呢我这边就不多说了,官网写的又简单又详细.

界面的XML

以下是Activity的布局文件,消息列表我选择用RecyclerView来实现

<LinearLayout 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="wrap_content"android:orientation="vertical"android:background="@color/beijing"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="@dimen/Title_Height"android:background="@color/white"><ImageViewandroid:id="@+id/jg_details_back"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_centerVertical="true"android:paddingLeft="30px"android:paddingRight="60px"android:src="@mipmap/back" /><TextViewandroid:id="@+id/jg_details_title"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text=""android:textColor="@color/heise"android:textSize="@dimen/Title_TextSize" /></RelativeLayout><android.support.v7.widget.RecyclerViewandroid:id="@+id/jg_details_recy"android:layout_width="match_parent"android:layout_height="0px"android:layout_weight="1"/><TextViewandroid:layout_width="match_parent"android:layout_height="1px"android:background="@color/fenge" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:background="@color/white"android:paddingLeft="30px"android:paddingRight="30px"><EditTextandroid:id="@+id/jg_details_edit"android:layout_width="0px"android:layout_height="wrap_content"android:layout_marginBottom="20px"android:layout_marginTop="20px"android:layout_weight="1"android:background="@mipmap/sousuo1"android:gravity="center_vertical"android:hint="请输入咨询的问题"android:imeOptions="actionSend"android:paddingLeft="20px"android:paddingRight="20px"android:paddingTop="10px"android:paddingBottom="10px"android:singleLine="true"android:textColor="@color/huise"android:textCursorDrawable="@null"android:textSize="14sp" /><ImageViewandroid:id="@+id/jg_details_img"android:layout_width="80px"android:layout_marginTop="20px"android:layout_height="80px"android:layout_marginLeft="20px"android:src="@mipmap/send_img" /></LinearLayout>
</LinearLayout>

控件初始化:

这些代码 放在Activity的onCreate方法中就可以了,

其中涉及到的东西都会在下面讲到

        title = findViewById(R.id.jg_details_title);mEdit = findViewById(R.id.jg_details_edit);mRecycler = findViewById(R.id.jg_details_recy);mRecycler.setLayoutManager(new LinearLayoutManager(this));mAdapter = new JG_details_Adapter(this);mRecycler.setAdapter(mAdapter);position = getIntent().getIntExtra("position", 0);//设置消息接收 监听GlobalEventListener.setJG(this, false);//进入会话状态,不接收通知栏JMessageClient.enterSingleConversation(this.userName);

这边涉及到一个消息接收的监听类GlobalEventListener

这里面存放了两个静态的activity实体,一个是会话列表,一个是会话详情.

这样做呢,是方便消息接到的第一时间内,能调用到initData()来刷新数据

刚封装这个类的时候,刷新数据我是想用EventBus的,但是后来一想,定义两个静态对象要简明直接的多

/**作者:赵星海*时间:18/11/21 16:08*用途:极光IM消息接收处理*/
public class GlobalEventListener {private Context MainContext;private static Activity_JG JG_list = null; // 会话列表对象 private static Activity_JG_details JG_details = null;// 会话详情对象 public GlobalEventListener(Context context) {MainContext = context;JMessageClient.registerEventReceiver(this);}public static void setJG(Activity activity, boolean islist) {if (islist) {JG_list = (Activity_JG) activity;} else {JG_details = (Activity_JG_details) activity;}}//通知点击 前往会话列表public void onEvent(NotificationClickEvent event) {MainContext.startActivity(new Intent(MainContext, Activity_JG.class));}// 接收消息 (主线程)(刷新UI)public void onEventMainThread(MessageEvent event){if (JG_details != null) {JG_details.initData();} else if (JG_list != null) {JG_list.initData();}}}

接下来说一下数据加载,也就是监听到新数据调用的 initData() :

在activity中写一个这样的方法 负责数据加载和消息接收类 调用刷新

public void initData() {List<Conversation> msgList = JMessageClient.getConversationList();if (msgList != null) {if (msgList.size() > 0) {if (msgList.get(position) != null) {conversation = msgList.get(position);//重置会话未读消息数conversation.resetUnreadCount();}}}if (conversation != null) {title.setText(conversation.getTitle() == null ? "" : conversation.getTitle());UserInfo info = (UserInfo) conversation.getTargetInfo();userName = info.getUserName();//userName = "f8443445-a7ef-47d8-8005-b0d57851b396";  //todo 可自定义//使列表滚动到底部if (conversation.getAllMessage() != null) {if (conversation.getAllMessage().size() > 0) {mAdapter.setData(conversation.getAllMessage());//设置刷新不闪屏((SimpleItemAnimator) mRecycler.getItemAnimator()).setSupportsChangeAnimations(false);if (one) {mAdapter.notifyDataSetChanged();} else {mAdapter.notifyItemInserted(conversation.getAllMessage().size() - 1);}mRecycler.scrollToPosition(conversation.getAllMessage().size() - 1);}}mAdapter.setOnItemClickListener(new JG_details_Adapter.OnItemClickListener() {@Overridepublic void onItemClick(View view, int position) {switch (view.getId()) {case R.id.item_jg_details_img:ImageContent imageContent = (ImageContent) conversation.getAllMessage().get(position).getContent();startActivity(new Intent(Activity_JG_details.this, Activity_img.class).putExtra("ImgUrl", imageContent.getLocalThumbnailPath()));overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);//动画break;}}});}one=false; // 代表不是第一次initData}

接下来呢在看一下Adapter和ViewHolder

Adapter这边的逻辑相对简单,只是定义了一个点击响应

/*** Created by Xinghai.Zhao on 18/11/19.*/
/**作者:赵星海*用途: 极光聊天页面Adapter*/
public class JG_details_Adapter extends RecyclerView.Adapter {private OnItemClickListener mOnItemClickListener = null;private Context MyContext;private List<Message> mList;public JG_details_Adapter(Context context) {this.MyContext = context;}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {LayoutInflater from = LayoutInflater.from(MyContext);View view = from.inflate(R.layout.item_jg_details, parent, false);return new JG_details_holder(view, MyContext,mOnItemClickListener);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {JG_details_holder holder1 = (JG_details_holder) holder;if (mList!=null||mList.size()>0){holder1.setHolderData(mList.get(position),position);//将position保存在itemView的Tag中,以便点击时进行获取   ----------------------holder.itemView.setTag(position);}}@Overridepublic int getItemCount() {return mList == null ? 0 : mList.size();}public void setOnItemClickListener(OnItemClickListener listener) {//-------------this.mOnItemClickListener = listener;}public void setData(List<Message> data) {this.mList = data;}public void removeItem(int position) {mList.remove(position);}//define interfacepublic interface OnItemClickListener {  //--------------------------void onItemClick(View view, int position);}}

ViewHolder这边,我的逻辑是:

自己的消息显示 (已读/未读)状态,对方发来的消息则不显示.

/*** Created by Xinghai.Zhao on 18/04/02.*/
/**作者:赵星海*时间:18/11/27 17:52*用途:极光聊天页面Holder*/
public class JG_details_holder extends BaseViewHolder implements View.OnClickListener {private RoundedImageView MyImg;  //发送的图片private TextView MyTv_content, MyTV_Time, My_tc, My_tc1, My_Tv_state;private CircleImageView MyHead;private Context MyContext;private JG_details_Adapter.OnItemClickListener mOnItemClickLis = null;private View view;public JG_details_holder(View itemView, Context con, JG_details_Adapter.OnItemClickListener mOnItemClick) {super(itemView);MyContext = con;mOnItemClickLis = mOnItemClick;}@Overridepublic void findView(View view) {this.view = view;MyImg = this.view.findViewById(R.id.item_jg_details_img);//图片MyHead = view.findViewById(R.id.item_jg_details_head);  //头像MyTv_content = view.findViewById(R.id.item_jg_details_content);//内容MyTV_Time = view.findViewById(R.id.item_jg_details_time); // 时间My_tc = view.findViewById(R.id.item_jg_details_tc);My_tc1 = view.findViewById(R.id.item_jg_details_tc1);My_Tv_state = view.findViewById(R.id.item_jg_details_state);MyImg.setOnClickListener(this);MyHead.setOnClickListener(this);MyTv_content.setOnClickListener(this);MyTV_Time.setOnClickListener(this);My_Tv_state.setOnClickListener(this);}@TargetApi(Build.VERSION_CODES.M)@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)@Overridepublic void setHolderData(Object o, int position) {if (o != null) {Message bean = (Message) o;if (bean.getFromUser() != null) {if (bean.getFromUser().getUserName().equals(UserUtils.id)) {//是自己的聊天MyHead = view.findViewById(R.id.item_jg_details_head1);  //头像 右边MyHead.setVisibility(View.VISIBLE);//头像显示隐藏view.findViewById(R.id.item_jg_details_head).setVisibility(View.GONE);//内容背景MyTv_content.setBackground(MyContext.getDrawable(R.drawable.textview_jg_msg_i));MyTv_content.setTextColor(MyContext.getColor(R.color.white));My_tc.setVisibility(View.VISIBLE);//权重挤压My_tc1.setVisibility(View.GONE);//对方是否未读My_Tv_state.setVisibility(View.VISIBLE);if (bean.haveRead()) {My_Tv_state.setText("已读");My_Tv_state.setTextColor(MyContext.getColor(R.color.blue));}{My_Tv_state.setText("未读");My_Tv_state.setTextColor(MyContext.getColor(R.color.huise));}} else {My_Tv_state.setVisibility(View.GONE);//对方是否未读MyHead = view.findViewById(R.id.item_jg_details_head);  //头像MyHead.setVisibility(View.VISIBLE);//头像显示隐藏view.findViewById(R.id.item_jg_details_head1).setVisibility(View.GONE);//内容背景MyTv_content.setBackground(MyContext.getDrawable(R.drawable.textview_jg_msg_he));MyTv_content.setTextColor(MyContext.getColor(R.color.heise));My_tc.setVisibility(View.GONE);My_tc1.setVisibility(View.VISIBLE);}MyHead.setOnClickListener(this); //刷新头像点击事件//头像bean.getFromUser().getAvatarBitmap(new GetAvatarBitmapCallback() {@Overridepublic void gotResult(int i, String s, Bitmap bitmap) {if (bitmap != null) {MyHead.setImageBitmap(bitmap);} else {Log.e("极光会话详情-用户头像赋值", "bitmap为空!");}}});switch (bean.getContentType()) {case text:MyTv_content.setVisibility(View.VISIBLE);MyTV_Time.setVisibility(View.GONE);MyImg.setVisibility(View.GONE);//内容TextContent textContent = (TextContent) bean.getContent();String text = textContent.getText();MyTv_content.setText(text);break;case image:MyTv_content.setVisibility(View.GONE);MyTV_Time.setVisibility(View.GONE);MyImg.setVisibility(View.VISIBLE);ImageContent imageContent = (ImageContent) bean.getContent();if (imageContent.getLocalThumbnailPath() != null) {Glide.with(MyContext).load(imageContent.getLocalThumbnailPath()).into(MyImg);}break;case prompt: //提示MyTv_content.setVisibility(View.GONE);MyTV_Time.setVisibility(View.VISIBLE);MyImg.setVisibility(View.GONE);//内容PromptContent promptContent = (PromptContent) bean.getContent();String promptText = promptContent.getPromptText();MyTV_Time.setText(promptText);break;}}}}@Overridepublic void onClick(View v) {if (mOnItemClickLis != null) {mOnItemClickLis.onItemClick(v, getPosition());}}}

BaseViewHolder

这个类是我封装的一个holder上层类 主要是为了代码逻辑区分,一目了然.. 等等作用.

/*** Created by Xinghai.Zhao on 18/03/29.*/
/**作者:赵星海*时间:18/03/29 16:57*用途:ViewHolder上层类*/
public abstract class BaseViewHolder extends RecyclerView.ViewHolder{public BaseViewHolder(View itemView) {super(itemView);findView(itemView);}public abstract void findView(View view);public abstract void setHolderData(Object o,int position);}

最后说一下ViewHoler对应的的布局文件 item_jg_details

这里面主要原理是 左右各放一个头像 , 根据用户是自己还是别人,来区分隐藏哪一边,

中间竖向排列了文本textView,图片imageView和 提醒view("已读/未读")

根据消息类型绝对显示隐藏哪一个

中间还放置了权重挤压view: item_jg_details_tc 和 item_jg_details_tc 它俩的作用是把消息挤到对应地方

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/beijing"android:orientation="vertical"><TextViewandroid:id="@+id/item_jg_details_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:padding="8px"android:textColor="@color/huise"android:textSize="12sp"android:visibility="gone" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><de.hdodenhof.circleimageview.CircleImageViewandroid:id="@+id/item_jg_details_head"android:layout_width="180px"android:layout_height="180px"android:paddingBottom="30px"android:paddingLeft="30px"android:paddingTop="30px"android:visibility="gone" /><LinearLayoutandroid:layout_width="0px"android:layout_height="wrap_content"android:layout_margin="40px"android:layout_weight="1"android:orientation="horizontal"><TextViewandroid:id="@+id/item_jg_details_tc"android:layout_width="0px"android:layout_height="match_parent"android:layout_weight="1"android:visibility="visible" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:id="@+id/item_jg_details_content"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="30px"android:layout_marginTop="40px"android:paddingBottom="10px"android:paddingLeft="18px"android:paddingRight="18px"android:paddingTop="10px"android:textColor="@color/heise"android:textSize="15sp"android:visibility="gone" /><com.makeramen.roundedimageview.RoundedImageViewandroid:id="@+id/item_jg_details_img"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="wrap_content"android:layout_height="wrap_content"android:scaleType="fitCenter"app:riv_corner_radius="12px"app:riv_mutate_background="true"app:riv_oval="false"app:riv_tile_mode="repeat" /><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:visibility="gone" /><TextViewandroid:id="@+id/item_jg_details_state"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:padding="8px"android:textSize="12sp"android:visibility="gone" /></LinearLayout><TextViewandroid:id="@+id/item_jg_details_tc1"android:layout_width="0px"android:layout_height="match_parent"android:layout_weight="1"android:visibility="gone" /></LinearLayout><de.hdodenhof.circleimageview.CircleImageViewandroid:id="@+id/item_jg_details_head1"android:layout_width="180px"android:layout_height="180px"android:paddingBottom="30px"android:paddingRight="30px"android:paddingTop="30px"android:visibility="gone" /></LinearLayout></LinearLayout >

注意事项啊:

item中的头像使用了三方的CircleImageView,圆角图片使用了RoundedImageView

这俩控件直接写两行依赖就可以使用:

// 头像切圆implementation 'de.hdodenhof:circleimageview:2.2.0'
//圆角图片compile 'com.makeramen:roundedimageview:2.2.1'

如需扩展使用,还需要注意以下几点:

Adapter的点击事件中的 Activity_img 这个不用我多解释了吧,有了极光拿到的图片地址:ImgUrl

跳到Activity_img页面全屏加载一下加一个缩放手势就好了;  实在不会的我给你指条明路:PhotoView 百度搜去吧

再多说几句啊,Item这边我多次使用了view的现隐功能 setVisibility(View.VISIBLE);

如需要扩展,请多留意

最后嘱咐一点: 聊天界面关闭时同时关闭监听和打开通知栏接收

@Overrideprotected void onDestroy() {//退出会话界面 (开始接收通知栏)JMessageClient.exitConversation();//设置消息接收 监听GlobalEventListener.setJG(null, false);super.onDestroy();}

关于以上代码不明白的,或者我没有说清楚的,可以在评论区问我.

声明:

以上代码是从项目中摘出来的,不存在Demo,并且都是本人一行一行码出来的.

Android 极光IM简单的聊天界面全手动实现相关推荐

  1. Android(安卓)一个简单的聊天界面的实现(eclipse实现)

    这几天刚刚学习一下安卓的编程,尝试制作了一个简单的聊天界面(还没有实现网络等后续功能)软件界面如图.(使用eclipse实现) 当输入一些内容后,聊天界面可以下拉显示更多的聊天信息,如下图 首先对这个 ...

  2. Android仿QQ、微信聊天界面长按提示框效果

    最近在工作项目中要实现一个长按提示 "复制" 的功能,类似于QQ.微信聊天界面长按提示框效果,本来想偷懒在网上找个开源的项目用,但是看了好几个都不是很满意,所以就打算按照自己的思路 ...

  3. Android适配器以及作用,Android Studio:自定义Adapter(适配器)的一些通俗易懂的理解(以一个简单的聊天界面为例)...

    本文是博主对Adapter(适配器)的一些理解,为了加深对Adapter的理解以及记录自己的阶段学习而写,同时也适合初学者阅读,参考本条博客的逻辑进行学习. 第一  先来看看实现这个程序需要需要创建哪 ...

  4. android即时通讯ui框架,android IM即时通信之聊天界面UI框架

    写在最前面 现在很多软件都要求加入即时通信的功能,当然很多都用了三方(环信.融信...).最近,项目也有此需求,我们选择的是环信.环信也提供了UI框架,但是说实在的一般的应用用不了那么多功能,可能就简 ...

  5. python简单可视化聊天界面_如何用Python制作可视化输入界面

    继续研究Python的应用,我们在有些程序中需要输入一些参数,可由几种方式实现 1.直接写在程序里,适合编程使用 2.使用input()函数,运行程序时输入 3.做成可视化界面,然后让程序获得 今天主 ...

  6. python简单可视化聊天界面_Python可视化界面

    可视化界面程序,本来不想写,只在console台运行就好,但是后来很多小伙伴都有这样的需求: 需要从redis中删除某个key的value,然后需要跟key去查,有些小伙伴不会用redis,就产生如下 ...

  7. Android文本长按qq风格,Android仿QQ、微信聊天界面长按提示框效果

    先来看看效果图 如何使用 示例代码 PromptViewHelper pvHelper = new PromptViewHelper(mActivity); pvHelper.setPromptVie ...

  8. Android Jetpack Compose——一个简单的微信界面

    一个简单的微信界面 简述 效果视频 底部导航栏 导航元素 导航栏 放入插槽 绘制地图 消息列表 效果图 实现 聊天 效果图 实现 气泡背景 联系人界面 效果图 实现 好友详情 效果图 实现 发现 效果 ...

  9. Flutter简单聊天界面布局及语音录制播放

    目录 前言: 注意事项: 用到的部分组件依赖及版本: 遇到的坑 遇到的坑1: 遇到的坑2: 遇到的坑3: 遇到的坑4: Fluuter语音录制及播放组件生命周期 Flutter录音组件生命周期图: F ...

最新文章

  1. mysql删除开放用户权限
  2. IntelliJ IDEA(2017)安装和破解
  3. Sql Server查询语句的一些小技巧
  4. IdnentiyServer-使用客户端凭据访问API
  5. Scapy:局域网MAC地址扫描脚本
  6. python sum函数numpy_如何用numba加速python?
  7. java提高篇四_(转)java提高篇(四)-----理解java的三大特性之多态
  8. undefined reference to `std::ios_base::Init::Init() 解决
  9. 小米跨界成立餐饮公司?其实就是新园区食堂...
  10. 记录一下目前thinksoar portal的进度和计划!
  11. 一行代码蒸发了 ¥6,447,277,680 人民币!
  12. 数据可视化工具在教学中的意义
  13. WebRTC手册(一)
  14. linux 内核dump,linux内核调试技巧之一 dump_stack【转】
  15. 记:用notepad2替换系统自带记事本notepad
  16. VS不能使用回车键和删除键及其他键问题
  17. 点击开始十秒倒计时html,十秒倒计时案例.html
  18. 西门子s7-200的PLC编程软件,帮助程序无法打开问题解决方法
  19. Windows调试工具入门-3-WinDbg内核调试配置
  20. html 5 游戏 脚本,HTML 5开发RPG游戏之四(游戏脚本化)(2)

热门文章

  1. 微机原理跟计算机三级哪个科目相关,2015全国计算机三级考试各科目了解
  2. HDU 1533 费用流入门
  3. 手机——小灵通互发短信PDU编码注意事项
  4. 解决ubuntu18.04卡在“starting Gnome Display Manager“
  5. excel字母数字排序_Excel数字不能正确排序或添加
  6. vue限制只能输入数字_vue+element 中 el-input框 限制只能输入数字及一位小数
  7. n皇后问题的两种递归方法C语言实现
  8. [zt]三款iPhone防盗应用软件推荐:让小偷哭去吧
  9. Norflash和Nandflash的区别
  10. 2022年全球市场决明子粉总体规模、主要生产商、主要地区、产品和应用细分研究报告