在项目开发中,用到编辑框的地方经常涉及到要监听或者控制软键盘的显示/隐藏状态。本以为这是很容易解决的一个小问题,没想到当初碰到这个问题才明白还得花点小心思才能整好。现将针对软键盘的显示/隐藏状态的监听/监控方法做一些总结,以备后用。


一、点击空白处隐藏软键盘

这是具有编辑框焦点的页面对输入法软键盘状态监听的一般需求和解决方法.

首先获得InputMethodManager:

       InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);


监听点击:

    /**
     * 点击监听
     */
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        onHideSoftInput(event);
        return super.onTouchEvent(event);
    }
    
    /**
     * 点击空白处,关闭输入法软键盘
     */
    public void onHideSoftInput(MotionEvent event)
    {
        if (event.getAction() == MotionEvent.ACTION_DOWN)
        {
            if (getCurrentFocus() != null && getCurrentFocus().getWindowToken() != null)
            {
                manager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
            }
        }
    }



二、popwindow与输入法软键盘的结合

    先说下自己想实现的效果:点击顶部按钮,打开编辑菜单popwindow并自动弹出软键盘;再次点击顶部按钮,或者点击编辑菜单popwindow上面的底部按钮,关闭菜单并隐藏软键盘;菜单打开状态,点击返回键,若菜单已显示先关闭软键盘,再点击则关闭菜单。

    大致效果图如下:

wKioL1P1iuXSwcC_AAFS9YVSCmM556.gif




1.重写根布局,监听根布局高度变化

对于这个需求,简单的用上面第一点的方法是无效的。这里没法直接通过getCurrentFocus()方法判断页面是否获取焦点来控制,需要通过对popwindow的高度变化进行判断。同时也试过下面的方法,同样无效。


InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
boolean isOpen=imm.isActive();//isOpen若返回true,则表示输入法打开


    popwindow的根布局我这里用的是RelativeLayout,RelativeLayout类可以通过重写onSizeChanged方法来监听布局大小变化。重写一个RelativeLayout类便实现了对popwindow的高度变化的监听了。

代码如下:

/**
 * 监听输入法软键盘显示状态的自定义RelativeLayout
 * 
 * @author zeng
 * 
 */
public class ResizeRelativeLayout extends RelativeLayout
{
    
    public ResizeRelativeLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mListener != null)
        {
            mListener.OnResizeRelative(w, h, oldw, oldh);
        }
    }
    
    // 监听接口
    private OnResizeRelativeListener mListener;
    
    public interface OnResizeRelativeListener
    {
        void OnResizeRelative(int w, int h, int oldw, int oldh);
    }
    
    public void setOnResizeRelativeListener(OnResizeRelativeListener l)
    {
        mListener = l;
    }
    
}


2.配置布局文件,初始化UI等,代码如下:

初始化UI代码:

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        initUI();
    }

    private void initUI()
    {
        mBtn_open = findViewById(R.id.button1);
        mBtn_open.setOnClickListener(this);
        
        // 编辑窗口
        LayoutInflater inflater = getLayoutInflater();
        View menuLayout = inflater.inflate(R.layout.menu_window, null);
        
        mEditMenuWindow = new PopupWindow(menuLayout, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, true);
        mEditMenuWindow.setBackgroundDrawable(getResources().getDrawable(R.color.white));
        mEditMenuWindow.setTouchable(true);
        mEditMenuWindow.setFocusable(true);
        mEditMenuWindow.setAnimationStyle(R.style.MenuAnimation);
        mEditMenuWindow.setOutsideTouchable(false);
        mEditMenuWindow.update();
        //监听菜单消失
        mEditMenuWindow.setOnDismissListener(this);
        
        
        // 菜单控件
        mEt_menu = (EditText) menuLayout.findViewById(R.id.menu_edit);
        TextView btn_send = (TextView) menuLayout.findViewById(R.id.menu_send);
        btn_send.setOnClickListener(this);
        
        
        // 监听主布局大小变化,监控输入法软键盘状态
        listenerKeyBoardState(menuLayout);
    }


其中menu_window.xml文件代码如下:

