如何在Android中捕获“虚拟键盘显示/隐藏”事件?


Answers:


69

注意

此解决方案不适用于软键盘, onConfigurationChanged也不适用于软(虚拟)键盘。


您必须自己处理配置更改。

http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

样品:

// from the link above
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);


    // Checks whether a hardware keyboard is available
    if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show();
    } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show();
    }
}

然后,只需更改某些视图的可见性,更新字段并更改布局文件即可。


4
@shiami试试newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO〜克里斯
cimnine

3
仅当您已注册活动以监听AndroidManifest中所需的configChanges时,此方法才有效。
2011年

65
请更新您的答案,并告知它不适用于软键盘。我浪费了半天时间尝试您的代码。然后看到了这些评论。
Shirish Herwade

17
这对于原来是“虚拟”键盘不起作用。
brummfondel 2014年

18
好吧,问题是关于软键盘,为什么对硬件键盘的答案是公认的?-1!
Denys Vitali

56

这可能不是最有效的解决方案。但这每次都对我有用...在需要收听softKeyboard的任何地方调用此函数。

boolean isOpened = false;

public void setListenerToRootView() {
    final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {

            int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
            if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.
                Toast.makeText(getApplicationContext(), "Gotcha!!! softKeyboardup", 0).show();

                if (isOpened == false) {
                    //Do two things, make the view top visible and the editText smaller
                }
                isOpened = true;
            } else if (isOpened == true) {
                Toast.makeText(getApplicationContext(), "softkeyborad Down!!!", 0).show();
                isOpened = false;
            }
        }
    });
}

注意:如果用户使用浮动键盘,此方法将导致问题。


1
addOnGlobalLayoutListener?
coolcool1994 2014年

7
这闻起来像是内存泄漏。您正在向全局对象添加侦听器,该侦听器将保留您并永远不会让您离开。
flexicious.com 2014年

9
这也不适用于使用android:windowSoftInputMode="adjustPan"adjustResize全屏窗口设置的“活动” ,因为布局永远不会调整大小。
Ionoclast Brigham

1
这仅适用于adjustResize。对于adjustPan,heightDiff不会更改。
alexhilton

2
为什么要比较布尔值?
Xerus

37

如果要从“活动”中处理IMM(虚拟)键盘窗口的显示/隐藏,则需要对布局进行子类化,并重写onMesure方法(以便您可以确定布局的宽度和高度)。之后,通过setContentView()将子类化的布局设置为您的Activity的主视图。现在,您将能够处理IMM显示/隐藏窗口事件。如果听起来很复杂,那不是真的。这是代码:

main.xml

   <?xml version="1.0" encoding="utf-8"?>
   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >
        <EditText
             android:id="@+id/SearchText" 
             android:text="" 
             android:inputType="text"
             android:layout_width="fill_parent"
             android:layout_height="34dip"
             android:singleLine="True"
             />
        <Button
             android:id="@+id/Search" 
             android:layout_width="60dip"
             android:layout_height="34dip"
             android:gravity = "center"
             />
    </LinearLayout>

现在在您的Activity中声明布局的子类(main.xml)

    public class MainSearchLayout extends LinearLayout {

    public MainSearchLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");

        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight){
            // Keyboard is shown

        } else {
            // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

您可以从代码中看到,我们为子类构造函数中的Activity增加了布局

inflater.inflate(R.layout.main, this);

现在,只需为我们的活动设置子类化布局的内容视图即可。

public class MainActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MainSearchLayout searchLayout = new MainSearchLayout(this, null);

        setContentView(searchLayout);
    }

    // rest of the Activity code and subclassed layout...

}

3
我需要进一步调查,但是对于在大屏幕设备上进行小对话框的情况下(对于布局测量不会受到键盘的存在影响的情况下)小对话框是否可行,我感到怀疑。
PJL

4
对于android:windowSoftInputMode =“ adjustPan”无效。我希望在出现软键盘后,屏幕不会缩水。您能告诉我们任何解决方法,以便它甚至适用于AdjustPan
Shirish Herwade

如果(actualHeight> proposaledheight){//显示键盘} else {//键盘被隐藏}
Aamirkhan

您也可以使用具有相同想法的自定义视图,例如gist.github.com/juliomarcos/8ca307cd7eca607c8547
Julio Rodrigues

1
不适用于使用android:windowSoftInputMode="adjustPan"adjustResize全屏窗口设置的“活动” ,因为布局永远不会调整大小。
Ionoclast Brigham

35

我这样做:

添加OnKeyboardVisibilityListener界面。

public interface OnKeyboardVisibilityListener {
    void onVisibilityChanged(boolean visible);
}

HomeActivity.java

public class HomeActivity extends Activity implements OnKeyboardVisibilityListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_sign_up);
    // Other stuff...
    setKeyboardVisibilityListener(this);
}

