利用一个名为EmotionKeyboard的工具类,在Android应用中解决表情面板与软键盘切换闪烁的问题。该工具类基于输入框的焦点状态和触摸事件,实现了在输入框的上方显示表情面板,同时在需要时隐藏软键盘的功能。通过该工具类,用户可以方便地在自己的应用中集成表情面板与软键盘的切换功能,从而提升用户的交互体验。
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/chat_activity_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true">
<LinearLayout
android:id="@+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 顶部栏 -->
<LinearLayout
android:id="@+id/top_bar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/FAFAFA"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/go_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:src="@drawable/go_back_icon" />
<TextView
android:id="@+id/user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="用户名"
android:textColor="@android:color/black"
android:textSize="18sp" />
<ImageView
android:id="@+id/menu_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:src="@drawable/more_menu_icon" />
</LinearLayout>
<!-- 聊天内容布局 -->
<FrameLayout
android:id="@+id/chat_frame"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp">
<!-- 在这里放置你的聊天内容 -->
</FrameLayout>
<!-- 底部菜单栏 -->
<LinearLayout
android:id="@+id/bottom_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/FAFAFA"
android:minHeight="55dp"
android:orientation="horizontal"
android:paddingTop="10dp">
<!-- 语音 -->
<ImageView
android:id="@+id/bt_voice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
android:src="@drawable/voice_icon" />
<!-- 输入框 -->
<EditText
android:id="@+id/chat_box"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:background="@drawable/input_box"
android:ems="10"
android:inputType="text|textMultiLine"
android:maxLines="5"
android:paddingLeft="16dp"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingBottom="8dp"
android:text="" />
<!-- 表情 -->
<ImageView
android:id="@+id/bt_emoji"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginEnd="5dp"
android:layout_marginBottom="10dp"
android:src="@drawable/emoji_icon" />
<!-- 菜单 -->
<ImageView
android:id="@+id/bt_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginEnd="5dp"
android:layout_marginBottom="10dp"
android:src="@drawable/menu_add_icon"
android:visibility="visible" />
<!-- 菜单 -->
<!--发送-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:orientation="horizontal">
<android.widget.Button
android:id="@+id/send"
android:layout_width="65dp"
android:layout_height="35dp"
android:layout_gravity="center"
android:background="@drawable/button_background"
android:gravity="center"
android:text="@string/send"
android:textColor="@color/white"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/menu_paper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/emoji_paper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
- 实现表情面板与软键盘之间切换的EmotionKeyboard类
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
/**
* EmotionKeyboard是一个管理表情键盘的工具类
* 源码来自开源项目https://github.com/dss886/Android-EmotionInputDetector
* 本人仅做细微修改以及代码解析
*/
public class EmotionKeyboard {
private static final String SHARE_PREFERENCE_NAME = "EmotionKeyboard"; // 存储表情键盘相关信息的SharedPreferences名称
private static final String SHARE_PREFERENCE_SOFT_INPUT_HEIGHT = "soft_input_height"; // 软键盘高度在SharedPreferences中的key
private Activity mActivity; // 当前Activity
private InputMethodManager mInputManager; // 软键盘管理类
private SharedPreferences sp; // SharedPreferences对象,用于存储软键盘高度等信息
private View mEmotionLayout,mMenuLayout; // 表情布局,菜单布局
private View emotionButton,menuButton; // 表表情按钮,菜单按钮
private EditText mEditText; // 编辑框
private View mContentView; // 内容布局,用于固定bar的高度,防止跳闪
// 构造函数私有化,只能通过with方法获取实例
private EmotionKeyboard() {
}
/**
* 外部静态调用,用于获取EmotionKeyboard实例
*
* @param activity 当前Activity
* @return EmotionKeyboard实例
*/
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 内容view
* @return 当前EmotionKeyboard实例
*/
public EmotionKeyboard bindToContent(View contentView) {
mContentView = contentView;
return this;
}
/**
* 绑定编辑框
*
* @param editText 编辑框
* @return 当前EmotionKeyboard实例
*/
public EmotionKeyboard bindToEditText(EditText editText) {
mEditText = editText;
mEditText.requestFocus();
mEditText.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP && mEmotionLayout.isShown()) {
lockContentHeight(); // 显示软键盘时,锁定内容高度,防止跳闪。
hideEmotionLayout(true); // 隐藏表情布局,显示软键盘
// 软键盘显示后,释放内容高度
mEditText.postDelayed(() -> unlockContentHeightDelayed(), 200L);
}
if (event.getAction() == MotionEvent.ACTION_UP && mMenuLayout.isShown()) {
menuButton.animate().rotationBy(-45f).setDuration(250).start();
lockContentHeight(); // 显示软键盘时,锁定内容高度,防止跳闪。
hideMenuLayout(true); // 隐藏菜单布局,显示软键盘
// 软键盘显示后,释放内容高度
mEditText.postDelayed(() -> unlockContentHeightDelayed(), 200L);
}
return false;
});
return this;
}
/**
* 绑定表情按钮
*
* @return 当前EmotionKeyboard实例
*/
public EmotionKeyboard bindToEmojiButton() {
emotionButton.setOnClickListener(v -> {
//如果点击表情按钮时正在显示菜单布局
if (mMenuLayout.isShown()){
menuButton.animate().rotationBy(-45f).setDuration(250).start();
lockContentHeight(); // 显示软键盘时,锁定内容高度,防止跳闪。
hideMenuLayout(false); // 隐藏表情布局,但不显示软键盘
showEmotionLayout(); // 显示表情布局
unlockContentHeightDelayed(); // 表情布局显示后,释放内容高度
}
//如果已经显示表情布局,再次点击则切换键盘
else if (mEmotionLayout.isShown()) {
lockContentHeight(); // 显示软键盘时,锁定内容高度,防止跳闪。
hideEmotionLayout(true); // 隐藏表情布局,显示软键盘
unlockContentHeightDelayed(); // 软键盘显示后,释放内容高度
} else {
//如果正在显示键盘,则隐藏键盘显示表情布局
if (isSoftInputShown()) { // 同上
lockContentHeight();
showEmotionLayout();
unlockContentHeightDelayed();
} else {
//如果都没有显示,则直接显示表情布局
showEmotionLayout(); // 两者都没显示,直接显示表情布局
}
}
});
return this;
}
/**
* 绑定菜单按钮
*
* @return 当前EmotionKeyboard实例
*/
public EmotionKeyboard bindToMenuButton() {
menuButton.setOnClickListener(v -> {
//如果点击菜单按钮时正在显示表情布局
if (mEmotionLayout.isShown()){
//旋转45°延迟250毫秒
menuButton.animate().rotationBy(45f).setDuration(250).start();
lockContentHeight(); // 显示表情面板时,锁定内容高度,防止跳闪。
hideEmotionLayout(false); // 隐藏表情布局,但不显示软键盘
showMenuLayout(); //显示菜单布局
unlockContentHeightDelayed(); // 菜单布局显示后,释放内容高度
}
//如果已经显示菜单布局,再次点击则切换键盘
else if (mMenuLayout.isShown()) {
menuButton.animate().rotationBy(-45f).setDuration(250).start();
lockContentHeight(); // 显示软键盘时,锁定内容高度,防止跳闪。
hideMenuLayout(true); // 隐藏菜单布局,显示软键盘
unlockContentHeightDelayed(); // 软键盘显示后,释放内容高度
} else {
//如果正在显示键盘
if (isSoftInputShown()) { // 同上
menuButton.animate().rotationBy(45f).setDuration(250).start();
lockContentHeight();
showMenuLayout();
unlockContentHeightDelayed();
} else {
menuButton.animate().rotationBy(45f).setDuration(250).start();
//如果没有显示键盘也没有显示表情
showMenuLayout(); // 两者都没显示,直接显示菜单布局
}
}
});
return this;
}
/**
* 设置按钮键
*
* @param emotionView 表情内容布局
* @return 当前EmotionKeyboard实例
*/
public EmotionKeyboard setButtonView(View emotionView,View menuView) {
emotionButton = emotionView;
menuButton = menuView;
return this;
}
/**
* 设置表情内容布局
*
* @param emotionView 表情内容布局
* @return 当前EmotionKeyboard实例
*/
public EmotionKeyboard setEmotionView(View emotionView) {
mEmotionLayout = emotionView;
return this;
}
/**
* 设置菜单内容布局
*
* @param menuView 菜单内容布局
* @return 当前EmotionKeyboard实例
*/
public EmotionKeyboard setMenuView(View menuView) {
mMenuLayout = menuView;
return this;
}
/**
* 构建EmotionKeyboard实例
*
* @return 当前EmotionKeyboard实例
*/
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();
return this;
}
/**
* 点击返回键时先隐藏表情/菜单布局
*
* @return 是否拦截返回事件
*/
public boolean interceptBackPress() {
if (mEmotionLayout.isShown()) {
hideEmotionLayout(false);
return true;
} else if (mMenuLayout.isShown()) {
hideMenuLayout(false);
return true;
}
return false;
}
/**
* 显示表情布局
*/
private void showEmotionLayout() {
int softInputHeight = getSupportSoftInputHeight();
// 如果获取软键盘高度失败,则使用默认值 400
if (softInputHeight == 0) {
softInputHeight = sp.getInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, 765);
}
LogUtils.d("表情布局高度:"+softInputHeight);
// 隐藏软键盘
hideSoftInput();
// 将软键盘高度设置给表情布局
mEmotionLayout.getLayoutParams().height = softInputHeight;
LogUtils.d("将软键盘高度设置给表情布局"+String.valueOf(softInputHeight));
mEmotionLayout.setVisibility(View.VISIBLE);
}
/**
* 显示菜单布局
*/
private void showMenuLayout() {
int softInputHeight = getSupportSoftInputHeight();
// 如果获取软键盘高度失败,则使用默认值 400
if (softInputHeight == 0) {
softInputHeight = sp.getInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, 765);
}
LogUtils.d("菜单布局高度:"+softInputHeight);
// 隐藏软键盘
hideSoftInput();
// 将软键盘高度设置给表情布局
mMenuLayout.getLayoutParams().height = softInputHeight;
LogUtils.d("将软键盘高度设置给表情布局"+String.valueOf(softInputHeight));
mMenuLayout.setVisibility(View.VISIBLE);
}
/**
* 隐藏表情布局
*
* @param showSoftInput 是否显示软键盘
*/
private void hideEmotionLayout(boolean showSoftInput) {
if (mEmotionLayout.isShown()) {
mEmotionLayout.setVisibility(View.GONE);
if (showSoftInput) {
showSoftInput();
}
}
}
/**
* 隐藏菜单布局
*
* @param showSoftInput 是否显示软键盘
*/
private void hideMenuLayout(boolean showSoftInput) {
if (mMenuLayout.isShown()) {
mMenuLayout.setVisibility(View.GONE);
if (showSoftInput) {
showSoftInput();
}
}
}
/**
* 锁定内容高度,防止跳闪
*/
private void lockContentHeight() {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams();
params.height = mContentView.getHeight();
LogUtils.d("内容高度"+ params.height);
params.weight = 0.0F;
}
/**
* 释放被锁定的内容高度
*/
private void unlockContentHeightDelayed() {
mEditText.postDelayed(new Runnable() {
@Override
public void run() {
((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;
}
}, 200L);
}
/**
* 编辑框获取焦点,并显示软键盘
*/
private void showSoftInput() {
mEditText.requestFocus();
mEditText.post(new Runnable() {
@Override
public void run() {
mInputManager.showSoftInput(mEditText, 0);
}
});
}
/**
* 隐藏软键盘
*/
private void hideSoftInput() {
mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}
/**
* 是否显示软键盘
*
* @return 是否显示软键盘
*/
private boolean isSoftInputShown() {
return getSupportSoftInputHeight() != 0;
}
/**
* 获取软键盘的高度
*
* @return 软键盘高度
*/
private int getSupportSoftInputHeight() {
Rect r = new Rect();
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight();
int softInputHeight = screenHeight - r.bottom;
if (isNavigationBarExist()){
softInputHeight = softInputHeight - getSoftButtonsBarHeight();
}
LogUtils.d("软键盘高度"+ (softInputHeight));
if (softInputHeight <= 0) {
LogUtils.w("EmotionKeyboard--Warning: value of softInputHeight is below zero!");
}
if (softInputHeight > 200) {
sp.edit().putInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, softInputHeight).apply();
}
return softInputHeight;
}
/**
* 获取底部虚拟按键栏的高度
*
* @return 虚拟按键栏高度
*/
private int getSoftButtonsBarHeight() {
DisplayMetrics metrics = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
int usableHeight = metrics.heightPixels;
mActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
int realHeight = metrics.heightPixels;
LogUtils.d("虚拟按键栏高度"+ (realHeight - usableHeight));
if (realHeight > usableHeight) {
return realHeight - usableHeight;
} else {
return 0;
}
}
/**
* 获取软键盘高度
*
* @return 软键盘高度
*/
public int getKeyBoardHeight() {
return sp.getInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, 850);
}
private static final String NAVIGATION= "navigationBarBackground";
/**
* 检查设备是否具有导航栏(虚拟或物理)。
*
* @return 如果存在导航栏则返回 true,否则返回 false
*/
public boolean isNavigationBarExist() {
// 获取窗口装饰视图的根视图
ViewGroup vp = (ViewGroup) mActivity.getWindow().getDecorView();
// 遍历装饰视图的所有子视图
if (vp != null) {
for (int i = 0; i < vp.getChildCount(); i++) {
// 获取子视图的上下文
vp.getChildAt(i).getContext().getPackageName();
// 检查子视图是否具有导航栏
if (vp.getChildAt(i).getId() != View.NO_ID && NAVIGATION.equals(mActivity.getResources().getResourceEntryName(vp.getChildAt(i).getId()))) {
return true;
}
}
}
return false;
}
}
// 使用EmotionKeyboard.with()方法获取EmotionKeyboard的实例,并进行绑定操作
EmotionKeyboard.with(this)
.bindToEditText(chatBox) // 绑定编辑框
.setButtonView(btEmoji,btMenu)//设置按钮
.bindToEmojiButton() // 绑定表情按钮
.setEmotionView(emojiPaper) // 设置表情布局
.bindToMenuButton() // 绑定菜单按钮
.setMenuView(menuPaper) // 设置菜单布局
.bindToContent(chatFrame) // 绑定内容布局
.build(); // 构建EmotionKeyboard实例