<?xml version="1.0" encoding="utf-8"?>
<com.example.d_popwindow_inputkeyboard.ResizeRelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/menu_layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:padding="8dp" >

    <EditText
        android:id="@+id/menu_edit"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@+id/menu_send"
        android:layout_marginBottom="8dp"
        android:background="#ffffffff"
        android:enabled="true"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:gravity="top|left"
        android:inputType="none"
        android:padding="8dp"
        android:textSize="18sp" >

        <requestFocus />
    </EditText>

    <TextView
        android:id="@+id/menu_send"
        android:layout_width="fill_parent"
        android:layout_height="60dp"
        android:layout_alignParentBottom="true"
        android:layout_gravity="center"
        android:background="#ff0f0f0f"
        android:gravity="center"
        android:text="发送"
        android:textColor="#ffFF6004"
        android:textSize="20sp"
        android:textStyle="bold" />


</com.example.d_popwindow_inputkeyboard.ResizeRelativeLayout>



3.打开或关闭编辑窗口,同时自动显示或隐藏输入法软键盘。

  此方法两者的控制顺序:先显示软键盘,再打开编辑窗口;先关闭编辑窗口,若软键盘当前已显示则再隐藏软键盘。代码如下:

    //点击顶部发送按钮,打开/关闭编辑窗口
    private void clickTopSend()
    {
        if (mEditMenuWindow.isShowing())
        {
            //先关闭窗口再隐藏软键盘
            mEditMenuWindow.dismiss();
            
            // 隐藏输入法软键盘
//            hideKeyBoard();
        }
        else
        {
            // 窗口显示前显示输入法软键盘
            showKeyBoard();
            
            // 显示输入窗口
            mEditMenuWindow.showAsDropDown(mBtn_open, 0, 0);
        }
    }

    
    // 窗口显示前显示输入法软键盘
    private void showKeyBoard()
    {
        InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);// 调用此方法才能自动打开输入法软键盘
        mEditMenuWindow.setSoftInputMode(
                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        mEditMenuWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); // 在显示popupwindow之后调用,否则输入法会在窗口底层
    }
    
    // 隐藏输入法软键盘
    private void hideKeyBoard()
    {
        InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMgr.toggleSoftInput(InputMethodManager.HIDE_NOT_ALWAYS, 0);// 输入法软键盘打开时关闭,关闭时打开
        mEditMenuWindow.setSoftInputMode(
                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        mEditMenuWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); // 在显示popupwindow之后调用,否则输入法会在窗口底层

        //此方法无效
        //        if(this.getCurrentFocus().getWindowToken() != null)
//        {
//            ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(),
//                    InputMethodManager.HIDE_NOT_ALWAYS);// 关闭输入法软键盘
//        }
        
    }

    
    //点击底部发送按钮,关闭编辑窗口
    private void closeButtomSend()
    {
        mEditMenuWindow.dismiss();        
    }

    //编辑窗口关闭时,隐藏输入法软键盘
    @Override
    public void onDismiss()
    {
        // 如果软键盘打开,隐藏输入法软键盘
        if(mIsKeyboardOpened)
        {
            hideKeyBoard();
        }
    }



4.判断软键盘的显示/隐藏状态。

  通过对编辑窗口的根布局高度变化来判断软键盘是否处于显示状态,在接口方法OnResizeRelative(int w, int h, int oldw, int oldh)里实现对高度变化的判断。这里主要判断以下几种高度变化场景:


(1)布局的当前高度小于上一次的高度,即h < oldh,因为布局被软键盘顶上去了,高度变小了。这种场景同样适用于点击按钮第一次打开窗口时的场景,虽然点击按钮时,肉眼看到的是窗口一下就充满了大半个屏幕,也就是当前h > oldh(oldh = 0)。但事实上是,第一次打开窗口时,窗口菜单首先是充满整个屏幕然后再根据软键盘高度自动缩进的。以下是点击【打开/发送】按钮后,根布局的高度变化日志:

wKioL1P1sEyT8t62AADfC5tJP2w121.jpg

可以看出,首次打开时,窗口高度先是1038,然后自动缩进成544,并非一打开便已计算好填充高度。


(2)还有一种特殊情况,就是三星输入法在软键盘初次打开时,输入字符后软键盘高度会产生变化,同时造成根布局高度变小;若再清除已输入的字符,此时软键盘高度变小,根布局高度变大。而这两种情况下,也就是h < oldh 或者 h > oldh时,软键盘都是处于显示状态。

wKiom1P1lZGRrTh0AAOPbpgoicg133.gif


针对这种情况,我的解决方法是记录下初次打开时根布局的初始高度值,由于h < oldh时在第(1)步已经进行了判断了,所以这里只要判断 h > oldh时的情况。而无论 h > oldh变化多大,只要h不超过初始高度值(且初始高度值不为0),那么便可认为当前软键盘仍是处于打开状态。


