手机键盘输入法下载,发了表情,自动跳转到键盘模式,怎么设置

查看: 4102|回复: 4
Android 软键盘和emoji表情切换方案,和微信几乎一样的体验
签到天数: 23 天连续签到: 1 天[LV.4]偶尔看看III主题帖子e币
没有eoe的账号,级别还太低,出门如何吹牛逼?
才可以下载或查看,没有帐号?
本帖最后由 a12a15a05 于
17:05 编辑
注意:本项目还有一个小坑。第一次进去的时候有时候输入框没有得到焦点,没有弹出软键盘,所以不能更好的量取软键盘高度,给了一个默认值787 软键盘默认高度。所以可以忽略不计,一旦弹出了软键盘,这个高度就被记录下来了,存在本地,以便下一次用。
这几天没事,想到之前做im聊天的时候,表情输入和键盘之间的切换体验有些问题,看了微信的,觉得真好,就有了想描摹一下的心思,所有有了这个demo。站在巨人的肩膀上,我们才能走得更远。
emoji表情开源:/rockerhieu/emojicon
本demo开源地址 :/a12a15a05/KeyBoardDemo简书介绍地址 :/p/b6demo apk地址:
签到天数: 23 天连续签到: 1 天[LV.4]偶尔看看III主题帖子e币
排版好像有问题 简书介绍地址地址
签到天数: 179 天连续签到: 1 天[LV.7]常住居民III主题帖子e币
经验很宝贵 感谢分享 多谢楼主
签到天数: 32 天连续签到: 1 天[LV.5]常住居民I主题帖子e币
很宝贵 感谢分享 多谢楼
签到天数: 232 天连续签到: 1 天[LV.7]常住居民III主题帖子e币
感觉很不错。来看看。收藏了。
推荐阅读热门话题
61887420384328284281261252226215208204201192715
1&小时前昨天&23:59昨天&18:12昨天&17:22昨天&17:20昨天&17:19昨天&17:18昨天&16:18昨天&16:16昨天&15:35昨天&15:27昨天&15:23昨天&15:16昨天&12:49昨天&11:48昨天&11:46
Powered by
扫一扫 关注eoe官方微信【转】移动前端手机输入法自带emoji表情字符处理
时间: 15:27:16
&&&& 阅读:2078
&&&& 评论:
&&&& 收藏:1
标签:http://blog.csdn.net/binjly/article/details/  
  今天,测试给我提了一个BUG,说移动端输入emoji表情无法提交。很早以前就有思考过,手机输入法里自带的emoji表情,应该是某些特殊字符。既然是字符,那应该都能提交才对,可是为啥会被卡住呢?搜了一下,才发现,原来emoji用到的字符是4字节的utf-16(utf-16有2字节和4字节两种编码),而我们的数据库是采用的utf-8,并且最大只允许3字节的字符。这样冲突就产生了,表单因为这些emoji字符的存在无法提交。
  找到原因之后,接下来就要考虑解决方案了。目前考虑到的两种方案,一是让后台处理,把这个utf-16字符做一些转换(这里不做讨论)。第二种办法就是在前端直接转换成实体字符后再提交。这样,后台不用做任何处理,用户的提交的信息也得以保留,是不是一个两全其美的办法呢?接下来我们要讨论的就是怎么把emoji表情字符转换成实体字符。
  首先,我们来看看手机输入法里自带的emoji字符是什么样。下面截了一张图,来自&http://computerism.ru/emoji-smiles.htm 。我们看到,每个emoji表情字符对应的实体字符编码都比较大,如第一行的笑脸,实体字符为😊 。而且,我们注意到,后面还有一个16进制的编码 D83DDE0A。那这个编码是干嘛用的?接着往下看。
一、字符检测
  要想把这些emoji表情字符转换成实体字符,那么就要先把它们检测出来。说到字符检测,我们的正则这时就该上场了。首先我们得确定这些字符的范围。前面我们已经知道,emoji表情字符用的是4字节的utf-16编码,而4字节的utf-16编码不被后台接受。所以,我们的检测范围就变成了把所有4字节的utf-16编码检测出来。我们通过搜索查到,4字节的utf-16编码范围为U+010000到U+10FFFF,那么,我们的正则是不是可以这么写:/[\u010000-\u10FFFF]/g ? NO,你会发现这个正则完全不能按我们预期工作。这是为什么呢?
  上面这个问题,一些童鞋可能已经知道答案了。没错,就是javascript的编码问题引起的。我们知道,javascript采用的是unicode编码,再准确一点说,是ucs-2编码。从名字上,我们就已经知道,这种编码方案是2字节的。在2字节的编码中找4字节的字符,很显然并没那么简单。所以,我们得考虑一下,这个utf-16在ucs-2编码中是如何表示的呢?这里,我搜到了我们可爱的传教士&&阮老师的一篇文章 《Unicode与JavaScript详解》(/blog/2014/12/unicode.html) 。 简单来说,就是把utf-16的4字节字符,拆分成两个ucs-2的2字节字符。具体算法可参考阮老师的上述文章,本文就不详细讨论了。从阮老师的文章中,我们已经知道了,4字节utf-16在js中被用两个字符来表示,高位范围为0xD800 - 0xDBFF,低位范围为0xDC00 - 0xDFFF。那么我们用于检测的正则表达式也就出来了:/[\uD800-\uDBFF][\uDC00-\uDFFF]/g&。现在再回过头看看我们第一张图的那串16进制,D83DDE0A、D83DDE03,是不是突然就明白了呢?
二、转换算法
  现在,我们已经能够检测出表单里的emoji表情字符。那么,重头戏来了,我们怎么把这个字符转换成实体字符呢?我们知道,实体字符是用来表示单个字符的编码,而我们的emoji表情,在js里,却是用两个字符来表示的。这可怎么办?等等,谁说emoji是两个字符,说好的4字节单字符呢?没错,一开始emoji就是用utf-16表示的啊&这里,我又参考了另一篇文章,/cn/sets/emoji/,以下截了一部分图以做说明。
  我们还是以那个笑脸的字符为例,其utf-16编码为U+1F600,我们转成十进制看看。
  128512不正好是我们的实体编码😀 吗?所以,现在问题又变成了怎么取得emoji表情字符utf-16编码的问题了。可是,可是,我们刚刚已经知道了,在js里,emoji表情也是用ucs-2编码的啊,只不过变成了用两个字符来表示。那么,我们的问题最终演变成了怎么从ucs-2编码转换成utf-16编码的问题。
  感谢阮老师,在阮老师的那篇文章中,有提到utf-16转ucs-2(unicode)的公式
