填坑之旅--输入法

2,021 阅读11分钟

版权声明:本文为CSDN博主「Vander丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/l540675759/…

参考自:

1.彻底搞定Android开发中软键盘的常见问题 blog.csdn.net/mynameishua…

2.Android UI(EditText)详解 blog.csdn.net/qq_28057577…

3.微信软键盘布局闪动问题 blog.dreamtobe.cn/2015/09/01/…

前言

本文将从以下几个方面进行介绍:

(1)InputMethodService的源码解析,从源码解读中告诉你为什么软键盘弹出的是一个Dialog

(2)Android软键盘显示时,设置windowSoftInputMode的作用

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

(4)软键盘上面的按键监听

(5)横屏状态下,不希望软键盘显示全屏怎么处理

(6)控制软键盘的弹出和关闭的方法

(7)EditText在软键盘弹出的时候显示不全,怎么获取软键盘弹出和关闭的监听

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

Android软键盘的显示原理

软键盘其实是一个Dialog

  • InputMethodService为我们的输入法创建了一个Dialog,并且对某些参数进行了设置,使之能够在底部或者全屏显示。当我们点击输入框时,系统会对当前的主窗口进行调整,以便留出相应的空间来显示该Dialog在底部,或者全屏。

InputMethodService的源码解析

screenshot-20211012-173147.png

因为InputMethodService属于服务,接下来我们先看一下服务的入口onCreate()方法:

    @Override 
    public void onCreate() {
        //设置主题与xml里面设置theme是一样的道理
        mTheme = Resources.selectSystemTheme(mTheme,
                getApplicationInfo().targetSdkVersion,
                android.R.style.Theme_InputMethod,
                android.R.style.Theme_Holo_InputMethod,
                android.R.style.Theme_DeviceDefault_InputMethod,
                android.R.style.Theme_DeviceDefault_InputMethod);
        super.setTheme(mTheme);
        //创建InputMethodMananger
        mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
        mSettingsObserver = SettingsObserver.createAndRegister(this);
        mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0);
        mInflater = (LayoutInflater)getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        /**
         * 这里注意一下,首先这里的命名属于Window,然后我们发现了Gravity.BOTTOM,就更加确定了这个就是
         * 软键盘所创建的Dialog对象
         */
        mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
                WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
        if (mHardwareAccelerated) {
            mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        }
        initViews();
        mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
    }

通过上面的分析,我们怀疑这里的SoftInputWindow是软键盘弹出创建的Dialog对象,下面我们看下SoftInputWindow的源码。

public class SoftInputWindow extends Dialog{
    ....
}

软键盘的显示调整(windowSoftInputMode)

在Android中,可以通过给Activity设置windowSoftInputMode这个属性来控制软键盘与Activity的主窗口的交互方式。

Activity 的主窗口与包含屏幕软键盘的窗口的交互方式,该属性的设置影响两个方面:

1、当Activity成为用户注意的焦点时软键盘的状态 
    - 隐藏还是可见。
2、对Activity主窗口所做的调整 
    - 意思是是否将其尺寸调小为软键盘腾出空间
    - 当窗口部分被软键盘遮挡时是否平移其内容以使当前焦点可见。
  • 该设置必须是下面所列的值之一,或者是一个“state…”值加上一个“adjust…”值的组合,在任一组中设置多个值(例如,多个“state…”值)都会产生未定义结果。各值之间使用垂直条 | 分隔

控制软键盘显示还是隐藏

  • stateUnspecified 不指定软键盘的状态(隐藏还是可见) 将由系统选择合适的状态,或依赖主题中的设置,这是对软键盘行为的默认设置

  • stateUnchanged 保留状态 当 Activity 转至前台时保留软键盘最后所处的任何状态,无论是可见还是隐藏

  • stateHidden 隐藏软键盘 当用户确实是向前导航到 Activity,而不是因离开另一Activity 而返回时隐藏软键盘

  • stateAlwaysHidden 始终隐藏软键盘 当 Activity 的主窗口有输入焦点时始终隐藏软键盘

  • stateVisible 显示软键盘 在正常的适宜情况下(当用户向前导航到 Activity 的主窗口时)显示软键盘

  • stateAlwaysVisible 显示软键盘 当用户确实是向前导航到 Activity,而不是因离开另一Activity 而返回时.

stateVisible:从其他页面或桌面往前进入该Activity,会弹出软件盘,如果是从其他页面返回该Activity,则已收起的软键盘不会再弹出。

stateAlwaysVisible:不管是从从其他页面或桌面往前进入该Activity,还是从其他页面返回该Activity,已收起的软键盘都会再弹出。

