首页 > 代码库 > android 软键盘的显示与隐藏问题的研究

android 软键盘的显示与隐藏问题的研究

在android中,经常会和输入法的软件键盘交互。在Manifest文件里,系统给activity的一个属性-windowSoftInputMode来控制输入法的显示方式。该属性提供了Activity的window与软键盘的window交互的方式。这里的属性设置有两方面的影响:
1.软键盘的显示与隐藏。-当Activity界面成为用户的焦点时,或隐藏或显示。
2。对Activty的主window窗口进行调整。或者将Activity的window窗口调小以便为软键盘腾出空间,或者当Activity的部分window被软件盖住时,移动Activity的内容以便用户能够看到当前的焦点。

你至少要从下列属性值选取一项对其设置,要么是”state...“,要么是”adjust...“。你可以设置多个值。设置不同的属性值,要用|进行分开。比如:
<activityandroid:windowSoftInputMode="stateVisible|adjustResize" . . . >
下面是对属性的值的描述。
描述
”stateUnSpecified“不指定软件的状态(显示或隐藏)。系统会根据主题中的设置来选择相应的状态。 该属性软键盘的默认设置。
”stateUnchnaged“总是保持上次软键盘的状态。当Activity进入到最前端时,不论是它上次它是显示或隐藏,保持不变。
”stateHidden“当用户进入目标Activity时,软键盘保持隐藏状态。这里的Activity是用户是向前进入Activity,而不是由于退出其它Activity退回到目标Activity。
”stateVisible“只有条件合适(当用户前进进入到Activity的主window),就会显示键盘
”stateAlawaysVisible“当用户选择进入目标Activity时,软键盘被设置为可见的。这里的Activity是用户向前进入的Activity,而不是由于退出其它Activity而回到目标Activity
"adjustUnspecified"不指定是否去调整Activity的界面。或者调整Activity窗口的大小以便为软键盘腾出空间或者移动窗口的内容来屏幕上当前的焦点可见。系统会自动选择其中一种模式,这依赖于窗口是包含可以滑动其内容的view.如有这样的视图,窗口的大小就会被调整。在这样的假定的情况下,很小的滑动就可以使用窗口的内容可见。 该属性是主windowr默认设置。
”adjustResize“Activity的窗口总是被调整其大小以便为软键盘腾出空间。
”adjustPan“Activity的主窗口不会被调整其大小以便为软键盘腾出空间。相反,窗口的内容会被自动移动以便当前的焦点不会被软键盘遮住,用户可以总是看到他输入的内容。这个值一般用于用户很少想调整窗口的大小的情况下,因为用户可能需要关闭软键盘来与窗口的其它部分进行交互。

从上面系统的描述了解了系统在处理两个交互时都显示Activity和软键盘都是看作window窗口来处理的。
很多的时候,我们都希望能够监听到软件键盘的显示与关闭状态,比如下图的情况,

你有一个登录界面是这样设计的


你希望登录时它是这样的,


然后你去尝试给activity的windowSoftInputMode属性加上值,当你加上 adjustResize时,它是这样的


你不甘心,你将adjustResize修改成adjustPan,结果是这样的



没有办法,只有靠我们自己来监听键盘的显示或隐藏来动态改变布局。现在的问题是怎么监听到键盘的动态改变呢,系统并没有提供相应的方法,我们可以通过两种方法来实现。
1.在adjustResize属性下,activity的窗口大小会发生改变,而窗口中的layout的大小也必然后会发生改变。当view大小发生改变时,必然会引起onSizeChanged(int, int, int, int)的回调。所以可能自定义一个Layout,来监测onSizeChanged()函数的回调,然后在其中的作相应的处理。这个方法也是网上传的最多的方法。
2.借助ViewTreeOberserver类。ViewTreeOberserver可以用来注册一个监听器,它能监听view树的全局变化。这些变化包括但不限于,整个View的布局,绘制传递的源头,触摸模式的变化。

第一种方法的实现:
自定义一个Layout,以RelativeLayout为例,

可以在onSizeChanged(int w, int h,int oldw,int oldh) 方法里监听界面大小的变化,然后在该方法里定义回调函数,当监听到键盘被调出时,将键盘向上推一段距离,这样将登录键显示出来。

package com.example.keyboardlistener;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.RelativeLayout;

public class KeyboardLayout extends RelativeLayout {

