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


简介

自定义的表情输入键盘在很多应用中都会有用到,譬如微信、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
方法返回值参数描述
attachEditText(PandaEmoEditText input)void表情输入控件(如使用自定义 EditText 可直接继承重写)绑定输入控件
getAttachEditText()void获取当前表情控件绑定的输入框控件
reloadEmosvoidposition 重载表情控件数据后默认选中 Tab 的 position添加或者删除表情数据后重载刷新表情控件

PandaEmoManager

  • PandaEmoManager 为核心配置类,表情控件的各种参数都通过此类的构造器进行配置
属性类型描述默认值
EMOT_DIRString默认表情图在 assets 中的路径 (assets 目录到配置 xml 文件之间的部分的路径,demo 中的 face)face
SOURCE_DIRStringEMOT_DIR 目录下存放图片资源的文件夹路径 (demo 中的 images)source_default
STICKER_PATHStringSticker贴图包的存放目录,该目录下的每一个文件目录都为一个贴图包/data/data/< package name>/files/sticker
CACHE_MAX_SIZEint加载表情 LruCache 缓存大小1024
DEFAULT_EMO_BOUNDS_DPint表情图显示大小(非贴图表情)30dp
defaultIconint表情 Tab 资源文件名R.drawable.ic_default
mContextContext上下文null
mConfigFileStringEMOT_DIR 目录下的 emoji 配置文件名称emoji_default.xml
mIImageLoaderIImageLoaderSticker 图片加载器接口,加载方式外部传入null
MAX_CUSTOM_STICKERint最大添加的自定义贴图表情数30
EMOJI_ROWintemoji 表情单页行数3
EMOJI_COLUMNintemoji 表情单页列数7
STICKER_ROWintsticker 表情单页行数2
STICKER_COLUMNintsticker 表情单页列数4
showAddButtonbooleantab 栏是否显示添加按钮ture
showSetButtonbooleantab 栏是否显示设置按钮ture
showStickersbooleantab 栏是否显示贴图按钮(所以 sticker)ture
方法返回值参数描述
init()void无参初始化 拼接 Sticker 路径及创建自定义贴图 文件夹 (STICKER_PATH + "/selfSticker" )
makePattern()Pattern无参创建 emoji 正则匹配器

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

PandaEmoManager.Builder

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

KeyBoardManager

  • KeyBoardManager 为输入法软键盘与表情输入控件协调管理类
属性类型描述默认值
SHARE_PREFERENCE_NAMEString用于存储键盘高度的 SP 的名字"EmotionKeyBoard"
SHARE_PREFERENCE_SOFT_INPUT_HEIGHTString用于存储键盘高度的 key"EmotionKeyBoard"
mActivityActivity控件依附的 Activity 界面null
mEmotionViewPandaEmoView当前管理的表情输入控件null
interceptBackPressboolean是否拦截返回键false
lockViewView锁定高度的 View(即同一线性父布局中,表情控件之外的布局视图)null
mOnEmotionButtonOnClickListenerOnEmotionButtonOnClickListener表情显示控制按钮监听null
mOnInputShowListenerOnInputShowListener监听输入栏的弹出与关闭null
方法返回值参数描述
with()KeyBoardManagerActivity : 当前输入控件依附的 Activity初始化 KeyBoardManager 创建单例
bindToLockContent()KeyBoardManagerlockView:切换需要锁定高度的 View赋值给内部属性 lockView
bindToEmotionButton()KeyBoardManagerView...: 多个 View 参数,切换控制按钮为输入控件绑定控制按钮
setEmotionView()KeyBoardManagerPandaEmoView: 被管理的表情控件绑定当前管理的输入控件(绑定的控件必须在此之前调用 PandaEmoView.attachEditText() 否则内部将会空指针)
interceptBackPress()booleannull在 Activity 的 backPressd() 中检查是否需要拦截返回键关闭输入栏而不是退出界面
hideInputLayout()voidnull供外部调用关闭输入栏(不能未初始化直接调用)
showInputLayout()voidnull供外部调用显示输入栏(不能未初始化直接调用)

EmoticonManager

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

StickerManager

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

PandaEmoTranslator

  • PandaEmoTranslator 为 emoji 表情 [文字] 转表情的转换工具类
方法返回值参数描述
getInstance()PandaEmoTranslatornull获取文本表情转换器单例
setMaxGifPerView()voidmaxGifPerView: 每个 TextView 控件最多显示动态表情的个数设置每个 TextView 控件最多显示动态表情的个数,超过此数全部显示为静态表情
getMaxGifPerView()intnull获取每个 TextView 控件最多显示动态表情的个数
makeGifSpannable()SpannableStringclassTag:PandaEmoView 依附的 ActivityTag(推荐使用 Activity.getLocalClassName());value : 待替换的文本 ;gif 运行回调(回调中需要刷新 TextView 重绘)整段图文混排,支持 gif 和静态 emoji
makeEmojiSpannable()SpannableStringclassTag:PandaEmoView 依附的 ActivityTag(推荐使用 Activity.getLocalClassName());value : 待替换的文本 ;gif 运行回调(回调中需要刷新 TextView 重绘)整段图文混排,所以内容转换为静态 emoji
resumeGif()voidactivityTag : makeGifSpannable() 传入的 tag 名开始 activityTag 对应的所有 gif 表情执行
pauseGif()void暂停所有的 Gif 表情运行
clearGif()voidactivityTag : makeGifSpannable() 传入的 tag 名停止 activityTag 对应的所有 gif 表情执行,并将期从任务栈中移除

关于内存优化

因为表情,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 缓存,避免重复加载创建新对象。

本库地址 PandaEmoView 欢迎 star 和提 issue


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值