Android 表情面板与键盘切换闪烁解决方法

利用一个名为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实例
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