private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
    final View parentView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
    parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        private boolean alreadyOpen;
        private final int defaultKeyboardHeightDP = 100;
        private final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
        private final Rect rect = new Rect();

        @Override
        public void onGlobalLayout() {
            int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
            parentView.getWindowVisibleDisplayFrame(rect);
            int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == alreadyOpen) {
                Log.i("Keyboard state", "Ignoring global layout change...");
                return;
            }
            alreadyOpen = isShown;
            onKeyboardVisibilityListener.onVisibilityChanged(isShown);
        }
    });
}


@Override
public void onVisibilityChanged(boolean visible) {
    Toast.makeText(HomeActivity.this, visible ? "Keyboard is active" : "Keyboard is Inactive", Toast.LENGTH_SHORT).show();
  }
}

希望这对您有帮助。


2
谢谢你。这是完美的解决方案+1
Harin Kaklotar

1
谢谢,为我工作!如果您只想调整RecyclerView,请参阅此处的解决方案:stackoverflow.com/a/43204258/373106
David Papirov

1
完美的可重用实现,可用于Activity或Fragment,谢谢
Pelanes

1
真的很好。
ZaoTaoBao18年

@DavidPapirov,您粘贴了指向RecyclerView的链接,但此处未提及。
CoolMind

22

基于Nebojsa Tomcic的代码,我开发了以下RelativeLayout子类:

import java.util.ArrayList;

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

public class KeyboardDetectorRelativeLayout extends RelativeLayout {

    public interface IKeyboardChanged {
        void onKeyboardShown();
        void onKeyboardHidden();
    }

    private ArrayList<IKeyboardChanged> keyboardListener = new ArrayList<IKeyboardChanged>();

    public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    public void addKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.add(listener);
    }

    public void removeKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.remove(listener);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight) {
            notifyKeyboardShown();
        } else if (actualHeight < proposedheight) {
            notifyKeyboardHidden();
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void notifyKeyboardHidden() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardHidden();
        }
    }

    private void notifyKeyboardShown() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardShown();
        }
    }

}

效果很好...标记,当您的“活动”的“软输入模式”设置为“ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE”时,此解决方案将起作用


3
对于android:windowSoftInputMode =“ adjustPan”无效。我希望在出现软键盘后,屏幕不会缩水。您能告诉我们任何解决方法,以便它甚至适用于AdjustPan
Shirish Herwade

1
这也不适用于使用android:windowSoftInputMode="adjustPan"adjustResize全屏窗口设置的“活动” ,因为布局永远不会调整大小。
Ionoclast Brigham

触发了好几次。
zionpi '16

22

就像@amalBit的答案一样,向全局布局注册一个侦听器,并计算dectorView的可见底部与其建议的底部之间的差异,如果该差异大于某个值(猜测IME的高度),则我们认为IME向上:

    final EditText edit = (EditText) findViewById(R.id.edittext);
    edit.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (keyboardShown(edit.getRootView())) {
                Log.d("keyboard", "keyboard UP");
            } else {
                Log.d("keyboard", "keyboard Down");
            }
        }
    });

private boolean keyboardShown(View rootView) {

    final int softKeyboardHeight = 100;
    Rect r = new Rect();
    rootView.getWindowVisibleDisplayFrame(r);
    DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
    int heightDiff = rootView.getBottom() - r.bottom;
    return heightDiff > softKeyboardHeight * dm.density;
}

高度阈值100是IME的推测最小高度。

这适用于AdjustPan和AdjustResize。


2
我正要拔头发!!您救了我的头发;)
Vijay Singh Chouhan

1
这是唯一的好答案,它可以在软键盘上正常工作,谢谢
Z3nk

12

Nebojsa的解决方案几乎对我有用。当我在多行EditText中单击时,它知道已显示键盘,但是当我开始在EditText中键入时,actualHeight和proposalHeight仍然相同,因此它不知道它们的键盘仍在显示。我做了一点修改以存储最大高度,并且效果很好。这是修改后的子类:

public class CheckinLayout extends RelativeLayout {

    private int largestHeight;

    public CheckinLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.checkin, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        largestHeight = Math.max(largestHeight, getHeight());

        if (largestHeight > proposedheight)
            // Keyboard is shown
        else
            // Keyboard is hidden

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

10

不知道是否有人发布此内容。发现此解决方案易于使用!。该SoftKeyboard类是gist.github.com。但是,在键盘弹出/隐藏事件回调中,我们需要一个处理程序来在UI上正确执行操作:

/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use your root layout
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);