[javascript]&&
H&=&Math.floor((c-0x10000)&/&0x400)+0xD800&
L&=&(c&-&0x10000)&%&0x400&+&0xDC00&
  可是,这个是utf-16转ucs-2,我们要的是ucs-2转utf-16啊?怎么办?推导回去呗。我们先看看这两个公式都做了什么。首先,高位的公式,把字符C减0x10000,再除0x400,取其商,再加0xD800。而低位则是字符C减0x1000,取除0x400的余,再加0xDC00。所以这个字符其实被分成了两部分:商和余,然后再把处理后的商做为高位,加上处理后的余做为低位,这样组合成了ucs-2字符。我们知道,被除数=除数&商+余数。那么,我们也可以反过来,求得C/0x400的商,再加上C/0x400的余,不就能算出C了吗。为了便于计算,我们用Q表示C的商,用M表示C的余,那么就有了以下公式:
[javascript]&&
H&=&Q&-&0x10000&/&0x400&+&0xD800&&
L&=&M&-&0x10000&%&0x400&+&0xDC00&&
C&=&Q&*&0x400&+&M&&
H&=&Q&-&0x10000&/&0x400&+&0xD800&&
L&=&M&+&0xDC00&&
C&=&Q&*&0x400&+&M&&
H&*&0x400&+&L&=&Q&*&0x400&-&0x10000&/&0x400&*&0x400&+&0xD800&*&0x400&+&M&+&0xDC00&&
H&*&0x400&+&L&=&C&-&0x10000&+&0xD800&*&0x400&+&0xDC00&&
C&=&(H&-&0xD800)&*&0x400&+&0x10000&+&L&-&0xDC00&&
&  公式出来之后,相信大家已经知道怎么做了,不过最后还是献一下丑,把我自己写的一个处理函数提供给大家参考:
[javascript]&&
function&utf16toEntities(str)&{&&
&&&&var&patt=/[\ud800-\udbff][\udc00-\udfff]/g;&
&&&&str&=&str.replace(patt,&function(char){&&
&&&&&&&&&&&&var&H,&L,&&&
&&&&&&&&&&&&if&(char.length===2)&{&&
&&&&&&&&&&&&&&&&H&=&char.charCodeAt(0);&
&&&&&&&&&&&&&&&&L&=&char.charCodeAt(1);&
&&&&&&&&&&&&&&&&code&=&(H&-&0xD800)&*&0x400&+&0x10000&+&L&-&0xDC00;&
&&&&&&&&&&&&&&&&return&"&#"&+&code&+&";";&&
&&&&&&&&&&&&}&else&{&&
&&&&&&&&&&&&&&&&return&&&
&&&&&&&&&&&&}&&
&&&&&&&&});&&
&&&&return&&&
&  运行结果如下:
  细心的童鞋,在刚刚看那些参考文章的时候,也许已经发现了,其实并不是所有的emoji表情字符都是utf-16编码的,也有一部分落在了ucs-2编码的范围(即只用了两个字节)。不过这都不是重点,重点是,我们已经成功的把utf-16编码部分的emoji表情转换为了实体字符。标签:
&&国之画&&&& &&&&chrome插件
版权所有 京ICP备号-2
迷上了代码!您的位置: &
搜狗输入法手写键盘新功能介绍 输入即可生成表情图片
人气:518作者:佚名出处:软吧
近日,手机搜狗输入法最新版手写键盘加入了新功能,不用再键盘输入与手写涂鸦里面进行切换啦,那么其他具体功能都有哪些呢?下面就是软吧小编为大家带来的搜狗输入法手写键盘新功能介绍,一起来看看吧!
搜狗输入法手写键盘新功能介绍
此次搜狗输入法V8.12的重大更新是&键盘手写&和&键盘画板&功能,这项功能在键盘输入时无需切换模式即可轻松完成输入。
用户在开启&键盘手写&模式后,键盘区域自然变成了手写板,拼音输入过程中也能直接在键盘区手写,输入之后搜狗输入法会为手写出的字标注出拼音,不仅用户使用方便而且提升了输入效率。
该功能最有意思之处是,可以在键盘手写板上直接画出表情或者所想的图案,随后能直接生成图片发送,而有了这个功能每个人都能成为表情包强者了。
以上就是软吧小编为大家带来的搜狗输入法手写键盘新功能介绍,想要体验这么流弊功能吗?那就赶快下载:&&&&吧!
| | | | | | |
关注手机软件,关注,请向您的5个QQ好友宣传,多谢支持!Copyright (C) , All Rights Reserved .
深圳市艾秀信息技术有限公司 版权所有最近公司在项目上要使用到表情与键盘的切换输入,自己实现了一个,还是存在些缺陷,比如说键盘与表情切换时出现跳闪问题,这个相当困扰我,不过所幸在Github(其中一个不错的开源项目是/dss886/Android-EmotionInputDetector这个代码整体结构相当不错)和论坛上找些解决方案与思路,当然这里我也是研究了好多个开源项目的代码,然后把几个开源项目的代码优点结合自己的思路最后才整合出比较不错的实现效果,可以说跟微信基本一样(嘿嘿,只能说目前还没发现大Bug,若发现大家一起日后慢慢完善,这里我也只是给出了实现方案,拓展其他表情我并没有实现哈,不过代码中我实现了一个可拓展的fragment模板以便大家实现自己的表情包),我只是实现了一页表情,代码我也进行另外的封装与拓展,大家需要多表情的话只需要实现自己的表情fragment界面,然后根据工厂类获取即可,废话不多说先上图看效果:
& & & & &&
效果还不错吧,哈哈。下面开始介绍:
本篇主要分析的核心类EmotionKeyboard.java,EmotionComplateFragment.java,EmotionMainFragment.java,FragmentFactory.java,还有一个是工具类里的EmotionUtils.java和GlobalOnItemClickManagerUtils.java&这几个类我会重点分析一下,其他的大家自行看源码哈。
嗯,下面就开始咯,先来看看本篇主要内容以及大概思路:
1.解决表情与键盘切换跳闪问题
为了让大家对这个问题有一定了解,我先来个简单案例,用红色面板代表表情面板,效果如下:
& && & & & & & &
先看左图,通过左图我们可以看出,当表情显示时,我们点击表情按钮,隐藏表情显示软件盘时,内容Bar有一个明显的先向下后恢复的跳闪现象,这样用户体验相当的差,实际上我们期望的是右图的效果,无论怎么切换都不会有跳闪现象,这就是我所有说的键盘与表情切换的跳闪问题。
&&&&大家对这个问题有了大概了解后,我们再来深入分析如何实现右图的不跳闪效果。这里我们做个约定,我们把含有表情那个bar统称为内容Bar。
&& &解决这个问题的思路:
&&&&android系统在弹出软键盘时,会把我们的内容 Bar 顶上去,因此只有表情面板的高度与软键盘弹出时高度一致时,才有可能然切换时高度过渡更自然,所以我们必须计算出软键盘的高度并设置给表情面板。仅仅有这一步跳闪问题还是依旧存在,因此这时我们必须想其他办法固定内容Bar,因为所有的跳闪原因都是表情面板隐藏,而软键盘往上托出瞬间,Activity高度变高(为什么会变高后面会说明),内容Bar往下滑后,又被软键盘顶回原来位置造成的。因此只要固定了内容Bar的位置,闪跳问题就迎刃而解了。那么如何固定内容Bar的位置呢?我们知道在一个布局中一个控件的位置其实是由它上面所有控件的高度决定的,如果其上面其他控件的高度不变,那么当前控件的高度自然也不会变化,即使到时Activity的高度发生了变化也也不会影响该控件的位置(整个界面的显示是挂载在window窗体上的,而非Activity,不了解的可以先研究一下窗体的创建过程),因此我们只要在软键盘弹出前固定内容Bar上面所有控件高度,从而达到固定内容Bar位置(高度)的目的。
&&&&好了,有思路了,我们再一步步按上面思路解决:
&&&&1.先获取键盘高度,并设置表情面板的高度为软键盘的高度。Android系统在界面上弹出软键盘时会将整个Activity的高度压缩,此时windowSoftInputMode属性设置为adjustResize(对windowSoftInputMode不清楚的话,请自行查阅相关资料哈),这个属性表示Activity的主窗口总是会被调整大小,从而保证软键盘显示空间。在这种情况下我们可以通过以下方法计算软键盘的高度:
Rect r = new Rect();
* decorView是window中的最顶层view,可以从window中通过getDecorView获取到decorView。
* 通过decorView获取到程序显示的区域,包括标题栏,但不包括状态栏。
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
//获取屏幕的高度
int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight();
//计算软件盘的高度
int softInputHeight = screenHeight - r.或许有些人对r.bottom和mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r)不太理解,这里我简单解释下,直接上图:
这下就清晰了吧,右边是Rect参数解析图,辅助大家对rect的理解。
Rect r = new Rect();
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r)
这两句其实将左图中蓝色边框( 其实也就是actvity的大小)的size大小参数封装到Rect中,以便我们后续使用。虽然计算出来的区域大小不包含状态栏,但是r.bottom(红色箭头长度)的大小是从屏幕顶部开始计算的所以包含了状态栏的高度。
注意:区域大小是这样计算出来的:区域的高:r.bottom-r.top , 区域的宽:r.right-r.left ,当然这个跟计算软键盘高度没关系,只是顺带提一下。
因此我们可以通过屏幕高度-r.bottom即可获取到软键盘高度。
&2.固定内容Bar的高度,解决闪跳问题
软键盘高度解决后,现在剩下的问题关键就在于控制内容Bar的高度了,那么如何做呢?我们先来看一个布局文件
&?xml version=&1.0& encoding=&utf-8&?&
&LinearLayout xmlns:android=&/apk/res/android&
android:orientation=&vertical& android:layout_width=&match_parent&
android:layout_height=&match_parent&&
android:id=&@+id/listview&
android:layout_weight=&1&
android:layout_width=&match_parent&
android:layout_height=&0dp&
&FrameLayout
android:id=&@+id/fl_emotionview_main&
android:layout_width=&match_parent&
android:layout_height=&wrap_content& /&
&/LinearLayout&
其中ListView的layout_height为0dp、layout_weight为1,这样这个ListView就会自动充满整个布局,这里ListView可以替换成任意控件,FrameLayout则为表情布局(也可认为就是我们前面所说的内容Bar,只不过这里最终会被替换成整个表情布局),我们的目的就是在弹出软键盘时固定FrameLayout的高度,以便去除跳闪问题。根据我们前面的思路,FrameLayout的高度是由其上面的控件决定的也就是由ListView决定的,也就是说我们只要在软键盘弹出前固定ListView的内容高度即可。
因此我们可以通过下面的方法来锁定ListView的高度,(mContentView就是我们所指的ListView,这些方法都封装在EmotionKeyboard.java类中)
* 锁定内容高度,防止跳闪
private void lockContentHeight(){
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams();
params.height = mContentView.getHeight();
params.weight = 0.0F;
}将weight置0,然后将height设置为当前的height,在父控件(LinearLayout)的高度变化时它的高度也不再会变化。释放ListView的高度:
private void unlockContentHeightDelayed() {
mEditText.postDelayed(new Runnable() {
public void run() {
((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;
}其中的LinearLayout.LayoutParams.weight = 1.0F;,在代码里动态更改LayoutParam的weight,会导致父控件重新onLayout(),也就达到改变控件的高度的目的。
到此两个主要问题都解决了,我们直接上核心类代码,该类来自github上的开源项目(& )我在使用中直接从该项目中抽取了该类, 并做了细微修改,也添加了代码注释。
package com.zejian.emotionkeyboard.
import android.annotation.TargetA
import android.app.A
import android.content.C
import android.content.SharedP
import android.graphics.R
import android.os.B
import android.util.DisplayM
import android.view.MotionE
import android.view.V
import android.view.WindowM
import android.view.inputmethod.InputMethodM
import android.widget.EditT
import android.widget.LinearL
import com.zejian.emotionkeyboard.utils.LogU
* author : zejian
* time : 日 上午11:14:27
* description :源码来自开源项目/dss886/Android-EmotionInputDetector
本人仅做细微修改以及代码解析
public class EmotionKeyboard {
private static final String SHARE_PREFERENCE_NAME = &EmotionKeyboard&;
private static final String SHARE_PREFERENCE_SOFT_INPUT_HEIGHT = &soft_input_height&;
private Activity mA
private InputMethodManager mInputM//软键盘管理类
private SharedP
private View mEmotionL//表情布局
private EditText mEditT//
private View mContentV//内容布局view,即除了表情布局或者软键盘布局以外的布局,用于固定bar的高度,防止跳闪
private EmotionKeyboard(){
* 外部静态调用
* @param activity
public static EmotionKeyboard with(Activity activity) {
EmotionKeyboard emotionInputDetector = new EmotionKeyboard();
emotionInputDetector.mActivity =
emotionInputDetector.mInputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
emotionInputDetector.sp = activity.getSharedPreferences(SHARE_PREFERENCE_NAME, Context.MODE_PRIVATE);
return emotionInputD
* 绑定内容view,此view用于固定bar的高度,防止跳闪
* @param contentView
public EmotionKeyboard bindToContent(View contentView) {
mContentView = contentV
* 绑定编辑框
* @param editText
public EmotionKeyboard bindToEditText(EditText editText) {
mEditText = editT
mEditText.requestFocus();
mEditText.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && mEmotionLayout.isShown()) {
lockContentHeight();//显示软件盘时,锁定内容高度,防止跳闪。
hideEmotionLayout(true);//隐藏表情布局,显示软件盘
//软件盘显示后,释放内容高度
mEditText.postDelayed(new Runnable() {
public void run() {
unlockContentHeightDelayed();
* 绑定表情按钮
* @param emotionButton
public EmotionKeyboard bindToEmotionButton(View emotionButton) {
emotionButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (mEmotionLayout.isShown()) {
lockContentHeight();//显示软件盘时,锁定内容高度,防止跳闪。
hideEmotionLayout(true);//隐藏表情布局,显示软件盘
unlockContentHeightDelayed();//软件盘显示后,释放内容高度
if (isSoftInputShown()) {//同上
lockContentHeight();
showEmotionLayout();
unlockContentHeightDelayed();
showEmotionLayout();//两者都没显示,直接显示表情布局
* 设置表情内容布局
* @param emotionView
public EmotionKeyboard setEmotionView(View emotionView) {
mEmotionLayout = emotionV
public EmotionKeyboard build(){
//设置软件盘的模式:SOFT_INPUT_ADJUST_RESIZE
这个属性表示Activity的主窗口总是会被调整大小,从而保证软键盘显示空间。
//从而方便我们计算软件盘的高度
mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN |
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
//隐藏软件盘
hideSoftInput();
* 点击返回键时先隐藏表情布局
public boolean interceptBackPress() {
if (mEmotionLayout.isShown()) {
hideEmotionLayout(false);
private void showEmotionLayout() {
int softInputHeight = getSupportSoftInputHeight();
if (softInputHeight == 0) {
softInputHeight = sp.getInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, 400);
hideSoftInput();
mEmotionLayout.getLayoutParams().height = softInputH
mEmotionLayout.setVisibility(View.VISIBLE);
* 隐藏表情布局
* @param showSoftInput 是否显示软件盘
private void hideEmotionLayout(boolean showSoftInput) {
if (mEmotionLayout.isShown()) {
mEmotionLayout.setVisibility(View.GONE);
if (showSoftInput) {
showSoftInput();
* 锁定内容高度,防止跳闪
private void lockContentHeight() {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams();
params.height = mContentView.getHeight();
params.weight = 0.0F;
* 释放被锁定的内容高度
private void unlockContentHeightDelayed() {
mEditText.postDelayed(new Runnable() {
public void run() {
((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;
* 编辑框获取焦点,并显示软件盘
private void showSoftInput() {
mEditText.requestFocus();
mEditText.post(new Runnable() {
public void run() {
mInputManager.showSoftInput(mEditText, 0);
* 隐藏软件盘
private void hideSoftInput() {
mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
* 是否显示软件盘
private boolean isSoftInputShown() {
return getSupportSoftInputHeight() != 0;
* 获取软件盘的高度
private int getSupportSoftInputHeight() {
Rect r = new Rect();
* decorView是window中的最顶层view,可以从window中通过getDecorView获取到decorView。
* 通过decorView获取到程序显示的区域,包括标题栏,但不包括状态栏。
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
//获取屏幕的高度
int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight();
//计算软件盘的高度
int softInputHeight = screenHeight - r.
* 某些Android版本下,没有显示软键盘时减出来的高度总是144,而不是零,
* 这是因为高度是包括了虚拟按键栏的(例如华为系列),所以在API Level高于20时,
* 我们需要减去底部虚拟按键栏的高度(如果有的话)
if (Build.VERSION.SDK_INT &= 20) {
// When SDK Level &= 20 (Android L), the softInputHeight will contain the height of softButtonsBar (if has)
softInputHeight = softInputHeight - getSoftButtonsBarHeight();
if (softInputHeight & 0) {
LogUtils.w(&EmotionKeyboard--Warning: value of softInputHeight is below zero!&);
//存一份到本地
if (softInputHeight & 0) {
sp.edit().putInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, softInputHeight).apply();
return softInputH
* 底部虚拟按键栏的高度
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private int getSoftButtonsBarHeight() {
DisplayMetrics metrics = new DisplayMetrics();
//这个方法获取可能不是真实屏幕的高度
mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
int usableHeight = metrics.heightP
//获取当前屏幕的真实高度
mActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
int realHeight = metrics.heightP
if (realHeight & usableHeight) {
return realHeight - usableH
* 获取软键盘高度
public int getKeyBoardHeight(){
return sp.getInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, 400);
}EmotionKeyboard类使用的是设计模式中的builder模式来创建对象。其中mEmotionLayout是表情布局,mContentView是内容布局view,即除了表情布局或者软键盘布局以外的布局,用于固定bar的高度,防止跳闪,当然mContentView可以是任意布局。
& & * 绑定表情按钮
& & *&@param&emotionButton
& & *&@return
& &&public&EmotionKeyboard bindToEmotionButton(View&emotionButton)
& & & &&emotionButton.setOnClickListener(new&View.OnClickListener()
& & & & & &&@Override
& & & & & &&public&void&onClick(View&v)
& & & & & & & &&if&(mEmotionLayout.isShown())
& & & & & & & & & & lockContentHeight();//显示软件盘时,锁定内容高度,防止跳闪。
& & & & & & & & & & hideEmotionLayout(true);//隐藏表情布局,显示软件盘
& & & & & & & & & & unlockContentHeightDelayed();//软件盘显示后,释放内容高度
& & & & & & & & }&else&{
& & & & & & & & & &&if&(isSoftInputShown()) {//同上
& & & & & & & & & & & & lockContentHeight();
& & & & & & & & & & & & showEmotionLayout();
& & & & & & & & & & & & unlockContentHeightDelayed();
& & & & & & & & & & }&else&{
& & & & & & & & & & & & showEmotionLayout();//两者都没显示,直接显示表情布局
& & & & & & & & & & }
& & & & & & & & }
& & & & & & }
& & & & });
& & & &&return&this;
这里我主要重点说明一下点击表情按钮时,显示或者隐藏表情布局以及软键盘的逻辑:
首先我们通过mEmotionLayout.isShown()去判断表情是否已经显示,如果返回true,这时肯定要去切换成软键盘,因此必须先通过lockContentHeight()方法锁定mContentView内容高度,然后通过hideEmotionLayout(true)方法因此表情布局并显示软键盘,这里传入true表示显示软键盘,如果传入false则表示不显示软键盘,软键盘显示后通过unlockContentHeightDelayed()方法去解锁mContentView内容高度。
但如果mEmotionLayout.isShown()返回了false,这有两种情况,第1种是如果此时软键盘已经显示,则需先锁定mContentView内容高度,再去隐藏软键盘,然后显示表情布局,最后再解锁mContentView内容高度。第2种情况是软键盘和表情都没显示,这下就简单了,直接显示表情布局即可。
好,这个类解析到这,其他直接看源码哈,注释杠杠的。
最后我们来试着在外部使用该类,使用例子如下:
package com.zejian.emotionkeyboard.
import android.content.C
import android.support.v4.view.ViewP
import android.util.AttributeS
import android.view.MotionE
* Created by zejian
16/1/7 上午11:12
* Description:不可横向滑动的ViewPager
public class NoHorizontalScrollerViewPager extends ViewPager{
public NoHorizontalScrollerViewPager(Context context) {
super(context);
public NoHorizontalScrollerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
* 重写拦截事件,返回值设置为false,这时便不会横向滑动了。
* @param ev
public boolean onInterceptTouchEvent(MotionEvent ev) {
* 重写拦截事件,返回值设置为false,这时便不会横向滑动了。
* @param ev
public boolean onTouchEvent(MotionEvent ev) {
NoHorizontalScrollerViewPager+RecyclerView+Fragment实现思路:
我们以NoHorizontalScrollerViewPager作为载体,fragment作为展示界面,RecyclerView作为底部滚动条,每当点击RecyclerView的item时,我们使用viewPager.setCurrentItem(position,false)方法来切换fragment界面即可(这里传入false是表示不需要viewPager的切换动画)。这样我们就可以实现不同类表情的切换了。(提示一下这里所指的fragment其实是就工程目录中的EmotiomComplateFragment.java类)这个比较简单,就不多啰嗦了。实现代码稍后会一起提供。
3.表情面板的实现思路
&&3.1 表情图片的本质与显示
&&&&表情的显示从直观上看确实是一个图片,但实际只是一种特殊的文本(ImageSpan),比如微博里表情就是&[表情名字]&的接口,可爱的表情就是[可爱]...因此这里我打算利用&[表情名字]&作为key,图片的r值作为内容进行存取,EmotionUtils类如下
package com.zejian.emotionkeyboard.
import android.support.v4.util.ArrayM
import com.zejian.emotionkeyboard.R;
* @author : zejian
* @time : 日 上午11:32:33
* @email :
* @description :表情加载类,可自己添加多种表情,分别建立不同的map存放和不同的标志符即可
public class EmotionUtils {
* 表情类型标志符
public static final int EMOTION_CLASSIC_TYPE=0x0001;//经典表情
* key-表情文字;
* value-表情图片资源
public static ArrayMap&String, Integer& EMPTY_MAP;
public static ArrayMap&String, Integer& EMOTION_CLASSIC_MAP;
EMPTY_MAP = new ArrayMap&&();
EMOTION_CLASSIC_MAP = new ArrayMap&&();
EMOTION_CLASSIC_MAP.put(&[呵呵]&, R.drawable.d_hehe);
EMOTION_CLASSIC_MAP.put(&[嘻嘻]&, R.drawable.d_xixi);
EMOTION_CLASSIC_MAP.put(&[哈哈]&, R.drawable.d_haha);
EMOTION_CLASSIC_MAP.put(&[爱你]&, R.drawable.d_aini);
EMOTION_CLASSIC_MAP.put(&[挖鼻屎]&, R.drawable.d_wabishi);
EMOTION_CLASSIC_MAP.put(&[吃惊]&, R.drawable.d_chijing);
EMOTION_CLASSIC_MAP.put(&[晕]&, R.drawable.d_yun);
EMOTION_CLASSIC_MAP.put(&[泪]&, R.drawable.d_lei);
EMOTION_CLASSIC_MAP.put(&[馋嘴]&, R.drawable.d_chanzui);
EMOTION_CLASSIC_MAP.put(&[抓狂]&, R.drawable.d_zhuakuang);
EMOTION_CLASSIC_MAP.put(&[哼]&, R.drawable.d_heng);
EMOTION_CLASSIC_MAP.put(&[可爱]&, R.drawable.d_keai);
EMOTION_CLASSIC_MAP.put(&[怒]&, R.drawable.d_nu);
EMOTION_CLASSIC_MAP.put(&[汗]&, R.drawable.d_han);
EMOTION_CLASSIC_MAP.put(&[害羞]&, R.drawable.d_haixiu);
EMOTION_CLASSIC_MAP.put(&[睡觉]&, R.drawable.d_shuijiao);
EMOTION_CLASSIC_MAP.put(&[钱]&, R.drawable.d_qian);
EMOTION_CLASSIC_MAP.put(&[偷笑]&, R.drawable.d_touxiao);
EMOTION_CLASSIC_MAP.put(&[笑cry]&, R.drawable.d_xiaoku);
EMOTION_CLASSIC_MAP.put(&[doge]&, R.drawable.d_doge);
EMOTION_CLASSIC_MAP.put(&[喵喵]&, R.drawable.d_miao);
EMOTION_CLASSIC_MAP.put(&[酷]&, R.drawable.d_ku);
EMOTION_CLASSIC_MAP.put(&[衰]&, R.drawable.d_shuai);
EMOTION_CLASSIC_MAP.put(&[闭嘴]&, R.drawable.d_bizui);
EMOTION_CLASSIC_MAP.put(&[鄙视]&, R.drawable.d_bishi);
EMOTION_CLASSIC_MAP.put(&[花心]&, R.drawable.d_huaxin);
EMOTION_CLASSIC_MAP.put(&[鼓掌]&, R.drawable.d_guzhang);
EMOTION_CLASSIC_MAP.put(&[悲伤]&, R.drawable.d_beishang);
EMOTION_CLASSIC_MAP.put(&[思考]&, R.drawable.d_sikao);
EMOTION_CLASSIC_MAP.put(&[生病]&, R.drawable.d_shengbing);
EMOTION_CLASSIC_MAP.put(&[亲亲]&, R.drawable.d_qinqin);
EMOTION_CLASSIC_MAP.put(&[怒骂]&, R.drawable.d_numa);
EMOTION_CLASSIC_MAP.put(&[太开心]&, R.drawable.d_taikaixin);
EMOTION_CLASSIC_MAP.put(&[懒得理你]&, R.drawable.d_landelini);
EMOTION_CLASSIC_MAP.put(&[右哼哼]&, R.drawable.d_youhengheng);
EMOTION_CLASSIC_MAP.put(&[左哼哼]&, R.drawable.d_zuohengheng);
EMOTION_CLASSIC_MAP.put(&[嘘]&, R.drawable.d_xu);
EMOTION_CLASSIC_MAP.put(&[委屈]&, R.drawable.d_weiqu);
EMOTION_CLASSIC_MAP.put(&[吐]&, R.drawable.d_tu);
EMOTION_CLASSIC_MAP.put(&[可怜]&, R.drawable.d_kelian);
EMOTION_CLASSIC_MAP.put(&[打哈气]&, R.drawable.d_dahaqi);
EMOTION_CLASSIC_MAP.put(&[挤眼]&, R.drawable.d_jiyan);
EMOTION_CLASSIC_MAP.put(&[失望]&, R.drawable.d_shiwang);
EMOTION_CLASSIC_MAP.put(&[顶]&, R.drawable.d_ding);
EMOTION_CLASSIC_MAP.put(&[疑问]&, R.drawable.d_yiwen);
EMOTION_CLASSIC_MAP.put(&[困]&, R.drawable.d_kun);
EMOTION_CLASSIC_MAP.put(&[感冒]&, R.drawable.d_ganmao);
EMOTION_CLASSIC_MAP.put(&[拜拜]&, R.drawable.d_baibai);
EMOTION_CLASSIC_MAP.put(&[黑线]&, R.drawable.d_heixian);
EMOTION_CLASSIC_MAP.put(&[阴险]&, R.drawable.d_yinxian);
EMOTION_CLASSIC_MAP.put(&[打脸]&, R.drawable.d_dalian);
EMOTION_CLASSIC_MAP.put(&[傻眼]&, R.drawable.d_shayan);
EMOTION_CLASSIC_MAP.put(&[猪头]&, R.drawable.d_zhutou);
EMOTION_CLASSIC_MAP.put(&[熊猫]&, R.drawable.d_xiongmao);
EMOTION_CLASSIC_MAP.put(&[兔子]&, R.drawable.d_tuzi);
* 根据名称获取当前表情图标R值
* @param EmotionType 表情类型标志符
* @param imgName 名称
public static int getImgByName(int EmotionType,String imgName) {
Integer integer=
switch (EmotionType){
case EMOTION_CLASSIC_TYPE:
integer = EMOTION_CLASSIC_MAP.get(imgName);
LogUtils.e(&the emojiMap is null!!&);
return integer == null ? -1 :
* 根据类型获取表情数据
* @param EmotionType
public static ArrayMap&String, Integer& getEmojiMap(int EmotionType){
ArrayMap EmojiMap=
switch (EmotionType){
case EMOTION_CLASSIC_TYPE:
EmojiMap=EMOTION_CLASSIC_MAP;
EmojiMap=EMPTY_MAP;
return EmojiM
}ArrayMap&String, Integer& EMPTY_MAP是一个空集合这个主要是防止空指针情况的,而ArrayMap&String, Integer& EMOTION_CLASSIC_MAP则是我们前所显示经典表情集合,这个集合有一个标志(public static final int EMOTION_CLASSIC_TYPE=0x0001)这个标志是在获取表情集合和表情图片的唯一标识,也就是说我们需要从EMOTION_CLASSIC_MAP集合中获取表情时必须通过标志符辨别,我们往下看可以看到getEmojiMap(int
EmotionType),这个方法就是根据EmotionType这个标志类型来获取我们表情集合的,同时这也说明了,如果我们自己需要添加别的表情类型,这时就需要在这个类中创建一个新的集合,同时还必须创建一个新的标志类型来区别这个集合。可能大家还是不是很理解,这里我再贴一下工厂类的获取方法大家可能就明白了
* 获取fragment的方法
* @param emotionType 表情类型,用于判断使用哪个map集合的表情
public Fragment getFragment(int emotionType){
Bundle bundle = new Bundle();
bundle.putInt(FragmentFactory.EMOTION_MAP_TYPE,emotionType);
EmotiomComplateFragment fragment= EmotiomComplateFragment.newInstance(EmotiomComplateFragment.class,bundle);
}调用时,如下:
//创建fragment的工厂类
FragmentFactory factory=FragmentFactory.getSingleFactoryInstance();
//创建修改实例
EmotiomComplateFragment f1= (EmotiomComplateFragment) factory.getFragment(EmotionUtils.EMOTION_CLASSIC_TYPE);
这里我们通过工厂类getFragment(int emotionType)方法的创建出模版表情类EmotiomComplateFragment,为什么说是模版呢,因为只要我们创建时传递集合标志不同,例如经典表情传递的就是EmotionUtils.EMOTION_CLASSIC_TYPE,这时EmotiomComplateFragment类内部就会根据传递的集合类型去EmotionUtils类中获取相对应的集合,这样也就会创建出我们所需要的表情面板。
这里小结一下:通过上术分析我们可以知道如果我们要添加自己的其他类型表情,只需以下步骤:
步骤1.在EmotionUtils类创建一个表情集合,并赋予这个集合唯一标志
步骤2.在EmotionUtils类中的两个获取方法中完善相应的代码。
步骤3.在创建新的EmotiomComplateFragment模板类时,传递相应的集合标志符即可创建相应的表情面板。
& & 接下来的问题就是表情如何显示呢?其实这里主要用到了SpannableString拓展性字符串相关知识点,SpannableString可以让一段字符串在显示的时候,将其中某小段文字附着上其他内容或替换成其他内容,拓展内容可以是图片或者是文字格式,比如加粗,显示特殊颜色等。对于SpannableString不熟悉,可先看看这篇文章&&
下面我只对本篇需要用到的SpannableString作简要介绍:
ImageSpan,这个是可以将指定的特殊字符替换成我们所需要的图片。也就是我们可以使用&[表情名字]&这个key作为指定的特殊字符,然后在文本中替换成该key所对应的特殊表情即可。
简单实例如下:
SpannableString spannableString = new SpannableString(source);
int size = (int) tv.getTextSize()*13/10;
Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes);
Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
ImageSpan span = new ImageSpan(context, scaleBitmap);
spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
首先将我们要替换的字符串转换成SpannableString再创建一个ImageSpan并把我们的表情图片包含在内,最后利用SpannableString的setSpan方法,将span对象设置在对应位置,这样就完成了特殊字符与文字的转换。参数解析如下,
start就是需要附着的内容的开始位置
end是需要附着的内容的开始位置
flag标志位,这里是最常用的EXCLUSIVE_EXCLUSIVE的表示span拓展文本不包含前后(这个参数还有其他类型,这里不过多介绍)
3.2 利用正则表达式找出特殊字符便于转换成表情
这里我们利用正则表达式找出特殊字符,根据我们自己的需求编写特定的正则表达式,如下:
String regex = &\\[[\u4e00-\u9fa5\\w]+\\]&;
其中[]是我们特殊需要的字符,因此必须使用“//”进行转义,\u4e00-\u9fa5表示中文,\\w表示下划线的任意单词字符,+ 代表一个或者多个,
因此这段正则就代表,匹配方括号内有一或多个文字和单词字符的文本。有了正则表达式,剩下就是找匹配的问题了,这里我们可以先用
matcher.find()获取到匹配的开始位置,作为setSpan的start值,再使用matcher.group()方法获取到匹配规则的具体表情文字。
对于matcher.find()和matcher.group()这里简单介绍一下
matcher.find()代表部分匹配,从当前位置开始匹配,找到一个匹配的子串,将移动下次匹配的位置。因此我们可以通过这个方法获取到匹配的
开始位置,作为setSpan的start值(如果字符串中有多个表情就会执行多次匹配)。
matcher.group(),获取匹配到的具体字符。
下面直接上SpanStringUtils.java类对代码:
package com.zejian.emotionkeyboard.
import android.content.C
import android.content.res.R
import android.graphics.B
import android.graphics.BitmapF
import android.text.S
import android.text.SpannableS
import android.text.style.ImageS
import android.widget.TextV
import java.util.regex.M
import java.util.regex.P
* @author : zejian
* @time : 日 上午11:30:39
* @email :
* @description :文本中的emojb字符处理为表情图片
public class SpanStringUtils {
public static SpannableString getEmotionContent(int emotion_map_type,final Context context, final TextView tv, String source) {
SpannableString spannableString = new SpannableString(source);
Resources res = context.getResources();
String regexEmotion = &\\[([\u4e00-\u9fa5\\w])+\\]&;
Pattern patternEmotion = pile(regexEmotion);
Matcher matcherEmotion = patternEmotion.matcher(spannableString);
while (matcherEmotion.find()) {
// 获取匹配到的具体字符
String key = matcherEmotion.group();
// 匹配字符串的开始位置
int start = matcherEmotion.start();
// 利用表情名字获取到对应的图片
Integer imgRes = EmotionUtils.getImgByName(emotion_map_type,key);
if (imgRes != null) {
// 压缩表情图片
int size = (int) tv.getTextSize()*13/10;
Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes);
Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
ImageSpan span = new ImageSpan(context, scaleBitmap);
spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableS
代码相对比较简单,这里就不啰嗦啦。
3.3 表情面板的实现(ViewPager+GridView)
这里的自然就是使用到ViewPager和GridView相结合实现多界面滑动的效果,参考了微信的实现,每页都是一个GridView显示20个表情,末尾还有一个删除按钮。
实现思路:
利用ViewPager作为滑动控件,同时结合GridView来布局每个表情,GridView会显示3行7列,共21个Item,即每页都是一个GridView显示20个表情,末尾还有一个删除按钮。为了让Item能大小合适,我们在这里利用动态计算的方式设置宽高,因为屏幕宽度各有不同。每个item宽度的计算方式,由(屏幕的宽度-左右边距大小(如果有的话就减去)-每个item间隙距离)/7,最终便得到item的宽度。至于表情面板的高度=(item宽度*3+间隙*6),即可获取中高度,为什么间隙*6?这里并没有什么计算原理,纯粹是我在调试的过程中试出来的值,这个值相对比较合理,也比较美观,当然大家也可根据自己需要调整。最后就是有多少页的问题了,这里可以通过for循环表情集合的所有元素,把每次循环获取的元素添加到一个集合中,每次判断集合是否满20个元素,每满20个集合就利用该集合去创建一个GridView的表情面板View,同时再新建一个集合存放新获取到的元素,以次循环。最后把所有表情生成的一个个GridView放到一个总view集合中,利用ViewPager显示即可。要注意的是在GridView的适配器和点击事件中,都利用position判断,如果是最后一个就进行特殊的显示(删除按钮)和点击处理。
package com.zejian.emotionkeyboard.
import android.os.B
import android.support.v4.view.ViewP
import android.view.LayoutI
import android.view.V
import android.view.ViewG
import android.widget.GridV
import android.widget.LinearL
import com.zejian.emotionkeyboard.R;
import com.zejian.emotionkeyboard.adapter.EmotionGridViewA
import com.zejian.emotionkeyboard.adapter.EmotionPagerA
import com.zejian.emotionkeyboard.emotionkeyboardview.EmojiIndicatorV
import com.zejian.emotionkeyboard.utils.DisplayU
import com.zejian.emotionkeyboard.utils.EmotionU
import com.zejian.emotionkeyboard.utils.GlobalOnItemClickManagerU
import java.util.ArrayL
import java.util.L
* Created by zejian
16/1/5 下午4:32
* Description:可替换的模板表情,gridview实现
public class EmotiomComplateFragment extends BaseFragment {
private EmotionPagerAdapter emotionPagerGvA
private ViewPager vp_complate_emotion_
private EmojiIndicatorView ll_point_//表情面板对应的点列表
private int emotion_map_
* 创建与Fragment对象关联的View视图时调用
* @param inflater
* @param container
* @param savedInstanceState
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_complate_emotion, container, false);
initView(rootView);
initListener();
return rootV
* 初始化view控件
protected void initView(View rootView){
vp_complate_emotion_layout = (ViewPager) rootView.findViewById(R.id.vp_complate_emotion_layout);
ll_point_group= (EmojiIndicatorView) rootView.findViewById(R.id.ll_point_group);
//获取map的类型
emotion_map_type=args.getInt(FragmentFactory.EMOTION_MAP_TYPE);
initEmotion();
* 初始化监听器
protected void initListener(){
vp_complate_emotion_layout.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int oldPagerPos=0;
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
public void onPageSelected(int position) {
ll_point_group.playByStartPointToNext(oldPagerPos,position);
oldPagerPos=
public void onPageScrollStateChanged(int state) {
* 初始化表情面板
* 思路:获取表情的总数,按每行存放7个表情,动态计算出每个表情所占的宽度大小(包含间距),
而每个表情的高与宽应该是相等的,这里我们约定只存放3行
每个面板最多存放7*3=21个表情,再减去一个删除键,即每个面板包含20个表情
根据表情总数,循环创建多个容量为20的List,存放表情,对于大小不满20进行特殊
处理即可。
private void initEmotion() {
// 获取屏幕宽度
int screenWidth = DisplayUtils.getScreenWidthPixels(getActivity());
// item的间距
int spacing = DisplayUtils.dp2px(getActivity(), 12);
// 动态计算item的宽度和高度
int itemWidth = (screenWidth - spacing * 8) / 7;
//动态计算gridview的总高度
int gvHeight = itemWidth * 3 + spacing * 6;
List&GridView& emotionViews = new ArrayList&&();
List&String& emotionNames = new ArrayList&&();
// 遍历所有的表情的key
for (String emojiName : EmotionUtils.getEmojiMap(emotion_map_type).keySet()) {
emotionNames.add(emojiName);
// 每20个表情作为一组,同时添加到ViewPager对应的view集合中
if (emotionNames.size() == 20) {
GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight);
emotionViews.add(gv);
// 添加完一组表情,重新创建一个表情名字集合
emotionNames = new ArrayList&&();
// 判断最后是否有不足20个表情的剩余情况
if (emotionNames.size() & 0) {
GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight);
emotionViews.add(gv);
//初始化指示器
ll_point_group.initIndicator(emotionViews.size());
// 将多个GridView添加显示到ViewPager中
emotionPagerGvAdapter = new EmotionPagerAdapter(emotionViews);
vp_complate_emotion_layout.setAdapter(emotionPagerGvAdapter);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(screenWidth, gvHeight);
vp_complate_emotion_layout.setLayoutParams(params);
* 创建显示表情的GridView
private GridView createEmotionGridView(List&String& emotionNames, int gvWidth, int padding, int itemWidth, int gvHeight) {
// 创建GridView
GridView gv = new GridView(getActivity());
//设置点击背景透明
gv.setSelector(android.R.color.transparent);
gv.setNumColumns(7);
gv.setPadding(padding, padding, padding, padding);
gv.setHorizontalSpacing(padding);
gv.setVerticalSpacing(padding * 2);
//设置GridView的宽高
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(gvWidth, gvHeight);
gv.setLayoutParams(params);
// 给GridView设置表情图片
EmotionGridViewAdapter adapter = new EmotionGridViewAdapter(getActivity(), emotionNames, itemWidth,emotion_map_type);
gv.setAdapter(adapter);
//设置全局点击事件
gv.setOnItemClickListener(GlobalOnItemClickManagerUtils.getInstance(getActivity()).getOnItemClickListener(emotion_map_type));
注释非常清晰哈。我就不啰嗦了。
但这有个要注意的是在for循环时是通过EmotionUtils的getEmojiMap(emotion_map_type).keySet()获取集合,这也印证前面我们所说的EmotiomComplateFragment内部是通过集合标志判断集合类型,最终获取到所需的集合数据,也就生成了不同表情类型的面板。
3.4&表情的输入框插入和删除
思路:在表情框输入一个表情实际上是在当前光标位置插入一个表情,添加完表情后再把当前光标移动到表情之后,所以我们首先要获取到光标到首位置,这个可以利用EditText.setSelectionStart()方法,添加完表情后要设置光标的位置到表情之后,这个可以使用EditText.setSelection(position)方法。当然如果点击的是删除按钮,那么直接调用系统的 Delete 按钮事件即可。下面直接上代码:
// 点击的是表情
EmotionGridViewAdapter emotionGvAdapter = (EmotionGridViewAdapter) itemA
if (position == emotionGvAdapter.getCount() - 1) {
// 如果点击了最后一个回退按钮,则调用删除键事件
mEditText.dispatchKeyEvent(new KeyEvent(
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
// 如果点击了表情,则添加到输入框中
String emotionName = emotionGvAdapter.getItem(position);
// 获取当前光标位置,在指定位置上添加表情图片文本
int curPosition = mEditText.getSelectionStart();
StringBuilder sb = new StringBuilder(mEditText.getText().toString());
sb.insert(curPosition, emotionName);
// 特殊文字处理,将表情等转换一下
mEditText.setText(SpanStringUtils.getEmotionContent(emotion_map_type,
mContext, mEditText, sb.toString()));
// 将光标设置到新增完表情的右侧
mEditText.setSelection(curPosition + emotionName.length());
这里要理解一点就是让控件调用系统事件的方法为EditText.displatchKeyEvent(new KeyEvent(action, code));其中action就是动作,用ACTION_DOWN按下动作就可以了而
code为按钮事件码,删除对应的就是KEYCODE_DEL。
4.表情点击事件全局监听的实现
上面弄明白了表情的输入与删除操作后,我们就要考虑一个问题了,那就是在哪里设置监听?直接在创建GridView时,这个确实行得通,不过我们还要再考虑一个问题,那就是如果我们存在多个GridView呢?怪我咯,多复制几遍咯。我们是高级工程师对吧,这样重复代码显然是不可出现在我们眼前的,因此这里我们决定使用全局监听来设置点击事件,当然这个并非我想到的,这个是在github开源项目我在阅读源码时,发现的,这种方式挺不错,我就拿来用咯。直接上代码:
package com.zejian.emotionkeyboard.
import android.content.C
import android.view.KeyE
import android.view.V
import android.widget.AdapterV
import android.widget.EditT
import com.zejian.emotionkeyboard.adapter.EmotionGridViewA
* Created by zejian
16/1/8 下午5:05
* Description:点击表情的全局监听管理类
public class GlobalOnItemClickManagerUtils {
private static GlobalOnItemClickManagerU
private EditText mEditT//输入框
private static Context mC
public static GlobalOnItemClickManagerUtils getInstance(Context context) {
if (instance == null) {
synchronized (GlobalOnItemClickManagerUtils.class) {
if(instance == null) {
instance = new GlobalOnItemClickManagerUtils();
public void attachToEditText(EditText editText) {
mEditText = editT
public AdapterView.OnItemClickListener getOnItemClickListener(final int emotion_map_type) {
return new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView&?& parent, View view, int position, long id) {
Object itemAdapter = parent.getAdapter();
if (itemAdapter instanceof EmotionGridViewAdapter) {
// 点击的是表情
EmotionGridViewAdapter emotionGvAdapter = (EmotionGridViewAdapter) itemA
if (position == emotionGvAdapter.getCount() - 1) {
// 如果点击了最后一个回退按钮,则调用删除键事件
mEditText.dispatchKeyEvent(new KeyEvent(
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
// 如果点击了表情,则添加到输入框中
String emotionName = emotionGvAdapter.getItem(position);
// 获取当前光标位置,在指定位置上添加表情图片文本
int curPosition = mEditText.getSelectionStart();
StringBuilder sb = new StringBuilder(mEditText.getText().toString());
sb.insert(curPosition, emotionName);
// 特殊文字处理,将表情等转换一下
mEditText.setText(SpanStringUtils.getEmotionContent(emotion_map_type,
mContext, mEditText, sb.toString()));
// 将光标设置到新增完表情的右侧
mEditText.setSelection(curPosition + emotionName.length());
代码相当简单,就是创建一个AdapterView.OnItemClickListener的全局监听器,然后在里面实现表情的输入与删除操作即可。那么怎么使用呢?
我们在EmotionMainFragment类中使用创建GlobalOnItemClickManagerUtils,并绑定编辑框,部分代码如下:
//创建全局监听
GlobalOnItemClickManagerUtils globalOnItemClickManager= GlobalOnItemClickManagerUtils.getInstance(getActivity());
if(isBindToBarEditText){
//绑定当前Bar的编辑框
globalOnItemClickManager.attachToEditText(bar_edit_text);
// false,则表示绑定contentView, 此时外部提供的contentView必定也是EditText
globalOnItemClickManager.attachToEditText((EditText) contentView);
mEmotionKeyboard.bindToEditText((EditText)contentView);
绑定的编辑框可能有两种情况,可能是Bar上的编辑框,但也可能是contentView,此时外部提供的contentView是EditText(可以直接理解为是把之前所说的listview替换成了edittext)。最后别忘记在EmotiomComplateFragment类种创建GridView时注册该监听器,
//设置全局点击事件
gv.setOnItemClickListener(GlobalOnItemClickManagerUtils.getInstance(getActivity()).getOnItemClickListener(emotion_map_type));
好了,到此本篇也完结了,下面给出源码下载方式:
主要参考:
github开源项目:/dss886/Android-EmotionInputDetector
文章:/thread--1.html?_dsign=cbb4daa9
本文已收录于以下专栏:
相关文章推荐
android 键盘表情流畅切换大家都用微信,在聊天页面,软键盘与表情栏,工具栏之间的切换非常流畅,没有引起输入bar的上下抖动,体验非常好。这里讲一讲它的实现原理。
最近公司在项目上要使用到表情与键盘的切换输入,自己实现了一个,还是存在些缺陷,比如说键盘与表情切换时出现跳闪问题,这个相当困扰我,不过所幸在Github(其中一个不错的开源项目,其代码整体结构很不错)...
网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。
Timer与TimerTask(Java实现)
public class timerTask extends Activity{
private int recLen = 11...
在购物网站的促销活动中一般都有倒计时限制购物时间或者折扣的时间,这些都是如何实现的呢?
在一个安卓客户端项目中恰好遇到了类似的问题,一开始使用的是Timer与 TimerTask, 虽然此方法通用,...
转载请注明出处:
http://blog.csdn.net/javazejian/article/details/
最近公司在项目上要使用到表情与键盘的切换输入,自己实现了一个,还...
网上有很多开源的项目, 但通常都封装的很复杂, 并不能很友好的自定义, 甚至原理都看不清楚.
没关系, 这篇文章就是讲原理的. 让你分分钟都能自定义一个出来.
正常情况下, 当我们切换表情和键...
啥也不多说,先上图
他的最新文章
讲师:王渊命
讲师:蔡栋
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)}

我要回帖

更多关于 键盘切换不了输入法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信