表情与键盘的切换输入大部分IM都会需要到,之前自己实现了一个,还是存在些缺陷,比如说键盘与表情切换时出现跳闪问题,这个困扰了我些时间,最后终于苦逼地整合出比较不错的实现效果(这里不仅给出了实现方案,还提供一个可拓展的fragment模板以便大家实现自己的表情包)代码我已进行另外的封装与拓展,大家需要其他表情的话只需要根据fragment模板实现自己的表情界面,然后根据工厂类获取即可,EmotionKeyboard.java, (表情操作核心类)
EmotionComplateFragment.java(表情fragment模板)
FragmentFactory.java,(生产表情fragment模板工厂类)
EmotionUtils.java(表情字符转换工具)
GlobalOnItemClickManagerUtils.java(全局监听类)
1.解决表情与键盘切换跳闪问题
1.1跳闪问题概述
为了让大家对这个问题有一定了解,我先来个简单案例,用红色面板代表表情面板,当表情显示时,我们点击表情按钮,隐藏表情显示软件盘时,内容Bar有一个明显的先向下后恢复的跳闪现象,这样用户体验相当的差,无论怎么切换都不会有跳闪现象,这就是我所有说的键盘与表情切换的跳闪问题。到这里,我们对这个问题有了大概了解后。这里我们做个约定,我们把含有表情那个bar统称为内容Bar。
1.2 解决跳闪问题的思路:
android系统在弹出软键盘时,会把我们的内容 Bar 顶上去,因此只有表情面板的高度与软键盘弹出时高度一致时,才有可能然切换时高度过渡更自然,所以我们必须计算出软键盘的高度并设置给表情面板。仅仅有这一步跳闪问题还是依旧存在,因此这时我们必须想其他办法固定内容Bar,因为所有的跳闪都是表情面板隐藏,而软键盘往上托出瞬间,Activity高度变高(为什么会变高后面会说明),内容Bar往下滑后,又被软键盘顶回原来位置造成的。因此只要固定了内容Bar的位置,闪跳问题就迎刃而解了。那么如何固定内容Bar的位置呢?我们知道在一个布局中一个控件的位置其实是由它上面所有控件的高度决定的,如果其上面其他控件的高度不变,那么当前控件的高度自然也不会变化,即使到时Activity的高度发生了变化也也不会影响该控件的位置(整个界面的显示是挂载在window窗体上的,而非Activity,不了解的可以先研究一下窗体的创建过程),因此我们只要在软键盘弹出前固定内容Bar上面所有控件高度,从而达到固定内容Bar位置(高度)的目的。好了,有思路了,我们接下来一步步按上面思路解决问题。
1.3 解决跳闪问题的套路:1.3.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.bottom;
这里我们队对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.bottom1.3.2 固定内容Bar的高度,解决闪跳问题
软键盘高度解决后,现在剩下的问题关键就在于控制内容Bar的高度了,那么如何做呢?我们先来看一个布局文件<?xml version="1.0" encoding="utf-8"?>
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp"
/>
android:layout_width="match_parent"
android:layout_height="wrap_content" />
其中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;
}12345678
将weight置0,然后将height设置为当前的height,在父控件(LinearLayout)的高度变化时它的高度也不再会变化。释放ListView的高度:private void unlockContentHeightDelayed() {
mEditText.postDelayed(new Runnable() { @Override
public void run() {
((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;
}
}, 200L);
}
其中的LinearLayout.LayoutParams.weight = 1.0F;,在代码里动态更改LayoutParam的weight,会导致父控件重新onLayout(),也就达到改变控件的高度的目的。到此两个主要问题都解决了,我们直接上核心类代码,该类来自github上的开源项目我在使用中直接从该项目中抽取了该类, 并做了细微修改,也添加了代码注释。package com.zejian.emotionkeyboard.emotionkeyboardview;import android.annotation.TargetApi;import android.app.Activity;import android.content.Context;import android.content.SharedPreferences;import android.graphics.Rect;import android.os.Build;import android.util.DisplayMetrics;import android.view.MotionEvent;import android.view.View;import android.view.WindowManager;import android.view.inputmethod.InputMethodManager;import android.widget.EditText;import android.widget.LinearLayout;import com.zejian.emotionkeyboard.utils.LogUtils;/**
* author : zejian
* time : 2016年1月5日 上午11:14:27
* email : shinezejian@163.com
* description :源码来自开源项目https://github.com/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 mActivity; private InputMethodManager mInputManager;//软键盘管理类
private SharedPreferences sp; private View mEmotionLayout;//表情布局
private EditText mEditText;//
private View mContentView;//内容布局view,即除了表情布局或者软键盘布局以外的布局,用于固定bar的高度,防止跳闪
private EmotionKeyboard(){
} /**
* 外部静态调用
* @param activity
* @return
*/
public static EmotionKeyboard with(Activity activity) {
EmotionKeyboard emotionInputDetector = new EmotionKeyboard();
emotionInputDetector.mActivity = activity;
emotionInputDetector.mInputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
emotionInputDetector.sp = activity.getSharedPreferences(SHARE_PREFERENCE_NAME, Context.MODE_PRIVATE); return emotionInputDetector;
} /**
* 绑定内容view,此view用于固定bar的高度,防止跳闪
* @param contentView
* @return
*/
public EmotionKeyboard bindToContent(View contentView) {
mContentView = contentView; return this;
} /**
* 绑定编辑框
* @param editText
* @return
*/
public EmotionKeyboard bindToEditText(EditText editText) {
mEditText = editText;
mEditText.requestFocus();
mEditText.setOnTouchListener(new View.OnTouchListener() { @Override
public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP && mEmotionLayout.isShown()) {
lockContentHeight();//显示软件盘时,锁定内容高度,防止跳闪。
hideEmotionLayout(true);//隐藏表情布局,显示软件盘
//软件盘显示后,释放内容高度
mEditText.postDelayed(new Runnable() { @Override
public void run() {
unlockContentHeightDelayed();
}
}, 200L);
} return false;
}
}); return this;
} /**
* 绑定表情按钮
* @param emotionButton
* @return
*/
public EmotionKeyboard bindToEmotionButton(View emotionButton) {
emotionButton.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(