文章

软键盘

软键盘

EditText 设置 imeOptions 属性对软键盘的影响

将软键盘的 Enter 键更改为其他键,可以设置其 android:imeOptions 属性,这个属性可以控制软键盘的 Enter 键,以及横屏情况下的软键盘显示状态。

该设置必须是下面所列的值之一,或者是一个 “action…” 值加上一个 “flag…” 值的组合,在 action…组中设置多个值(例如,多个 “action…” 值)都会产生未定义结果,而 flag….可以设置多个。各值之间使用垂直条 (|) 分隔。

  1. 需要设置 android:singleLine=true,否则 enter 是回车作用
  2. android:singleLine 在 API3 已经废弃,可以用 android:maxLines="1" 代替,还需要加上 android:inputType="text" 才能生效

控制软键盘上的 Enter 键

  • android:imeOptions=”normal”
    输入框后面还有输入控件的时候会显示 next,没有时会显示 done(完成) image.png

  • android:imeOptions=”actionUnspecified”
    该属性为默认属性,一般情况下为 “normal” 的使用情形。 image.png

  • android:imeOptions=”actionNone”
    没有反应。
  • android:imeOptions=”actionGo”
    显示为 Go(前往) 按钮,需要自己写事件。 image.png

  • android:imeOptions=”actionSearch”
    显示搜索(Search)按钮,需要自己写事件。 image.png

  • android:imeOptions=”actionSend”
    显示 send(发送) 按钮,需要自己写事件。

image.png

  • android:imeOptions=”actionNext”
    显示 next(下一步) 按钮,作用是跳到下一个可输入的控件

image.png

  • android:imeOptions=”actionPrevious”
    显示上一步按钮,如果前面有输入控件,点击后会回到前一个控件获取焦点
  • android:imeOptions=”actionDone”
    显示 done(完成) 按钮,作用编辑完成。

横屏下控制软键盘

  • android:imeOptions=”flagNoFullscreen”
    在横屏下,当设置这个标志时,软键盘在弹出的时候,永远不会变成全屏状态,但是这个属性在 API 中说并不一定所有输入法都支持这个属性。
  • android:imeOptions=”flagNoExtractUi”
    这个属性也有意思,它的表现形式和 flagNoFullscreen 比较像。
    因为在横屏下,这两个属性单独设置都让软键盘半屏显示,但是这两个属性还是有所不同的。

这里可以参阅下 API,flagNoExtractUI 显示的半屏软键盘本身软键盘显示还是全屏的,但是将之前的全屏输入框给隐藏掉,所以给你显示半屏的效果。而且在单独使用的时候,可能你会发现软键盘是先从全屏然后过渡到半屏的。

所以要去掉全屏到半屏过渡效果,在横屏状态下,需要和 flagNoFullscreen 一块使用,来达到更好的体验。

  • android:imeOptions=”flagNavigatePrevious”
    横屏下设置输入法全屏,设置输入框上的按钮为 (previous) 上一个的作用。
  • android:imeOptions=”flagNavigateNext”
    横屏下设置输入法全屏,设置输入框上的按钮为 (Next) 下一个作用。
  • android:imeOptions=”flagNoAccessoryAction”
    横屏下设置输入法全屏,并且使其输入框上的按钮隐藏。
  • android:imeOptions=”flagNoEnterAction”
    横屏下设置输入法全屏,输入框内的按钮为完成 (Done) 状态.编辑完毕,点完成,软键盘消失。

Android 软键盘上的按键监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
mMainEt = (EditText) findViewById(R.id.main_et);
mMainEt.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v int actionId KeyEvent event) {
        switch (actionId) {
            //点击GO键
            case EditorInfo.IME_ACTION_GO:
                return true;
            //点击Done
            case EditorInfo.IME_ACTION_DONE:
                return true;
            //点击Next
            case EditorInfo.IME_ACTION_NEXT:
                return true;
            //点击Previous
            case EditorInfo.IME_ACTION_PREVIOUS:
                return true;
            //点击None
            case EditorInfo.IME_ACTION_NONE:
                return true;
            //点击Send
            case EditorInfo.IME_ACTION_SEND:
                return true;
        }
        return false;
    }
});

Android 软键盘外触摸隐藏软键盘

在 Activity 中 copy 下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
override
fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        var v = getCurrentFocus()
        if (isShouldHideKeyboard(v, ev)) {

            var imm: InputMethodManager = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            imm.hideSoftInputFromWindow(v.getWindowToken(),
                    InputMethodManager.HIDE_NOT_ALWAYS)
        }
    }
    return super.dispatchTouchEvent(ev)
}

// Return whether touch the view.
private fun isShouldHideKeyboard(v: View, event: MotionEvent): Boolean {
    if (v != null && (v is EditText)) {
        var l = intArrayOf(0, 0)
        v.getLocationInWindow(l)
        var left = l[0]
        var top = l[1]
        var bottom = top + v.getHeight()
        var right = left + v.getWidth()
        return !(event.getX() > left && event.getX() < right
                && event.getY() > top && event.getY() < bottom)
    }
    return false
}

Android 监听软键盘显示和高度变化