在软键盘弹出时,是否需要Activity对此进行调整

  • adjustUnspecified 主窗口的默认行为,不指定 Activity 的主窗口是否调整尺寸以为软键盘腾出空间,或者窗口内容是否进行平移以在屏幕上显露当前焦点。 系统会根据窗口的内容是否存在任何可滚动其内容的布局视图来自动选择其中一种模式。 在无滑动的控件上,默认的指定形式为adjustPan

  • adjustResize 始终调整 Activity 主窗口的尺寸来为屏幕上的软键盘腾出空间。

  • adjustPan 不调整 Activity 主窗口的尺寸来为软键盘腾出空间, 而是自动平移窗口的内容,使当前焦点永远不被键盘遮盖,让用户始终都能看到其输入的内容。 这通常不如尺寸调整可取,因为用户可能需要关闭软键盘以到达被遮盖的窗口部分或与这些部分进行交互。

  • adjustNoting 软键盘弹出时,主窗口Activity不会做出任何响应。

windowSoftInputMode 应用场景

adjustUnspecified : 当软键盘弹出时,系统自动指定窗口的调整模式,根据不同的情况会选择adjustResize或者adjustPan的一种。

adjustPan : 当软键盘弹出时,会将主窗口的平移(translateY),来适应软键盘的显示。

adjustResize : 当软键盘弹出时,会让布局重新绘制,这种一般适应于带有滑动性质的控制,让其向下滚动,然后适应软键盘的显示。

adjustNoting: 软键盘弹出时,主窗口不会做出任何反应。

20170707121951377.png

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

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

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

果要在横屏状态不希望软键盘全屏显示最好是将flagNoFullscreen和flagNoExtractUi结合使用,这样体验上会更好

android:imeOptions=”flagNoFullscreen|flagNoExtractUi”

Android软键盘上的按键监听

如果需要监听软键盘的右下角的按键,需要为EditText设置setOnEditorActionListener()监听,如果需要消费事件,则需要return true

        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;
            }
        });

控制键盘显示、隐藏

1、显示软键盘最可靠的方法如下。

InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
    view.requestFocus();
    imm.showSoftInput(view, 0);
}
  • view必须是VISIBLE的EditText,如果不是VISIBLE的,需要先将其设置为VISIBLE。
  • 当前界面必须已经加载完成,不能直接在Activity的onCreate(),onResume(),onAttachedToWindow()中使用,可以在这些方法中通过postDelayed的方式来延迟执行showSoftInput()。

2、隐藏软键盘最方便的方法如下。

InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
  • view可以当前布局中已经存在的任何一个View,如果找不到可以用getWindow().getDecorView()。

更多详细内容 Android手动显示和隐藏软键盘方法总结

EditText显示不全,并且需要监听软键盘弹出和关闭

属于软键盘遮挡布局的问题:

软键盘遮盖焦点:

当软键盘弹出的时候,将EditText等输入类的控件的焦点遮盖时,这时候可以设置adjustPan或者adjustResize可以很好的解决。

软键盘遮盖没有遮盖焦点,但是遮盖了需要显示的控件:

这时候设置通过设置属性adjustPan或者adjustResize还不足以解决问题,因为软键盘并没有遮盖了EditText的焦点,所以单独设置这两个属性是对软键盘或者界面是无法产生改变的。

这时我们必须从另外一个角度来考虑这个问题,就是来监听软键盘的弹出和关闭来操作布局,来解决这个问题。

这时候普遍会有以下的几种解决方案:

(1)设置adjustResize属性,当软键盘弹出的时候会重绘布局,然后设置根布局的OnLayoutChangeListener的监听,来监听布局的变化。

 mScrollView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
 @Override
 public void onLayoutChange(View v, int leftint top, int rightint bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                Log.d("new change ", "left : " + left + "top : " + top + "right : " + right + "bottom : " + bottom);
                Log.d("old change ", "oldLeft : " + oldLeft + "oldTop : " + oldTop + "oldRight : " + oldRight + "oldBottom : " + oldBottom);
            }
        });

//当软键盘弹出
07-09 21:16:22.911 23653-23653/com.xiucai.softdemo D/new change: left : 0top : 0right : 1080bottom : 817
07-09 21:16:22.911 23653-23653/com.xiucai.softdemo D/old change: oldLeft : 0oldTop : 0oldRight : 1080oldBottom : 1692

//当软键盘关闭
07-09 21:16:44.457 23653-23653/com.xiucai.softdemo D/new change: left : 0top : 0right : 1080bottom : 1692
07-09 21:16:44.457 23653-23653/com.xiucai.softdemo D/old change: oldLeft : 0oldTop : 0oldRight : 1080oldBottom : 817

我们可以通过上述打印结果发现,根布局的bottom发生变化了,从1692变化到817。我们可以通过这种方式来监听软键盘的弹出和收回,然后实现平移根布局或者其他的操作来保证软键盘不会遮盖你所需要的显示的控件。

(2)通过getViewTreeObserver().addOnGlobalLayoutListener()监听窗体的可见区域,来判断软键盘是否弹出。

/** 
 * @param root 最外层布局,需要调整的布局 
 * @param scrollToView 被键盘遮挡的scrollToView,滚动root,使scrollToView在root可视区域的底部 
 */  
