Android 极光IM简单的聊天界面全手动实现
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简单的聊天界面全手动实现相关推荐
- Android(安卓)一个简单的聊天界面的实现(eclipse实现)
这几天刚刚学习一下安卓的编程,尝试制作了一个简单的聊天界面(还没有实现网络等后续功能)软件界面如图.(使用eclipse实现) 当输入一些内容后,聊天界面可以下拉显示更多的聊天信息,如下图 首先对这个 ...
- Android仿QQ、微信聊天界面长按提示框效果
最近在工作项目中要实现一个长按提示 "复制" 的功能,类似于QQ.微信聊天界面长按提示框效果,本来想偷懒在网上找个开源的项目用,但是看了好几个都不是很满意,所以就打算按照自己的思路 ...
- Android适配器以及作用,Android Studio:自定义Adapter(适配器)的一些通俗易懂的理解(以一个简单的聊天界面为例)...
本文是博主对Adapter(适配器)的一些理解,为了加深对Adapter的理解以及记录自己的阶段学习而写,同时也适合初学者阅读,参考本条博客的逻辑进行学习. 第一 先来看看实现这个程序需要需要创建哪 ...
- android即时通讯ui框架,android IM即时通信之聊天界面UI框架
写在最前面 现在很多软件都要求加入即时通信的功能,当然很多都用了三方(环信.融信...).最近,项目也有此需求,我们选择的是环信.环信也提供了UI框架,但是说实在的一般的应用用不了那么多功能,可能就简 ...
- python简单可视化聊天界面_如何用Python制作可视化输入界面
继续研究Python的应用,我们在有些程序中需要输入一些参数,可由几种方式实现 1.直接写在程序里,适合编程使用 2.使用input()函数,运行程序时输入 3.做成可视化界面,然后让程序获得 今天主 ...
- python简单可视化聊天界面_Python可视化界面
可视化界面程序,本来不想写,只在console台运行就好,但是后来很多小伙伴都有这样的需求: 需要从redis中删除某个key的value,然后需要跟key去查,有些小伙伴不会用redis,就产生如下 ...
- Android文本长按qq风格,Android仿QQ、微信聊天界面长按提示框效果
先来看看效果图 如何使用 示例代码 PromptViewHelper pvHelper = new PromptViewHelper(mActivity); pvHelper.setPromptVie ...
- Android Jetpack Compose——一个简单的微信界面
一个简单的微信界面 简述 效果视频 底部导航栏 导航元素 导航栏 放入插槽 绘制地图 消息列表 效果图 实现 聊天 效果图 实现 气泡背景 联系人界面 效果图 实现 好友详情 效果图 实现 发现 效果 ...
- Flutter简单聊天界面布局及语音录制播放
目录 前言: 注意事项: 用到的部分组件依赖及版本: 遇到的坑 遇到的坑1: 遇到的坑2: 遇到的坑3: 遇到的坑4: Fluuter语音录制及播放组件生命周期 Flutter录音组件生命周期图: F ...
最新文章
- mysql删除开放用户权限
- IntelliJ IDEA(2017)安装和破解
- Sql Server查询语句的一些小技巧
- IdnentiyServer-使用客户端凭据访问API
- Scapy:局域网MAC地址扫描脚本
- python sum函数numpy_如何用numba加速python?
- java提高篇四_(转)java提高篇(四)-----理解java的三大特性之多态
- undefined reference to `std::ios_base::Init::Init() 解决
- 小米跨界成立餐饮公司?其实就是新园区食堂...
- 记录一下目前thinksoar portal的进度和计划!
- 一行代码蒸发了 ¥6,447,277,680 人民币!
- 数据可视化工具在教学中的意义
- WebRTC手册(一)
- linux 内核dump,linux内核调试技巧之一 dump_stack【转】
- 记:用notepad2替换系统自带记事本notepad
- VS不能使用回车键和删除键及其他键问题
- 点击开始十秒倒计时html,十秒倒计时案例.html
- 西门子s7-200的PLC编程软件,帮助程序无法打开问题解决方法
- Windows调试工具入门-3-WinDbg内核调试配置
- html 5 游戏 脚本,HTML 5开发RPG游戏之四(游戏脚本化)(2)
热门文章
- 微机原理跟计算机三级哪个科目相关,2015全国计算机三级考试各科目了解
- HDU 1533 费用流入门
- 手机——小灵通互发短信PDU编码注意事项
- 解决ubuntu18.04卡在“starting Gnome Display Manager“
- excel字母数字排序_Excel数字不能正确排序或添加
- vue限制只能输入数字_vue+element 中 el-input框 限制只能输入数字及一位小数
- n皇后问题的两种递归方法C语言实现
- [zt]三款iPhone防盗应用软件推荐:让小偷哭去吧
- Norflash和Nandflash的区别
- 2022年全球市场决明子粉总体规模、主要生产商、主要地区、产品和应用细分研究报告