学问Chat UI(3)
来源:互联网 发布:金融行业待遇 知乎 编辑:程序博客网 时间:2024/06/15 04:33
前言
- 上文学问Chat UI(2)分析了消息适配器的实现;
- 本文主要学习下插件功能如何实现的.并以图片插件功能作为例子详细说明,分析从具体代码入手;
概要
- 分析策略说明
- “+”功能UI布局如何实现?分析整体思路与所用的哪些控件;
- 分析DefaultExtensionModule与PluginAdapter两个类
- 图片插件如何实现?
分析策略
- 1.从融云提供完整的demo,操作“+”按钮,选择图片发送图片消息;
- 2.根据1的操作,寻找对应的控件与事件,理清逻辑;
- 3.从整体把握,看如何实现插件功能;
“+”功能UI布局如何实现
- 从UI看是两个部分:“+”按钮与扩展面板,点击会触发事件,判断扩展面板状态,未显示则显示扩展面板,显示状态则隐藏扩展面板;
- 代码上mPluginToggle对象就是那个"+"按钮,它是ImageView的实例,其中点击会触发
RongExtension.this.setPluginBoard()
方法;
this.mPluginToggle.setOnClickListener(new OnClickListener() { public void onClick(View v) { if(RongExtension.this.mExtensionClickListener != null) { RongExtension.this.mExtensionClickListener.onPluginToggleClick(v, RongExtension.this); } RongExtension.this.setPluginBoard(); } });
- 前面的是小菜,下面好好品下正菜,说明下后面所有代码中出现的8与0,分别代表Gone(消失)与Visible(可见);
- mPluginAdapter初始化状态与mPluginAdapter中显示状态作为主要判断条件;
private void setPluginBoard() { if(this.mPluginAdapter.isInitialized()) { if(this.mPluginAdapter.getVisibility() == 0) { //省略部分代码 } else { //省略部分代码 } } }
- 看到这里,我们会有疑问,mPluginAdapter是用来干什么的?在解答这个疑问之前,先来看下DefaultExtensionModule类。
DefaultExtensionModule干啥的
- 英文翻译下的意思默认的扩展功能模块,实现了图片,文件,地理位置3个基本插件;
- DefaultExtensionModule实现了IExtensionModule接口,其中要重点讲下onAttachedToExtension,onDetachedFromExtension,getPluginModules方法;
- 1.简单点说,onAttachedToExtension与onDetachedFromExtension负责管理其在RongExtension的生命周期,但是这里有个问题会出现内存泄露;*
public void onAttachedToExtension(RongExtension extension) { this.mEditText = extension.getInputEditText(); Context context = extension.getContext(); RLog.i(TAG, "attach " + this.stack.size()); //mEditText编辑框存放到stack栈对象中 this.stack.push(this.mEditText); Resources resources = context.getResources(); try { this.types = resources.getStringArray(resources.getIdentifier("rc_realtime_support_conversation_types", "array", context.getPackageName())); } catch (NotFoundException var5) { ; } } //判断栈大小,如果大于0出栈,并返回mEditText public void onDetachedFromExtension() { RLog.i(TAG, "detach " + this.stack.size()); if(this.stack.size() > 0) { this.stack.pop(); this.mEditText = this.stack.size() > 0?(EditText)this.stack.peek():null; } }
- 2.
getPluginModules方法
主要把功能插件对象--实现IPluginModule接口存放到ArrayList中,提供给外部使用;* - 需要说明的是地理位置在单聊的时候与群聊功能略有不同,单聊多了位置共享的功能,那么怎么区别呢?通过ConversationType参数判断;
- 地理位置功能默认集成的是高德SDK,确定
AMapNetworkLocationClient
存在后才会把地理位置插件加到ArrayList;
public List<IPluginModule> getPluginModules(ConversationType conversationType) { ArrayList pluginModuleList = new ArrayList(); ImagePlugin image = new ImagePlugin(); FilePlugin file = new FilePlugin(); pluginModuleList.add(image); String e; Class cls; try { //判断高德定位服务类是否存在,存在的话根据ConversationType类型把位置共享插件与我的位置插件添加ArrayList中; e = "com.amap.api.netlocation.AMapNetworkLocationClient"; cls = Class.forName(e); if(cls != null) { CombineLocationPlugin constructor = new CombineLocationPlugin(); DefaultLocationPlugin recognizer = new DefaultLocationPlugin(); boolean typesDefined = false; if(this.types != null && this.types.length > 0) { String[] arr$ = this.types; int len$ = arr$.length; for(int i$ = 0; i$ < len$; ++i$) { String type = arr$[i$]; if(conversationType.getName().equals(type)) { typesDefined = true; break; } } } if(typesDefined) { pluginModuleList.add(constructor); } else if(this.types == null && conversationType.equals(ConversationType.PRIVATE)) { pluginModuleList.add(constructor); } else { pluginModuleList.add(recognizer); } } } catch (Exception var15) { RLog.i(TAG, "Not include AMap"); var15.printStackTrace(); } if(conversationType.equals(ConversationType.GROUP) || conversationType.equals(ConversationType.DISCUSSION) || conversationType.equals(ConversationType.PRIVATE)) { pluginModuleList.addAll(InternalModuleManager.getInstance().getExternalPlugins(conversationType)); } pluginModuleList.add(file); //判断科大讯飞sdk是否存在,存在的话通过反射实例化语音识别插件并加入到ArraryList中 try { e = "com.iflytek.cloud.SpeechUtility"; cls = Class.forName(e); if(cls != null) { cls = Class.forName("io.rong.recognizer.RecognizePlugin"); Constructor var16 = cls.getConstructor(new Class[0]); IPluginModule var17 = (IPluginModule)var16.newInstance(new Object[0]); pluginModuleList.add(var17); } } catch (Exception var14) { RLog.i(TAG, "Not include Recognizer"); var14.printStackTrace(); } return pluginModuleList; }
关于PluginAdapter
- 继续上面提到的关于PluginAdapter的疑问,首先看下PluginAdapter这个类,代码如下:
- 这里暂时不去关注网格效果实现方式,关注
mInitialized
布尔类型值与addPlugins
方法 - mInitialized值在bindView被写入为true,说明被初始化了,而addPlugins方法把DefaultExtensionModule的插件集合加到mPluginModules中,并在initView使用到;
public class PluginAdapter { private static final String TAG = "PluginAdapter"; private List<IPluginModule> mPluginModules = new ArrayList(); private boolean mInitialized; public PluginAdapter() { } public boolean isInitialized() { return this.mInitialized; } //省略部分方法 public void addPlugins(List<IPluginModule> plugins) { for(int i = 0; plugins != null && i < plugins.size(); ++i) { this.mPluginModules.add(plugins.get(i)); } } //省略部分方法 public void bindView(ViewGroup viewGroup) { this.mInitialized = true; this.initView(viewGroup.getContext(), viewGroup); } private void initView(Context context, ViewGroup viewGroup) { //省略部分方法 } public int getVisibility() { return this.mPluginPager != null?this.mPluginPager.getVisibility():8; } //省略部分代码}
- 下面从RongExtension看PluginAdapter如何被使用?
1.PluginAdapter在RongExtension的构造函数中被实例化,然后initPlugins方法
把插件加到PluginAdapter对象中;
2.接下来,重点分析是上面提到的setPluginBoard方法
;长话多说,如果mPluginAdapter(插件适配器)未初始化,先进行初始化;
否则,根据扩展面板是否显示,显示则隐藏键盘与扩展面板,隐藏的话显示扩展面板并隐藏表面面板与键盘;最后要做的是,把语音输入隐藏,mEditTextLayout布局显示;
private void setPluginBoard() { if(this.mPluginAdapter.isInitialized()) { if(this.mPluginAdapter.getVisibility() == 0) { View pager = this.mPluginAdapter.getPager(); if(pager != null) { pager.setVisibility(pager.getVisibility() == 8?0:8); } else { this.mPluginAdapter.setVisibility(8); this.mContainerLayout.setSelected(true); this.showInputKeyBoard(); } } else { this.mEmoticonToggle.setImageResource(drawable.rc_emotion_toggle_selector); if(this.isKeyBoardActive()) { this.getHandler().postDelayed(new Runnable() { public void run() { RongExtension.this.mPluginAdapter.setVisibility(0); } }, 200L); } else { this.mPluginAdapter.setVisibility(0); } this.hideInputKeyBoard(); this.hideEmoticonBoard(); this.mContainerLayout.setSelected(false); } } else { this.mEmoticonToggle.setImageResource(drawable.rc_emotion_toggle_selector); this.mPluginAdapter.bindView(this); this.mPluginAdapter.setVisibility(0); this.mContainerLayout.setSelected(false); this.hideInputKeyBoard(); this.hideEmoticonBoard(); } this.hideVoiceInputToggle(); this.mEditTextLayout.setVisibility(0); }
图片插件如何实现?
- 前面的内容为后面理解图片插件的实现提供了铺垫,上面的
getPluginModules方法
提到的ImagePlugin
类是讲解的重点; - 在看ImagePlugin之前先来看下DefaultExtensionModule中的插件如何与PluginAdapter关联起来的?
DefaultExtensionModule中的插件如何与PluginAdapter关联
1 1.点击“+”的时候插件功能已经可以使用了,那么说明在聊天界面渲染之前插件已经被建立起来,很容易,想到初始化聊天IM服务是最好的时机;
//调用RongIM的public静态init方法,参数呢是实例化的DefaultExtensionModuleRongExtensionManager.getInstance().registerExtensionModule(new DefaultExtensionModule());
RongExtensionManager中有一个List
public void registerExtensionModule(IExtensionModule extensionModule) { if(mExtModules == null) { RLog.e("RongExtensionManager", "Not init in the main process."); } else if(extensionModule != null && !mExtModules.contains(extensionModule)) { RLog.i("RongExtensionManager", "registerExtensionModule " + extensionModule.getClass().getSimpleName()); if(mExtModules.size() <= 0 || !((IExtensionModule)mExtModules.get(0)).getClass().getCanonicalName().equals("com.jrmf360.rylib.modules.JrmfExtensionModule") && !((IExtensionModule)mExtModules.get(0)).getClass().getCanonicalName().equals("com.melink.bqmmplugin.rc.BQMMExtensionModule")) { mExtModules.add(extensionModule); } else { mExtModules.add(0, extensionModule); } extensionModule.onInit(mAppKey); } else { RLog.e("RongExtensionManager", "Illegal extensionModule."); }}
2 2.再看RongExtension的initData方法,把RongExtensionManager中的List
private void initData() { this.mExtensionModuleList = RongExtensionManager.getInstance().getExtensionModules(); this.mPluginAdapter = new PluginAdapter();//省略若干代码}
3 3.再看RongExtension的setConversation方法调用this.initPlugins(),当当当的,调用了实例化插件对象的addPlugins把插件加入到其中,从而形成关联;
private void initPlugins() { Iterator i$ = this.mExtensionModuleList.iterator(); while(i$.hasNext()) { IExtensionModule module = (IExtensionModule)i$.next(); List pluginModules = module.getPluginModules(this.mConversationType); if(pluginModules != null && this.mPluginAdapter != null) { this.mPluginAdapter.addPlugins(pluginModules); } } }
ImagePlugin
- ImagePlugin实现了IPluginModule接口,总共四个方法,代码如下:
public interface IPluginModule { Drawable obtainDrawable(Context var1); String obtainTitle(Context var1); void onClick(Fragment var1, EditExtension var2); void onActivityResult(int var1, int var2, Intent var3);}
- 看到这里你可能对四个方法是干什么产生疑问?别着急,欲知此事,请往下阅读;
贴上ImagePlugin的具体代码,这里看具体实现的代码,请看代码中注释;
public class ImagePlugin implements IPluginModule {ConversationType conversationType;String targetId;public ImagePlugin() {}//item的背景图片public Drawable obtainDrawable(Context context) { return ContextCompat.getDrawable(context, R.drawable.rc_ext_plugin_image_selector);}//item的插件标题public String obtainTitle(Context context) { return context.getString(R.string.rc_plugin_image);}//item点击事件public void onClick(Fragment currentFragment, EditExtension extension) { String[] permissions = new String[]{"android.permission.READ_EXTERNAL_STORAGE"}; //这里考虑android6.0权限变更,不仅需要声明权限,而且敏感权限需要允许时申请 if(PermissionCheckUtil.requestPermissions(currentFragment, permissions)) { this.conversationType = extension.getConversationType(); this.targetId = extension.getTargetId(); Intent intent = new Intent(currentFragment.getActivity(), PictureSelectorActivity.class); //回调Fragment 中的onActivityResult extension.startActivityForPluginResult(intent, 23, this); }}public void onActivityResult(int requestCode, int resultCode, Intent data) {}}
- 下面看下ConversationFragment选完图片以后回调如何进行?
首先对requestCode做了判断,如果不是102则回调了mRongExtension对象的onActivityPluginResult方法,然后根据请求代码分析是哪个插件回调回来的,在调用IExtensionClickListener接口对应的方法;
public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == 102) { this.getActivity().finish(); } else { this.mRongExtension.onActivityPluginResult(requestCode, resultCode, data); }}
这里有几个问题?onActivityResult可不可以直接处理?requestCode 的如何作用?
1 第一个问题,可以进行直接数据处理,但是需要约定好requestCode,如果通过融云回调的话不需要约定;
2 第二个问题,单独一个int类型的值容纳得信息有限,做过处理的就与众不同了,融云的方法是把后8位作为requestCode,前24位作为postion,为何要+1不是很懂,有知道请再评论中指出;this.mFragment.startActivityForResult(intent, (position + 1 << 8) + (requestCode & 255));
上述代码完成以后可以通过回调ConversationFragment实现的
this.mExtensionClickListener.onImageResult(list, lat1);
方法发送图片消息了,代码就不贴了;
总结
- 插件实现通过接口方式,耦合度降低,扩展性好;
- 添加插件时,无需大改RongExtension代码只要实现IPluginModule接口并注册到实现IExtensionModule的插件模块中,并在初始化RongIM时注册插件模块;
- 考虑功能的时候需要考虑到兼容性,扩展性;
- 学问Chat UI(3)
- 学问Chat UI(1)
- 学问Chat UI(2)
- Chat UI library for Android
- 学问
- CHAT
- CHAT
- chat
- chat
- chat
- chat
- chat
- Encrypted Chat Room 3_Design Documentation
- Encrypted Chat Room 4_Class Specifications(3)
- Node(3) TCP-based chat server
- 【Word2vec】【Node.js 爬虫】【Sonar 静态代码扫码】【APP UI 自动化测试】 | Chat · 预告
- 爱情学问
- 做人学问
- 用matplotlib作图——颜色与线型控制
- Maven及Eclipse配置Maven
- 【剑指offer】剑指offer 练习笔记
- 第六篇 CSS样式 背景、背景图、文本、链接
- Webstorm中使用babel转码器
- 学问Chat UI(3)
- LeetCode-Easy-Java-Non-decreasing Array
- STM32之内存分布与总线
- (转) arcgis for flex 画的graphic面,在其面上也能移动地图(及补充)
- Android Studio遇到的问题:Your CPU does not support required features (VT-x or SVM)
- POJ 1389 Area of Simple Polygons 线段树 扫描线
- ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
- python pandas字符串过滤
- 广州UI培训班为您解读UI设计和美工的区别?