private void controlKeyboardLayout(final View root, final View scrollToView) {  
    root.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() {  
        @Override  
        public void onGlobalLayout() {  
            Rect rect = new Rect();  
            //获取root在窗体的可视区域  
            root.getWindowVisibleDisplayFrame(rect);  
            //获取root在窗体的不可视区域高度(被其他View遮挡的区域高度)  
            int rootInvisibleHeight = root.getRootView().getHeight() - rect.bottom;  
            //若不可视区域高度大于100,则键盘显示  
            if (rootInvisibleHeight > 100) {  
                int[] location = new int[2];  
                //获取scrollToView在窗体的坐标  
                scrollToView.getLocationInWindow(location);  
                //计算root滚动高度,使scrollToView在可见区域  
                int srollHeight = (location[1] + scrollToView.getHeight()) - rect.bottom;  
                root.scrollTo(0, srollHeight);  
            } else {  
                //键盘隐藏  
                root.scrollTo(00);  
            }  
        }  
    });  
}  

上面代码目的让大家了解这种方式是怎么判断软键盘的弹出和隐藏。

里面需要平移的View,不一定是ScrollView,其他View也可,一般来说作为XML的根布局即可。

平常一般来说,我会使用这个工具类

package com.xiucai.common.manager;

import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;

import java.util.LinkedList;
import java.util.List;

/**
 * Created by SuperD on 2017/5/12.
 */

public class SoftKeyBroadManager  implements ViewTreeObserver.OnGlobalLayoutListener{

    public interface SoftKeyboardStateListener {
        void onSoftKeyboardOpened(int keyboardHeightInPx);

        void onSoftKeyboardClosed();
    }

    private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
    private final View activityRootView;
    private int lastSoftKeyboardHeightInPx;
    private boolean isSoftKeyboardOpened;

    public SoftKeyBroadManager(View activityRootView) {
        this(activityRootView, false);
    }

    public SoftKeyBroadManager(View activityRootView, boolean isSoftKeyboardOpened) {
        this.activityRootView = activityRootView;
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    public void onGlobalLayout() {
        final Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        Log.d("SoftKeyboardStateHelper""heightDiff:" + heightDiff);
        if (!isSoftKeyboardOpened && heightDiff > 500) { // if more than 100 pixels, its probably a keyboard...
            isSoftKeyboardOpened = true;
            notifyOnSoftKeyboardOpened(heightDiff);
            //if (isSoftKeyboardOpened && heightDiff < 100)
        } else if (isSoftKeyboardOpened && heightDiff < 500) {
            isSoftKeyboardOpened = false;
            notifyOnSoftKeyboardClosed();
        }
    }

    public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
    }

    public boolean isSoftKeyboardOpened() {
        return isSoftKeyboardOpened;
    }

    /**
     * Default value is zero (0)
     *
     * @return last saved keyboard height in px
     */
    public int getLastSoftKeyboardHeightInPx() {
        return lastSoftKeyboardHeightInPx;
    }

    public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.add(listener);
    }

    public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.remove(listener);
    }

    private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
        this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;

        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardOpened(keyboardHeightInPx);
            }
        }
    }

    private void notifyOnSoftKeyboardClosed() {
        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardClosed();
            }
        }
    }
}

不管是全屏或者其他情况,使用这个工具类来监听软键盘就可以了,具体的用法:

SoftKeyBroadManager mManager =new SoftKeyBroadManager ("根布局");
//添加软键盘的监听,然后和上面一样的操作即可.
mSoftKeyBroadManager.addSoftKeyboardStateListener();
//注意销毁时,得移除监听
mSoftKeyBroadManager.removeSoftKeyboardStateListener();

带有WebView时输入法遮挡问题

 a) 如果非`全屏模式`,可以使用`adjustResize`
 b) 如果是`全屏模式`,则使用`AndroidBug5497Workaround`进行处理。

更多详细内容: Android爬坑之旅:软键盘挡住输入框问题的终极解决方案

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

  • 自己的防抖动方案:

android.view.ViewRootImpl#scrollToRectOrFocus,系统使用它自动滚动 为了防止键盘弹出,系统调用DecorView产生滚动,导致页面抖动


@Override
public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
    // 为了防止键盘弹出,系统调用DecorView产生滚动,导致页面抖动
    // android.view.ViewRootImpl#scrollToRectOrFocus
    return enableGlobalVisibleRect && super.getGlobalVisibleRect(r, globalOffset);
}

@Override
public View findFocus() {
    View focus = super.findFocus();
    if (focus == this) {
        enableGlobalVisibleRect = false;
        post(new Runnable() {
            @Override
            public void run() {
                enableGlobalVisibleRect = true;
            }
        });
    }
    return focus;
}

// 屏蔽
private boolean enableGlobalVisibleRect = true;

没有解决冲突

解决Android软键盘,布局闪动的相关博文

解决Android软键盘布局闪动的Demo

开源的Android软键盘布局闪动的解决方案