仿微信表情输入键盘(支持 Gif 表情图文混排 )

来源:互联网 发布:远程登陆windows账号 编辑:程序博客网 时间:2024/04/28 16:45

作者 | PandaQ404

地址 | http://www.jianshu.com/p/fddca2b0a26b

声明 | 本文是 PandaQ404 原创,已获授权发布,未经原作者允许请勿转载



简介

自定义的表情输入键盘在很多应用中都会有用到,譬如微信、QQ 等社交聊天软件中更是不可缺少的部分。本文将解析一下个人的自定义表情输入控件库 PandaEmoView 的实现和使用。


特点

  • 支持 emoji 表情图片

  • 支持 gif 动态表情输入显示

  • 支持单张贴图表情(与微信收藏表情一致)

  • 支持题图表情库的添加删除


效果图


快速使用

引入库

compile 'com.pandaq:PandaEmoView:1.0.0'


表情资源及配置文件

  • 默认的 emoji 和 gif 表情以及他们的配置文件是放在开发包 assets 目录下的,若表情比较多比较大也可自行修改源码在 APP 启动时从服务器下载。



  • emoji 表情配置文件



  • 非自定义 sticker 配置文件(自定义 sticker 是没有配置文件的)



具体使用规则

与表情输入控件相关的 EditText 必须使用 PandaEditText

PandaEditText 只是重写了 onKeyPreIme() 获取按返回键的通知,继承自 EditText 的控件可继承 PandaEditText 自定义


  1. 应用 Application 中进行全局参数配置


private void configPandaEmoView() {
       new PandaEmoManager.Builder()
               .with(getApplicationContext()) // 传递 Context
               .configFileName("emoji.xml")// 配置文件名称
               .emoticonDir("face") // asset 下存放表情的目录路径(asset——> configFileName 之间的路径,结尾不带斜杠)
               .sourceDir("images") // 存放 emoji 表情资源文件夹路径(emoticonDir 图片资源之间的路径,结尾不带斜杠)
               .showAddTab(true)//tab栏是否显示添加按钮
               .showStickers(true)//tab栏是否显示贴图切换按键
               .showSetTab(true)//tab栏是否显示设置按钮
               .defaultBounds(30)//emoji 表情显示出来的宽高
               .cacheSize(1024)//加载资源到内存时 LruCache 缓存大小
               .defaultTabIcon(R.drawable.ic_default)//emoji表情Tab栏图标
               .emojiColumn(7)//单页显示表情的列数
               .emojiRow(3)//单页显示表情的行数
               .stickerRow(2)//单页显示贴图表情的行数
               .stickerColumn(4)//单页显示贴图表情的列数
               .maxCustomStickers(30)//允许添加的收藏表情数
               .imageLoader(new IImageLoader() {
                   @Override
                   public void displayImage(String path, ImageView imageView)
{ // 加载贴图表情的图片加载接口
                       Picasso.with(getApplicationContext())
                               .load(path)
                               .fit()
                               .centerCrop()
                               .into(imageView);
                   }
               })
               .build(); //构建 PandaEmoManager 单利
   }


2.使用此控件的 Activity 在 manifest 文件中配置

// 这句是一定要加上的。
android:windowSoftInputMode="adjustResize"


3.使用此控件的界面 xml 文件规则
布局规则如下图,lockView 即是我们正常显示内容的 View 它与表情输入控件 PandaEmoView 属于同一层级,父布局必须为纵向线性布局,且设置 lockView 权重为 1 ,PandaEmoView 高度包裹内容即可

4.使用控件的 Activity Java 代码设置


//界面控件初始化后 .attachEditText()绑定输入控件
//初始化 KeyBoardManager,PandaEmoView.attachEditText() 必须在后调用


主要使用类及公有方法概览

PandaEmoEditText

  • 表情输入框继承自 EditText 只对 onKeyIme() 进行复写用于监听输入键盘或者软键盘的弹出与关闭

PandaEmoView

  • 表情输入控件 View 继承自 RelativeLayout

PandaEmoManager

  • PandaEmoManager 为核心配置类,表情控件的各种参数都通过此类的构造器进行配置




剩余方法都为属性值的 getter() setter() 不在赘述。


PandaEmoManager.Builder

  • PandaEmoManager 的构造器类,属性及方法都与 PandaEmoManager 一一对应;

KeyBoardManager

  • KeyBoardManager 为输入法软键盘与表情输入控件协调管理类



EmoticonManager

  • EmoticonManager 为 emoji 表情加载管理类,此类提供方法将资源文件根据配置文件加载进内存,方法大多数为私有方法,源码中可查看注释。


StickerManager

  • StickerManager 为 sticker 表情加载管理类,此类提供方法将资源文件根据配置文件加载进内存,与 EmoticonManager 类似


PandaEmoTranslator

  • PandaEmoTranslator 为 emoji 表情 [文字] 转表情的转换工具类



关于内存优化

因为表情,gif 表情,自定义贴图,表情包贴图这些都涉及到图片资源加载到内存中。因此开发过程中不可避免的也遇到了许多的内存优化相关的问题。

工具

就地取材,直接使用 Android Studio 的 Monitors 工具可以直观的查看到应用运行过程中内存的变化过程

优化点1 —— Gif 播放类的优化


  • 问题:

