我想根据是否显示虚拟键盘来改变布局。我已经搜索了API和各种博客,但似乎找不到任何有用的东西。

这可能吗?

谢谢!


当前回答

我所做的是创建一个简单的绑定来隐藏视图时,键盘是可见的。 解决方案基于当前AndroidX实现的WindowInsetsCompat,仍处于beta (AndroidX核心1.5)-源代码

private fun isKeyboardVisible(insets: WindowInsets): Boolean {
    val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets)
    val systemWindow = insetsCompat.systemWindowInsets
    val rootStable = insetsCompat.stableInsets
    if (systemWindow.bottom > rootStable.bottom) {
        // This handles the adjustResize case on < API 30, since
        // systemWindow.bottom is probably going to be the IME
        return true
    }
    return false
}

@BindingAdapter("goneWhenKeyboardVisible")
fun View.goneWhenKeyboardVisible(enabled: Boolean) {
    if (enabled) {
        setOnApplyWindowInsetsListener { view, insets ->
            visibility = if (isKeyboardVisible(insets)) GONE else VISIBLE
            insets
        }
    } else {
        setOnApplyWindowInsetsListener(null)
        visibility = VISIBLE
    }
}

用法:

<FrameLayout
                android:id="@+id/bottom_toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:goneWhenKeyboardVisible="@{true}"
                />

其他回答

Nebojsa Tomcic的回答对我没有帮助。我有RelativeLayout与TextView和AutoCompleteTextView里面。我需要滚动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);
        }
    }
}

使用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

如果你想从你的活动中处理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);

现在只需设置Activity的子类布局的内容视图。

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...

}

Nebojsa的解决方案几乎对我有效。当我在一个多行EditText中单击时,它知道键盘被显示出来,但是当我开始在EditText中输入时,actualHeight和proposedHeight仍然是相同的,所以它不知道键盘仍然被显示出来。我做了一个轻微的修改,以存储最大高度,它工作得很好。下面是修改后的子类:

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

2020年更新

这现在是可能的:

在Android 11上,你可以这样做

view.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback {
    override fun onEnd(animation: WindowInsetsAnimation) {
        super.onEnd(animation)
        val showingKeyboard = view.rootWindowInsets.isVisible(WindowInsets.Type.ime())
        // now use the boolean for something
    }
})

你也可以听显示/隐藏键盘的动画,并做相应的转换。

我建议阅读Android 11预览版和相应的文档

Android 11之前

但是,这项工作还没有Compat版本,所以您需要求助于黑客。

你可以得到窗口的insets,如果底部的insets大于一些值,你认为是合理的(通过实验),你可以认为这是显示键盘。这不是很好,在某些情况下可能会失败,但没有框架支持这一点。

这是对这个确切问题的一个很好的回答https://stackoverflow.com/a/36259261/372076。或者,这里有一个页面给出了一些不同的方法来实现这个在Android 11之前:

https://developer.salesforce.com/docs/atlas.en-us.noversion.service_sdk_android.meta/service_sdk_android/android_detecting_keyboard.htm


Note

此解决方案不适用于软键盘和 onConfigurationChanged将不会被soft (virtual)调用 键盘。


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

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

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