QQ和微信的表情键盘大家应该是熟悉的不能再熟悉了吧。公司的一个项目里就需要一个和微信类似的表情键盘功能,在GitHub上找了一下,发现了XhsEmoticonsKeyboard。使用过一段时间后发现了一些问题。其中最大的一个问题就是:表情数据不能动态刷新。每增加一个表情就需要重新加载所有的数据。针对这个问题,我在XhsEmoticonsKeyboard的基础上做了一些改进,就有了EmoticonsBoard这个项目。
项目地址在这
效果
操作演示
实际看一下效果最直观
overlook.gif
这个效果和XhsEmoticonsKeyboard的没什么区别
表情数据动态刷新
change_data.gif这里演示了四种刷新方式
- 增加表情
- 删除表情
- 增加表情包
- 删除表情包
这里需要说明一下,什么是表情包?
表情包在代码中对应的类是EmoticonPack。一个EmoticonPack就代表了一个表情集合。比如上图中屏幕最下方的黄色微笑小图标就代表了一个表情包。
整体结构
screenshot的副本.jpgEmoticonsBoard就是整个键盘库的核心,和表情展示相关的主要有三个部分:EmoticonsFuncView,EmoticonsIndicator和EmoticonsToolBar。
- EmoticonsFuncView:展示每一页表情,继承自ViewPager
- EmoticonsIndicator:表情页指示器
- EmoticonsToolBar:展示表情包图标
这里我们只看一下表情相关的几个类,如果对其它的类有兴趣,可以下载代码自行研究。
刚才我们说了,展示表情的EmoticonsFuncView继承自ViewPager,既然是ViewPager,那么就应该有Adapter,它就是EmoticonPacksAdapter。
EmoticonPacksAdapter保存的是一个EmoticonPack列表。EmoticonPack刚才已经介绍过了,是表情包。EmoticonPack中有一个Emoticon列表。Emoticon就代表一个表情。EmoticonPack中还有一个属性:iconUri,它就是这个表情包图片的uri。每个EmoticonPack的图标最终会设置到EmoticonsToolBar里。而Emoticon列表就通过EmoticonPacksAdapter以页面的方式展示。
EmoticonsToolBar用来展示表情包图标,同时和可以在首尾增加两个View,比如增加和删除两个按钮。
类图中还有一个重要的接口:pageFactory,它的作用是生成ViewPager中每一页的View。通过它就能实现不同的页面展示。比如每页的行列数,点击表情时的效果,图标的大小等。
使用方法
Gradle
allprojects {
repositories {
jcenter()
}
}
and:
dependencies {
compile 'im.ll:emoticonsboard:1.0.0'
}
XML
<github.ll.emotionboard.EmoticonsBoard xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</github.ll.emotionboard.EmoticonsBoard>
EmoticonsBoard继承自RelativeLayout,所以它里面可以包含子View,但是它的子View只能有一个,多于一个的话会报错。
上面的例子就是包含了一个LinearLayout。
设置 Adapter
Java code:
EmoticonsBoard ekBar;
List<EmoticonPack> packs = new ArrayList<EmoticonPack>;
// init packs...
EmoticonPacksAdapter adapter = new EmoticonPacksAdapter(packs);
ekBar.setAdapter(adapter);
这里很简单,重点是如何构建EmoticonPack
构建
kotlin code
fun getEmoji(context: Context): EmoticonPack<Emoticon> {
val emojiArray = mutableListOf<Emoticon>()
DefEmoticons.sEmojiArray.take(30).mapTo(emojiArray) {
val emoticon = Emoticon()
emoticon.code = it.emoji
emoticon.uri = context.getResourceUri(it.icon)
return@mapTo emoticon
}
val pack = EmoticonPack<Emoticon>()
pack.emoticons = emojiArray
pack.iconUri = context.getResourceUri(R.mipmap.icon_emoji)
val factory = DeleteBtnPageFactory<Emoticon>()
factory.deleteIconUri = context.getResourceUri(R.mipmap.icon_del)
factory.line = 3
factory.row = 7
pack.pageFactory = factory
return pack
}
EmoticonPack有三个属性:emoticons就是表情列表。iconUri是这个表情包的图标,上面提到过。pageFactory是View工厂,上面也讲过。
这段代码首先是构造了一个Emoticon列表,Emoticon只有两个属性:code和uri。code是表情代码,uri是图片uri。
然后设置了EmoticonPack的PageFactory,在这用到的是DeleteBtnPageFactory,DeleteBtnPageFactory的功能就是能展示一个删除按钮。
EmoticonPack的创建很简单。这里有一点要注意一下,Emoticon我只提供了uri一个属性统一表示表情图片的地址。不管这个图片是资源,文件,还是网络图片,用一个uri就够了。这里的uri可以用android定义的也可以自己定义,只要能唯一确定图片的位置就行了。这里我写了一个工具类来获取各种uri:
kotlin code
enum class UriType {
ASSETS,
DRAWABLE,
FILE,
OTHER;
}
object UriUtils {
fun getUriType(uri: String): UriType {
if (uri.startsWith("android.resource:")) {
return UriType.DRAWABLE
}
if (uri.startsWith("file:///android_asset/")) {
return UriType.ASSETS
}
if (uri.startsWith("file://") && !uri.contains("android_asset")) {
return UriType.FILE
}
return UriType.OTHER
}
fun getResourceID(context: Context, uri: String) = uri.substring("android.resource://${context.packageName}/".length)
fun getFilePath(uri: String): String? {
return if (getUriType(uri) != UriType.FILE) {
null
} else {
uri.substring("file://".length)
}
}
fun getAssetsPath(uri: String): String? {
return if (getUriType(uri) != UriType.ASSETS) {
null
} else {
uri.substring("file:///android_asset/".length)
现在主流的图片加载库,比如Glide,都可以通过uri加载图片。如果你觉得还是用资源id方便,那么你也可以自己扩展Emoticon。
那么表情图标具体是在哪个地方加载呢?没错,就是在PageFactory里。
刷新数据
kotlin code
// modify mEmojiPack
mEmojiPack.isDataChanged = true
adapter?.notifyDataSetChanged()
非常简单,mEmojiPack数据改变后首先标记isDataChanged为true,然后调用notifyDataSetChanged方法就可以了。
这里注意,isDataChanged必须要设置为true
总结
以上就是整个项目的简单介绍。如果有兴趣就去下载代码看看吧。
网友评论