此方法运行后的日志如下:

wKiom1P1r4aBlKMUAAEvosZSRHE960.jpg

两种状况下,软键盘都是显示状态。


以下是监听高度变化判断的代码:

    /**
     * 监听主布局大小变化,监控输入法软键盘状态
     * @param menuLayout
     */
    private void listenerKeyBoardState(View menuLayout)
    {
        ResizeRelativeLayout mMenuLayout = (ResizeRelativeLayout) menuLayout.findViewById(R.id.menu_layout);
        mMenuLayout.setOnResizeRelativeListener(new ResizeRelativeLayout.OnResizeRelativeListener()
        {
            @Override
            public void OnResizeRelative(int w, int h, int oldw, int oldh)
            {
                mIsKeyboardOpened = false;
                Log.e("菜单高度", "h = " + h + ",oldh = " + oldh);
                
                //记录第一次打开输入法时的布局高度
                if (h < oldh &&  oldh > 0 && mMenuOpenedHeight == 0)
                {
                    mMenuOpenedHeight = h;
                }
                
                // 布局的高度小于之前的高度
                if (h < oldh )
                {
                    mIsKeyboardOpened = true;
                }
                //或者输入法打开情况下, 输入字符后再清除(三星输入法软键盘在输入后,软键盘高度增加一行,清除输入后,高度变小,但是软键盘仍是打开状态)
                else if((h <= mMenuOpenedHeight) && (mMenuOpenedHeight != 0))
                {
                    mIsKeyboardOpened = true;
                }

                Log.e("是否打开", "软键盘  = " + mIsKeyboardOpened);
            }
        });
    }


最后附上DEMO源码,详见附件。




三、InputMethodManager的一些相关方法(未有效使用过,仅作笔记)

1.调用显示系统默认的输入法

方法一、

InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

imm.showSoftInput(m_receiverView,InputMethodManager.SHOW_FORCED);

m_receiverView(接受软键盘输入的视图(View)

InputMethodManager.SHOW_FORCED(提供当前操作的标记,SHOW_FORCED表示强制显示)


方法二、

InputMethodManager m=(InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
m.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);


这个InputMethodManager类里面的toggleSoftInput方法的API中的解释是:

This method toggles the input method window display. If the input window is already displayed, it gets hidden. If not the input window will be displayed.

这个方法在界面上切换输入法的功能,如果输入法出于现实状态,就将他隐藏,如果处于隐藏状态,就显示输入法。


2.调用隐藏系统默认的输入法

((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(WidgetSearchActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);

(WidgetSearchActivity是当前的Activity)


3.获取输入法打开的状态

InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
boolean isOpen=imm.isActive();

isOpen若返回true,则表示输入法打开



四、SoftInputMode输入法软键盘模式相关说明


输入法软键盘模式选项:

public int softInputMode;


以下与输入法模式有关的各选项说明:


软输入区域是否可见。

public static final int SOFT_INPUT_MASK_STATE = 0x0f;



未指定状态。

public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;


不要修改软输入法区域的状态。

public static final int SOFT_INPUT_STATE_UNCHANGED = 1;


隐藏输入法区域(当用户进入窗口时)。

public static final int SOFT_INPUT_STATE_HIDDEN = 2;


当窗口获得焦点时,隐藏输入法区域。

public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;


显示输入法区域(当用户进入窗口时)。

public static final int SOFT_INPUT_STATE_VISIBLE = 4;


当窗口获得焦点时,显示输入法区域。

public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;


窗口应当主动调整,以适应软输入窗口。

public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;


未指定状态,系统将根据窗口内容尝试选择一个输入法样式。

public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;


当输入法显示时,允许窗口重新计算尺寸,使内容不被输入法所覆盖。

不可与SOFT_INPUT_ADJUSP_PAN混合使用;如果两个都没有设置,系统将根据窗口内容自动设置一个选项。

public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;


输入法显示时平移窗口。它不需要处理尺寸变化,框架能够移动窗口以确保输入焦点可见。

不可与SOFT_INPUT_ADJUST_RESIZE混合使用;如果两个都没有设置,系统将根据窗口内容自动设置一个选项。

public static final int SOFT_INPUT_ADJUST_PAN = 0x20;


当用户转至此窗口时,由系统自动设置,所以你不要设置它。

当窗口显示之后该标志自动清除。

public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;