参考网上的 gif 图文混排项目,虽然实现了 gif 与文字的图文混排效果,但存在致命的缺陷。该项目中每一个 gif 动态表情图都有一个对应的 Runable 对象去执行 gif 图片的逐帧播放,当一个表情重复输入也会有新的 Runable 对象去执行这样的操作,这样做的后果就是当输入的表情数量增加时,所消耗的内存是持续增长的。这显然不能满足生产使用的需求。


  • 解决方案:

考虑到此处内存增加的原因是让表情动起来的 Runable 泛滥引起的,因此减少 Runable 的数量就是解决此处内存问题的关键。我的方案做的比较彻底,整个应用 gif 表情这一块儿都交给一个 Runable 去处理,这个 Runable 在 PandaTranslator 中进行图文转化时会被初始化


// PandaTranslator 的 103 - 107 行 
103                   if (mGifRunnable == null) {
104                      mGifRunnable = new GifRunnable(gifDrawable, mHandler);
105                   } else {
106                      mGifRunnable.addGifDrawable(gifDrawable);
107                   }


因为 PandaTranslator 是一个单例实现,所以在他初始化后 mGifRunnable 也将保持唯一性。无论是新建初始化还是 addGifDrawable() 都是把 Gif 表情对象放入 GifRunnable 中的一个 Map 中。Map 的 key value 分别是表情控件依附的 Activity 的 LocalName 和 一个 AnimatedGifDrawable 的 List。在 GifRunnable 的 run 方法中会根据当前的 Activity 的 LocalName 去取出对应的 AnimatedGifDrawable 列表,遍历执行并按第一张 gif 表情的帧间隔去刷新 Drawable 并触发 TextView 刷新回调


@Override
   public void run() {
       isRunning = true;
       if (currentActivity != null) {
           List<AnimatedGifDrawable> runningDrawables = mGifDrawableMap.get(currentActivity);
           if (runningDrawables != null) {
               for (AnimatedGifDrawable gifDrawable : runningDrawables) {
                   AnimatedGifDrawable.RunGifCallBack listener = gifDrawable.getUpdateListener();
                   List<AnimatedGifDrawable.RunGifCallBack> runningListener = listenersMap.get(currentActivity);
                   if (runningListener != null) {
                   // 避免一个 TextView 多个表情时重复添加回调
                       if (!runningListener.contains(listener)) {
                           runningListener.add(listener);
                       }
                   } else {
                       // 为空时肯定不存在直接添加
                       runningListener = new ArrayList<>();
                       runningListener.add(listener);
                       listenersMap.put(currentActivity, runningListener);
                   }
                   gifDrawable.nextFrame();
               }
               for (AnimatedGifDrawable.RunGifCallBack callBack : listenersMap.get(currentActivity)) {
                   if (callBack != null) {
                       callBack.run();
                   }
               }
               frameDuration = runningDrawables.get(0).getFrameDuration();
           }
       }
       mHandler.postDelayed(this, frameDuration);
   }


这样就实现了全局使用一个 Runable 来执行 gif 动起来的任务,不同的界面也仅需要将该界面的 AnimatedGifDrawable 对象加入任务 Map 即可。


优化点2 —— 界面暂停或退出时 Gif 播放资源同步退出回收


上面说到的将 AnimatedGifDrawable 列表加入任务 Map,只进不出显然是不科学的也会持续增加内存的消耗。我们希望在 Activity 退出时能将将当前 Activity 的 AnimatedGifDrawable 列表销毁移除,在界面不可见但是可能会恢复时(pause 状态)暂停 Runable 的执行,减少资源消耗。于是 GifRunable 提供了如下三个方法给外部调用


/**
    * 使用了表情转换的界面退出时调用,停止动态图handler
    */

   public void clearHandler(String activityName) {
       currentActivity = null;
       //清除当前页的数据
       mGifDrawableMap.remove(activityName);
       // 当退出当前Activity后没表情显示时停止 Runable 清除所有动态表情数据
       listenersMap.remove(activityName);
       if (mGifDrawableMap.size() == 0) {
           clearAll();
       }
   }
   private void clearAll() {
       mHandler.removeCallbacks(this);
       mHandler.removeCallbacksAndMessages(null);
       mGifDrawableMap.clear();
       isRunning = false;
   }
   /**
    * 启动运行
    */

   public void startHandler(String activityName) {
       currentActivity = activityName;
       if (mGifDrawableMap != null && mGifDrawableMap.size() > 0 && !isRunning) {
           run();
       }
   }


它的调用入口都在 PandaTranslator 中,然后我们只需在使用到 PandaEmoView 或者直接在 BaseActivity 的 onResume(),onPause(),onDestory() 中分别调用以下三个方法:


PandaTranslator.getInstance().resumeGif(activityLocalName);
PandaTranslator.getInstance().pauseGif();
PandaTranslator.getInstance().clearGif(activityLocalName)


优化点3 —— 使用 LruCache 缓存 emoji 资源


根据 LRU 规则将表情 Gif 缓存,避免重复加载创建新对象。

最后

因为离职从南京回到成都还有工作的各种各样的原因,也是有四个多月没更博客了。这是重新开始写博客的第一篇,之后大概会以一个月 2-3 篇的样子更新,记录与分享,欢迎大家关注我的简书。

本库地址 https://github.com/PandaQAQ/PandaEmoView

欢迎 star 和提 issue


开源库

 推荐几个开源库



原创粉丝点击