Android软键盘切换

前言

现在很多应用都会有评论功能,方便用户随时反馈使用效果,为了避免文本评论的单调问题,往往都会支持一些自定义的表情图片输入软键盘。但是这些表情图片键盘往往不需要像真正的中文输入法那样作为单独的应用安装,这时就需要客户端针对Android软件盘做适配处理,方便用户切换输入法。

实现效果

切换输入法布局

实现接口

用户评论开发过程中通常需要和EditText控件打交道,编辑框通常编程过程中最常见的就是和软键盘做交互操作。在用户最初进入编辑评论时有时希望直接展示软键盘,减少用户选中输入框操作,最好是保证输入框不被软件盘覆盖住。android:windowSoftInputMode属性就包含了实现这些交互方式的属性,主要分成两个部分,一个是控制软件盘展示或隐藏的state开头属性值,一个是控制展示输入框的Window适配效果的adjust开头属性值,通常都是两个使用或(|)指定键盘盘和Windwo适配效果,最常见的Adjust属性值解释如下:

属性值意义
adjustPan如果Window内容布局可以滚动,尽量将输入框展示在软件盘之上,不能保证软件盘没有遮盖其他内容布局
adjustResizeWindow内容布局整体压缩,展示软件盘的地方不会展示内容布局
adjustNothingWindow布局不做任何调整
adjustUnspecified根据情况做adjustPan或者adjustResize

控制软件盘展示交互的state属性如下:

属性值意义
stateVisible初次进入界面展示软键盘
stateAlwaysVisible不管是初次进入还是导航到别的Activity重新回来,都展示软件盘
stateHide初次键入不展示软件盘
stateAlwaysHide不论初次还是从别的Activity导航回来都不展示软件盘
stateUnchanged软键盘保持在前一个Activity里的状态不论展开还是关闭
stateUnspecified未指定软键盘状态

考虑到要实现软件盘的切换,但是自定义的表情布局是需要展示在用户的界面上,但是要覆盖在系统软件盘界面之上,最重要的一点就是确定软件盘的高度和展示关闭状态。但是Android的内部实现并没有提供软件盘打开关闭的回调功能,这时adjustResize就起到了关键性的作用,可以在adjustResize模式下通过监控Window内容布局整体的高度变化来确定软件盘高度和开闭状态。

考虑到adjustResize会导致整个Window界面的压缩,所以切换的时候难免会引起界面跳动,如果避免这个情况需要了解系统提供的隐藏软键盘的接口,主要有下面两种:

public static void hideSoftInput(EditText editText) {
    InputMethodManager inputManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    inputManager.hideSoftInputFromInputMethod(editText.getWindowToken(), 0);
}

public static void hideSoftInputFromWindow(EditText editText) {
    InputMethodManager inputManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    inputManager.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}

hideSoftInputFromInputMethod调用之后会发现软键盘并没有被关闭,点击输入也没有问题;hideSoftInputFromWindow调用后真的把软键盘关闭了,而且整个Window界面也随之变成了全屏状态。二者的区别在哪里呢,如果你定义一个PopupWindow,在第一种方式关闭的Window上展示在DecorView底部,会发现PopupWindow展示在软件盘的位置,但是第二种PopupWindow会展示在软件盘之上,也就是说如果希望切换的自定义表情键盘展示在软件位置,需要用第一种隐藏方式,尽管看起来实际上并没有被隐藏,但是自定义表情布局会遮盖住它。

实现过程

首先需要实现对软件盘的展示关闭监听操作,这里使用自定以控件来实现。自定义控件需要和Window的根部局同样大小,同时在onSizeChanged方法中监听控件大小的改变,只有高度改变绝对值超过1/5屏幕宽高的时候才认定软件盘状态发生了改变,XML中布局如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    // 监控软键盘展示关闭的自定义布局
    <com.example.comment.KeyBoardLayout
        android:id="@+id/key_board_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    // 其他内容布局
    ....
</FrameLayout>

自定义布局在onSizeChangde中监听大小变化,同时使用接口通知Activity软件盘当前状态:

public class KeyBoardLayout extends View {
    private static final String TAG = "KeyBoardLayout";
    private KeyBoardListener keyBoardListener;

    // 软件盘监听回调接口
    public interface KeyBoardListener {
        // 当前软件盘是否展示,active为true表示展示,keyBoardHeight表明软件盘高度
        void onKeyBoardEvent(boolean active, int keyBoardHeight);
    }

    public KeyBoardLayout(Context context) {
        super(context);
    }

