作者 | 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 自定义
-
应用 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
开源库