zoukankan      html  css  js  c++  java
  • 学问Chat UI(3)

    前言

    • 上文学问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方法,参数呢是实例化的DefaultExtensionModule
    RongExtensionManager.getInstance().registerExtensionModule(new DefaultExtensionModule());
    
    • RongExtensionManager中有一个List对象,简单点说,调用registerExtensionModule就是把对象加入到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对象赋值给mExtensionModuleList,并实例化了PluginAdapter--插件适配器类

    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时注册插件模块;
    • 考虑功能的时候需要考虑到兼容性,扩展性;
  • 相关阅读:
    .Net Web开发技术栈
    C#foreach原理
    C#位运算符
    python写12306抢票
    java语法学习
    建立个人知识体系
    struts2静态方法和动态方法调用
    springmvc跳转的几种方式
    JDBC驱动程序的四种方式
    eclipse用axis2发布webserver
  • 原文地址:https://www.cnblogs.com/lmf-techniques/p/7404847.html
Copyright © 2011-2022 走看看