    public KeyBoardLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyBoardLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.d(TAG, "onSizeChanged(): w = " + w + ", h = " + h + ", oldw = " + oldw + ", oldh = " + oldh);
        super.onSizeChanged(w, h, oldw, oldh);
        int threshold = CommonUtils.getScreenHeight() / 5;
        // 如果旧高度和新高度都是有效值
        if (oldh > 0 && h > 0) {
            // 旧高度比新高度要大,表明键盘弹起
            int keyboardHeight = oldh - h;
            if (keyboardHeight > threshold) {
                notifyKeyBoardShow(keyboardHeight);
            } else if (keyboardHeight < 0) {
                notifyKeyBoardHide(-keyboardHeight);
            }
        }
    }

    private void notifyKeyBoardHide(int keyboardHeight) {
        if (keyBoardListener != null) {
            keyBoardListener.onKeyBoardEvent(false, keyboardHeight);
        }
    }

    private void notifyKeyBoardShow(int keyboardHeight) {
        if (keyBoardListener != null) {
            keyBoardListener.onKeyBoardEvent(true, keyboardHeight);
        }
    }

    public void setOnKeyBoardShow(KeyBoardListener keyBoardListener) {
        this.keyBoardListener = keyBoardListener;
    }

第二步在HintPopupWindow里自定义表情布局,内部使用PopupWindow做展示操作,在软件盘之上有一个控制布局,控制布局下面才是自定义的表情布局,这里只使用一副图片来代替自定义表情布局。

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <!--软件盘上方的控制布局,负责展示软件盘或者隐藏软件盘 -->
    <LinearLayout
        android:background="@android:color/white"
        android:orientation="horizontal"
        android:paddingLeft="10dp"
        android:layout_width="match_parent"
        android:layout_height="@dimen/comment_hint_height">

        <TextView
            android:layout_width="0dp"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="@string/comment_hint"
            android:layout_height="match_parent" />

        <!--负责切换软件盘和自定义表情布局的按钮-->
        <Button
            android:id="@+id/comment_hint_btn"
            android:text="@string/comment_hide_keyboard"
            android:layout_width="wrap_content"
            android:layout_height="@dimen/comment_hint_height" />

    </LinearLayout>


    <!--实际展示的表情布局-->
    <ImageView
        android:id="@+id/comment_custom_pic"
        android:src="@drawable/cute_cat"
        android:scaleType="centerCrop"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

在负责切换自定义表情和软键盘的按钮回调事件里,更新自定义布局的宽高:

@Override
public void onClick(View v) {
    // 如果自定义布局高度等于控制部分的高度,也就是当前展示的是软件盘
    if (popupWindow.getHeight() == originHeight) {
        if (keyboardHeight > 0) {
            button.setText(R.string.comment_open_keyboard);
            imageView.getLayoutParams().height = keyboardHeight;
            popupWindow.setHeight(v.getResources().getDimensionPixelSize(R.dimen.comment_hint_height)
                    + imageView.getLayoutParams().height);
            // 设置自定义表情布局可见,并且更新PopupWindow的高度
            popupWindow.update(CommonUtils.getScreenWidth(), popupWindow.getHeight());
            // 隐藏软件盘
            KeyboardUtils.hideSoftInput(editText);
        }
    } else {
        // 设置软件盘高度为控制部分的高度,并且打开软件盘
        button.setText(R.string.comment_hide_keyboard);
        popupWindow.setHeight(originHeight);
        popupWindow.update(CommonUtils.getScreenWidth(), popupWindow.getHeight());
        KeyboardUtils.showSoftInput(editText);
    }
}

最后需要在Activity里设置软件盘的监听事件,并且根据软件盘展示和消失控制自定义表情布局的展示和消失,确保二者不会出现展示冲突。

@Override
public void onKeyBoardEvent(boolean active, int keyBoardHeight) {
    isSoftActive = active;
    if (active) {
        onKeyBoardShow(keyBoardHeight);
    } else {
        onKeyBoardHide(keyBoardHeight);
    }
}

public void onKeyBoardShow(final int keyBoardHeight) {
    Log.d(TAG, "onKeyBoardShow(): keyBoardHeight = " + keyBoardHeight);
    final int hintTop = CommonUtils.getScreenHeight() - keyBoardHeight - hintPopupWindow.getHeight();
    hintPopupWindow.setKeyboardHeight(keyBoardHeight);
    hintPopupWindow.show(getWindow().getDecorView(), 0, hintTop);
}

private void onKeyBoardHide(int keyBoardHeight) {
    getWindow().getDecorView().postDelayed(new Runnable() {
        @Override
        public void run() {
            hintPopupWindow.hide();
        }
    }, 100);
}

以上就是实现的主要代码逻辑,详细的内部实现请查看软件盘切换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值