/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{

    @Override
    public void onSoftKeyboardHide() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });
    }

    @Override
    public void onSoftKeyboardShow() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });

    }   
});

这是获得SoftkeyBoard的Git“ gist.github.com/felHR85/…
douarbou 2015年

9

我通过在自定义EditText中重写onKeyPreIme(int keyCode,KeyEvent事件)来解决此问题。

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
        //keyboard will be hidden
    }
}

如何在片段或活动中使用它?@qbait
Maulik Dodia

它不起作用,只有当我离开页面时才能调用它。
DysaniazzZ

这是EditText的方法,请参见以下答案:stackoverflow.com/a/5993196/2093236
Dmide

4

我有办法做到这一点。虽然似乎没有要当软键盘显示或隐藏的检测方式,你可以在实际上检测时,它是大约要通过设置来显示或隐藏OnFocusChangeListenerEditText,你听。

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            //hasFocus tells us whether soft keyboard is about to show
        }
    });

注意:此骇客要注意的一件事是,EditText获得或失去焦点时会立即触发此回调。实际上,这将在软键盘显示或隐藏之前触发。我发现在键盘显示或隐藏执行某操作的最佳方法是使用a Handler并延迟约400ms,如下所示:

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            new Handler().postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        //do work here
                    }
                }, 400);
        }
    });

1
否则不起作用。OnFocusChangeListener仅告诉EditText状态更改后是否具有焦点。但是IMEEditText有焦点时可能会隐藏起来,如何检测到这种情况?
DysaniazzZ


2

我已经解决了单行textview后编码的问题。

package com.helpingdoc;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

public class MainSearchLayout extends LinearLayout {
    int hieght = 0;
    public MainSearchLayout(Context context, AttributeSet attributeSet) {

        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);


    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");
       if(getHeight()>hieght){
           hieght = getHeight();
       }
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();
        System.out.println("....hieght = "+ hieght);
        System.out.println("....actualhieght = "+ actualHeight);
        System.out.println("....proposedheight = "+ proposedheight);
        if (actualHeight > proposedheight){
            // Keyboard is shown


        } else if(actualHeight<proposedheight){
            // Keyboard is hidden

        }

        if(proposedheight == hieght){
             // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

2
对于android:windowSoftInputMode =“ adjustPan”无效。我希望在出现软键盘后,屏幕不会缩水。您能告诉我们任何解决方法,以便它甚至适用于AdjustPan
Shirish Herwade

当函数hide / show隐藏/显示时,此侦听器方法将调用两次或三次。我不完全是问题所在。
Jagveer Singh Rajput 2014年

2

您还可以检查第一个DecorView的子底部填充。显示键盘时,它将设置为非零值。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    View view = getRootView();
    if (view != null && (view = ((ViewGroup) view).getChildAt(0)) != null) {
        setKeyboardVisible(view.getPaddingBottom() > 0);
    }
    super.onLayout(changed, left, top, right, bottom);
}

1

可以通过OnGlobalLayoutListener中的简单hack来监听键盘的Hide | Show事件:

 final View activityRootView = findViewById(R.id.top_root);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();

                if (heightDiff > 100) {
                    // keyboard is up
                } else {
                    // keyboard is down
                }
            }
        });

这里activityRootView是您的Activity的根视图。


我的heightDiff在开始时是160,在kbd时是742,所以我不得不在开始处引入并设置initialHeightDiff
djdance

0

使用viewTreeObserver轻松获取键盘事件。

layout_parent.viewTreeObserver.addOnGlobalLayoutListener {
            val r = Rect()
            layout_parent.getWindowVisibleDisplayFrame(r)
            if (layout_parent.rootView.height - (r.bottom - r.top) > 100) { // if more than 100 pixels, its probably a keyboard...
                Log.e("TAG:", "keyboard open")
            } else {
                Log.e("TAG:", "keyboard close")
            }
        }

** layout_parent是您的视图edit_text.parent


-2

Nebojsa Tomcic的回答对我没有帮助。我有RelativeLayoutTextViewAutoCompleteTextView里面。TextView显示键盘和隐藏键盘时,我需要将其滚动到底部。为了实现这一点,我推翻了onLayout方法,它对我来说很好用。

public class ExtendedLayout extends RelativeLayout
{
    public ExtendedLayout(Context context, AttributeSet attributeSet)
    {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);

        if (changed)
        {
            int scrollEnd = (textView.getLineCount() - textView.getHeight() /
                textView.getLineHeight()) * textView.getLineHeight();
            textView.scrollTo(0, scrollEnd);
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.