一、什么是 Emoji
1.1 Emoji背景
Emoji 是一种 表情符号,来自日语词汇“絵文字”(假名为“えもじ”,读音即 emoji)。它的创造者是日本人栗田穰崇 ( Shigetaka Kurita ) ,他将目光投向儿时的各类元素以获取灵感,如日本漫画和日本汉字等。“日本漫画中有许多不一样的符号。漫画家会画出一些表情,表现一我的满头大汗或是迸发出一个想法时头上出现一个灯泡。”同时,从日本汉字中他得到了一种能力,用简单的字符来表达“秘密”和“爱”等抽象概念。
早期的 Emoji 表情并无一套统一的规范,日本的三大电信运营商,NTT DoCoMo,au/KDDI,Softbank 都各自有一套关于 Emoji 的编码规范,致使运营商用户之间发送 Emoji 表情时没法显示。直到2010年10月,随着 Unicode6.0 的发布,Emoji 的编码以及对应的表情图片正式被规范化,核心 Emoji 表情包含722个 Emoji 编码。
以后 2014年6月15日发布的 Unicode 7.0 规范以及 2016年6月22日发布的 Unicode 9 规范都不断地加入新的 Emoji 表情。目前,官方已经发布了Emoji 14.0,整个 Emoji 表情也达到了3600多个。具体细节可以查看unicode官网
不过,虽然 Emoji 已经被标准化,但是不同平台因为使用的字体不同,导致同样的 Unicode 代表的 Emoji,被渲染显示出来的效果也会不一样,如下。
1.2 Android 对 Emoji 的支持
在 Android 4.4 以前, Android 并不支持 emoji 表情,当时的解决方案主要是经过 imageSpan 配合 spannableString,来替换掉文字中的 emoji unicode 编码符号。从 Android 4.4 开始, 官方开始了 emoji 表情的支持,实现原理基本就是经过把 emoji 表情内置在系统的 ttf 字体库中,对文本进行过滤后显示出 emoji 表情。因为不一样 Android 版本内置的 ttf 字体库对 emoji 表情的版本支持程度不一样,致使老版本的 Android 对最新的 emoji 表情支持不全,因此一些 在新的 unicode 版本规范中被加入的 emoji 表情在老的 Android 设备上会显示方框乱码。为了处理这个问题,除去上文提到的 spannable 的处理方案,咱们还能够经过定义本身的 ttf 字体库给文本空间指定字体来显示 emoji 表情。
除此之外,谷歌官方也推出了EmojiCompat Support Library,目前这个库能向下兼容到 Android 4.4,其主要目标就是为了让咱们的 Android 设备可以支持最新的 emoji 表情,防止最新的 emoji 表情在咱们的手机上显示为☐。EmojiCompat 经过 CharSequence 文本中的 emoji 对应的 unicode 编码来识别 emoji 表情,将他们替换成 EmojiSpans ,最后再将 EmojiSpan 渲染成对应的 emoji 表情符号。
二、EmojiCompat
2.1 什么是EmojiCompat
EmojiCompat 是 Google 官方给我们提供的一个 Emoji 表情兼容库,最低支持到 Android 4.4(Api Level 19) 的系统设备,它可以防止应用中,出现以信封的形式来显示 Emoji,虽然它仅仅只是因为你当前的设备没有这个字体而已。通过 EmojiCompat ,你的设备无需等待 Android 系统更新,就可以获得最新的 Emoji 表情显示效果,原理如下。
可以看到,Emoji的一个显示原理,EmojiCompat 会判断当前设备是否支持这个 Emoji,如果支持则还是使用系统内置的字体加载,如果不支持,则使用 EmojiSpan 来进行替换,从而达到替换渲染的效果。
2.2 配置 EmojiCompat
EmojiCompat 提供两种字体的支持方式,它们分别是可下载的字体配置和本地捆绑的字体配置。
- 可下载的字体配置:可下载的字体的方式会在首次启动 app 的时候检查本地是否有该字体,没有的话会从网上下载最新的 Emoji 字体,然后遇到不支持的 Emoji,就会从这个字体文件中,加载资源并且渲染。
- 本地捆绑的字体配置:本地捆绑的方式会在 App 打包的过程中,植入一个最新的 Emoji 字体文件,然后遇到不支持的 Emoji,就会从这个字体文件中,加载资源并且渲染。
2.2.1 本地字体配置方式
首先,需要在 build.gradle添加emoji-bundled依赖,如下。
implementation 'androidx.emoji:emoji-bundled:1.1.0'
然后,初始化 构建本地捆绑字体配置EmojiCompat,由于初始化是耗时的,所以最好提前进行初始化,比如Application中。
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initEmoji()
}
private fun initEmoji() {
val config: EmojiCompat.Config = BundledEmojiCompatConfig(this)
config.setReplaceAll(true)
config.registerInitCallback(object : InitCallback() {
override fun onInitialized() {
//初始化成功回调
}
override fun onFailed(@Nullable throwable: Throwable?) {
//初始化失败回调
}
})
EmojiCompat.init(config)
}
}
2.2.2 可下载的字体配置
可下载的字体需要在 build.gradle添加emoji依赖,如下。
implementation 'androidx.emoji:emoji:1.1.0'
然后,构建可下载字体配置初始化 EmojiCompat,初始化的时候需要传入字体,如下。
private fun initEmojiCompat() {
val fontRequest = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
"Noto Color Emoji Compat",
R.array.emoji_list
)
val config: EmojiCompat.Config = FontRequestEmojiCompatConfig(this, fontRequest)
config.setReplaceAll(true)
config.registerInitCallback(object : InitCallback() {
override fun onInitialized() {
//初始化成功回调
}
override fun onFailed(throwable: Throwable?) {
//初始化失败回调
}
})
EmojiCompat.init(config)
}
2.3 使用EmojiCompat
初始化之后,接下来就是在业务开发中使用EmojiCompat。EmojiCompat 的处理逻辑,前面已经讲的很清楚了:首先,它会加载一个 Emoji 字体,然后判断当前设备是否支持需要显示的 Emoji,如果不支持,则使用 EmojiSpans 替换它,最终将处理过的 CharSequence 设置到 TextView 上。而这个过程,EmojiCompat 提供了一个process()方法。
从代码中可以看到,process()接受一个 CharSequence 并处理它,然后返回一个 CharSequence。举个例子:我们使用process()转换一个笑脸的表情。
EmojiCompat.get().process("笑脸: \uD83D\uDE01")
在实际项目中,如果每次都需要通过 EmojiCompat.get().process() 对字符串进行处理,其实挺麻烦的。为此,EmojiCompat 提供了EmojiTextView,EmojiButton,EmojiEditText等控件。
<androidx.emoji.widget.EmojiTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🤤" />
<androidx.emoji.widget.EmojiButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="❤" />
<androidx.emoji.widget.EmojiEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="😏" />
EmojiCompat 会判断当前设备是否支持这个 Emoji,如果支持则还是使用系统内置的字体加载,如果不支持,则使用 EmojiSpan 来进行替换,从而达到替换渲染的效果。这是在你没设置 config.setReplaceAll(true) 的情况下,而如果你设置了 config.setReplaceAll(true) ,那么所有的 Emoji 表情都会使用 EmojiSpan 替换并渲染。
2.4 自定义Emoji控件
当然,我们也可以自定义Emoji控件,自定义Emoji控件可以参考下EmojiAppCompatTextView 和 EmojiAppCompatEditView 中的实现。
三、Emoji2
由于之前的版本EmojiCompat 只兼容 Android 4.4 以上的设备,对于 4.4 如下的设备它的行为跟普通的 Android 组件是没有差别的。并且,EmojiCompat 的初始化时间大约只须要 150 毫秒,内存的占用大概在200kb,所以最近Google官方提供了Emoji2库。使用之前,需要先添加依赖,如下。
def emoji2_version = "1.0.0"
implementation "androidx.emoji2:emoji2:$emoji2_version"
implementation "androidx.emoji2:emoji2-views:$emoji2_version"
implementation "androidx.emoji2:emoji2-views-helper:$emoji2_version"
Emoji2一共提供了4个控件,EmojiButton、EmojiEditText、EmojiExtractTextLayout和EmojiTextView,使用方式和之前的差不多,比如。
<androidx.emoji2.widget.EmojiTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🤤"
tools:ignore="MissingConstraints" />
<androidx.emoji2.widget.EmojiButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="❤"
tools:ignore="MissingConstraints" />
<androidx.emoji2.widget.EmojiEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="😏"
tools:ignore="MissingConstraints" />
既然Emoji这么有趣,还不快在你的应用中使用 AppCompat 1.4。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。