    private onSizeChangedListener mChangedListener;
    private static final String TAG ="KeyboardLayoutTAG";
    private boolean mShowKeyboard = false;
    
    public KeyboardLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    public KeyboardLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public KeyboardLayout(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG, "onMeasure-----------");
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        super.onLayout(changed, l, t, r, b);
        Log.d(TAG, "onLayout-------------------");
    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // TODO Auto-generated method stub
        super.onSizeChanged(w, h, oldw, oldh);
        Log.d(TAG, "--------------------------------------------------------------");
        Log.d(TAG, "w----" + w + "\n" + "h-----" + h + "\n" + "oldW-----" + oldw + "\noldh----" + oldh);
        if (null != mChangedListener && 0 != oldw && 0 != oldh) {
            if (h < oldh) {
                mShowKeyboard = true;
            } else {
                mShowKeyboard = false;
            }
            mChangedListener.onChanged(mShowKeyboard);
            Log.d(TAG, "mShowKeyboard-----      " + mShowKeyboard);
        }
    }
    
    public void setOnSizeChangedListener(onSizeChangedListener listener) {
        mChangedListener = listener;
    }
    
    interface onSizeChangedListener{
        
        void onChanged(boolean showKeyboard);
    }
    
}

在主Activity里创建一个Handler,当监听到布局发生变化时,通过Handler更新UI。

package com.example.keyboardlistener;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.Button;

import com.example.keyboardlistener.KeyboardLayout.onSizeChangedListener;

public class MainActivity extends Activity {
    
    private static final String TAG = "KeyboardLayoutTAG";
    private KeyboardLayout mRoot;
    private Button mLogin;
    private int mLoginBottom;    
    private static final int KEYBOARD_SHOW = 0X10;
    private static final int KEYBOARD_HIDE = 0X20;
    private boolean mGetBottom = true;
    
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            switch (msg.what) {
            case KEYBOARD_HIDE:
                mRoot.setPadding(0, 0, 0, 0);
                break;

            case KEYBOARD_SHOW:
                int mRootBottom = mRoot.getBottom();
                Log.d(TAG, "the mLoginBottom is  " + mLoginBottom);
                mRoot.setPadding(0, mRootBottom - mLoginBottom, 0, 0);
                break;
                
            default:
                break;
            }
        }
        
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        getActionBar().hide();
        mRoot = (KeyboardLayout) findViewById(R.id.root_view);
        mLogin = (Button) findViewById(R.id.login);
        mRoot.setOnSizeChangedListener(new onSizeChangedListener() {
            
            @Override
            public void onChanged(boolean showKeyboard) {
                // TODO Auto-generated method stub
                if (showKeyboard) {
                    mHandler.sendMessage(mHandler.obtainMessage(KEYBOARD_SHOW));
                    Log.d(TAG, "show keyboard");
                } else {
                    mHandler.sendMessage(mHandler.obtainMessage(KEYBOARD_HIDE));
                }
            }
        });
        
        mRoot.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
            
            @Override
            public boolean onPreDraw() {
                // TODO Auto-generated method stub
                if (mGetBottom) {
                    mLoginBottom = mLogin.getBottom();//获取登录按钮的位置信息。
                }
                mGetBottom = false;
                return true;
            }
        });
        
    }

//    @Override
//    public boolean onCreateOptionsMenu(Menu menu) {
//        // Inflate the menu; this adds items to the action bar if it is present.
//        getMenuInflater().inflate(R.menu.main, menu);
//        return true;
//    }
    
    

}

第二种方法的实现:

借助ViewTreeObserver类对你想监听的view添加OnGlobalLayoutListener监听器,然后在onGlobalLayout()方法里窗口的变化情况来判断键盘是否被调出来了。下面是ViewTreeObserver监听的部分的代码:

mRoot.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            
            @Override
            public void onGlobalLayout() {
                int offset = mRoot.getRootView().getHeight() - mRoot.getHeight();
                //根据视图的偏移值来判断键盘是否显示
                if (offset > 300) {
                    mHandler.sendMessage(mHandler.obtainMessage(KEYBOARD_SHOW));
                } else {
                    mHandler.sendMessage(mHandler.obtainMessage(KEYBOARD_HIDE));
                }
                
            }
        });

其中Handler的具体实现同上述结构中的Handler一样。


参考文章:InputMethod


例子源码下载地址