https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/lib/src/main/java/com/blankj/utilcode/util/KeyboardUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
fun registerSoftInputChangedListener(activity: Activity, onSoftInputChangedListener: OnSoftInputChangedListener) {
    val contentView = activity.findViewById<View>(android.R.id.content)
    val onGlobalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
        if (onSoftInputChangedListener != null) {
            val height = getContentViewInvisibleHeight(activity)
            onSoftInputChangedListener.onSoftInputChanged(height)
        }
    }
    contentView.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
}

interface OnSoftInputChangedListener {
    fun onSoftInputChanged(height: Int)
}

private fun getContentViewInvisibleHeight(activity: Activity): Int {
    val contentView = activity.findViewById<FrameLayout>(android.R.id.content)
    val outRect = Rect()
    contentView.getWindowVisibleDisplayFrame(outRect)
    var statusBarHeight = BarUtils.getStatusBarHeight()

    var height = (statusBarHeight + contentView.bottom) - outRect.bottom

    var s1 = "contentView WindowVisibleDisplayFrame(${contentView}),top:${outRect.top}, bottom:${outRect.bottom}," +
            "left:${outRect.left},right:${outRect.right}\n"
//        tv_result.append(s1)
    LogUtil.i(s1)
    var s2 = "contentView ,top:${contentView.top}, bottom:${contentView.bottom}," +
            "left:${contentView.left},right:${contentView.right}\n"
//        tv_result.append(s2)
    LogUtil.i(s2)
    var s3 = "软键盘高度getContentViewInvisibleHeight ,${height}\n"
//        tv_result.append(s3)
    LogUtil.i(s3)

    return height
}

软键盘显示和隐藏 toggle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public final class SoftInputShowHidden {

    public static void showSoftInput(final Context context) {
        if (!(context instanceof Activity)) {
            return;
        }
        InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        final View currentFocusView = ((Activity) context).getCurrentFocus();
        if (inputMethodManager != null && currentFocusView != null)
            try {
                inputMethodManager.showSoftInput(currentFocusView, 0);
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
    }

    /**
     * Toggle the soft input display or not.
     */
    public static void toggleSoftInput(final Context context) {
        InputMethodManager imm =
                (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm == null) return;
        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
    }

    public static void hideSoftInput(final Context context) {
        if (!(context instanceof Activity)) {
            return;
        }
        InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        final View currentFocusView = ((Activity) context).getCurrentFocus();
        if (currentFocusView != null) {
            final IBinder windowToken = currentFocusView.getWindowToken();
            if (inputMethodManager != null && windowToken != null) {
                try {
                    inputMethodManager.hideSoftInputFromWindow(windowToken, 0);
                } catch (RuntimeException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

Android 软键盘泄漏 leak 和 5497 系统 bug

软键盘 leak

在 Activity#onDestroy() 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Fix the leaks of soft input.
 * <p>Call the function in {@link Activity#onDestroy()}.</p>
 *
 * @param context The context.
 */
public static void fixSoftInputLeaks(final Context context) {
    if (context == null) return;
    InputMethodManager imm =
            (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
    if (imm == null) return;
    String[] strArr = new String[]{"mCurRootView", "mServedView", "mNextServedView", "mLastSrvView"};
    for (int i = 0; i < 4; i++) {
        try {
            Field declaredField = imm.getClass().getDeclaredField(strArr[i]);
            if (declaredField == null) continue;
            if (!declaredField.isAccessible()) {
                declaredField.setAccessible(true);
            }
            Object obj = declaredField.get(imm);
            if (obj == null || !(obj instanceof View)) continue;
            View view = (View) obj;
            if (view.getContext() == context) {
                declaredField.set(imm, null);
            } else {
                return;
            }
        } catch (Throwable th) {
            th.printStackTrace();
        }
    }
}

系统 5497bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Fix the bug of 5497 in Android.
 *
 * @param activity The activity.
 */
public static void fixAndroidBug5497(final Activity activity) {
    final int flags = activity.getWindow().getAttributes().flags;
    if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {
        activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
    }
    final FrameLayout contentView = activity.findViewById(android.R.id.content);
    final View contentViewChild = contentView.getChildAt(0);
    final int paddingBottom = contentViewChild.getPaddingBottom();
    sContentViewInvisibleHeightPre5497 = getContentViewInvisibleHeight(activity);
    contentView.getViewTreeObserver()
            .addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    int height = getContentViewInvisibleHeight(activity);
                    if (sContentViewInvisibleHeightPre5497 != height) {
                        contentViewChild.setPadding(
                                contentViewChild.getPaddingLeft(),
                                contentViewChild.getPaddingTop(),
                                contentViewChild.getPaddingRight(),
                                paddingBottom + height
                        );
                        sContentViewInvisibleHeightPre5497 = height;
                    }
                }
            });
}

Android 键盘面板冲突,布局闪动的解决方法

JKeyboardPanelSwitch

Android 键盘面板冲突 布局闪动处理

Panel 使用:

  1. KPSwitchFSPanelLinearLayout 全屏和 Transulucent 使用
  2. KPSwitchPanelLinearLayout 非全屏和 Transulucent

PanelSwitchHelper

一个帮助键盘平稳过渡到功能面板的框架,支持动画无缝衔接,支持 activity/fragment/dialog/dialogFragment/popupWindow 容器,支持 IM/直播/视频播放/信息流评论等场景,支持全屏模式。

FluidKeyboardResize

本文由作者按照 CC BY 4.0 进行授权