Android获取软键盘是否弹出、软键盘高度

2,913 阅读2分钟

一、需求

效果图

文字说明

当软键盘弹出的时候,希望底部的Button正好能够在软件盘的上方。

涉及的核心知识点

1、软件盘高度计算。

2、需要考虑底部虚拟按键顶部的状态栏对于软键盘高度计算的影响。

二、实现

情况一、显示状态栏、底部虚拟按键导航栏。

布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入文字"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        android:layout_alignParentBottom="true"
        />
</RelativeLayout>

只有一个顶部的EditText和底部的Button。

MainActivity的逻辑如下:

package com.example.test;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.lang.reflect.Field;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private MainActivity context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 显示导航栏
        //requestWindowFeature(Window.FEATURE_NO_TITLE);
        //getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_main);

        final EditText et = findViewById(R.id.et);
        final Button btn = findViewById(R.id.btn);
        context = this;

        et.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){

            //当键盘弹出隐藏的时候会 调用此方法。
            @Override
            public void onGlobalLayout() {
                final int screenHeight =  context.getWindow().getDecorView().getRootView().getHeight();
                Log.e(TAG,"屏幕高度:" + screenHeight);
                Log.e(TAG,"是否有状态栏:" + isStatusBarShown(context));
                Log.e(TAG,"状态栏的高度:" + getStatusBarHeight(context));
                Log.e(TAG,"是否有虚拟导航栏:" + hasVirtualNavigationBar(context));
                Log.e(TAG,"虚拟导航栏的高度:" + getNavigationBarHeight(context));

                //获得可见区域的大小
                Rect r = new Rect();
                et.getWindowVisibleDisplayFrame(r);
                Log.e(TAG,"可见区域的高度:" + r.height());
                Log.e(TAG,"可见区域的left:" + r.left);
                Log.e(TAG,"可见区域的top:" + r.top);
                Log.e(TAG,"可见区域的right:" + r.right);
                Log.e(TAG,"可见区域的bottom:" + r.bottom);

                if (hasVirtualNavigationBar(context)) {
                    int heightDifference = screenHeight - r.bottom - getNavigationBarHeight(context) ;
                    Log.e(TAG,"软键盘高度:" + heightDifference);

                    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) btn.getLayoutParams();
                    layoutParams.bottomMargin = heightDifference;
                    btn.setLayoutParams(layoutParams);
                } else {
                    int heightDifference = screenHeight - r.bottom ;
                    Log.e(TAG,"软键盘高度:" + heightDifference);

                    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) btn.getLayoutParams();
                    layoutParams.bottomMargin = heightDifference;
                    btn.setLayoutParams(layoutParams);
                }
            }
        });
    }

    public  boolean isStatusBarShown(Activity context) {
        WindowManager.LayoutParams params = context.getWindow().getAttributes();
        int paramsFlag = params.flags & (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
        return paramsFlag == params.flags;
    }

    /**
     * 获得状态栏的高度
     * @param context
     * @return
     */
    public int getStatusBarHeight(Context context){
        Class<?> c = null;
        Object obj = null;
        Field field = null;
        int x = 0, statusBarHeight = 0;
        try {
            c = Class.forName("com.android.internal.R$dimen");
            obj = c.newInstance();
            field = c.getField("status_bar_height");
            x = Integer.parseInt(field.get(obj).toString());
            statusBarHeight = context.getResources().getDimensionPixelSize(x);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        return statusBarHeight;
    }

    /**
     * 判断手机是否含有虚拟按键(底部导航栏)  99%
     */
    public  boolean hasVirtualNavigationBar(Context context) {
        boolean hasSoftwareKeys = true;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Display d = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

            DisplayMetrics realDisplayMetrics = new DisplayMetrics();
            d.getRealMetrics(realDisplayMetrics);

            int realHeight = realDisplayMetrics.heightPixels;
            int realWidth = realDisplayMetrics.widthPixels;

            DisplayMetrics displayMetrics = new DisplayMetrics();
            d.getMetrics(displayMetrics);

            int displayHeight = displayMetrics.heightPixels;
            int displayWidth = displayMetrics.widthPixels;

            hasSoftwareKeys = (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
            boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
            hasSoftwareKeys = !hasMenuKey && !hasBackKey;
        }

        return hasSoftwareKeys;
    }

    /**
     * 获取导航栏高度,有些没有虚拟导航栏的手机也能获取到,建议先判断是否有虚拟按键
     */
    public int getNavigationBarHeight(Context context) {
        int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        return resourceId > 0 ? context.getResources().getDimensionPixelSize(resourceId) : 0;
    }

}

进入页面,没有软键盘,日志如下:

  

进入页面,有软键盘,日志如下:

通过上面的日志,我们可以得出下面的结论:

(1)屏幕高度 = 状态栏高度 + 可见区域高度 + 虚拟导航的高度 + 软键盘的高度

(2)软键盘的高度 = 屏幕高度 - 状态栏高度 - 可见区域高度 - 虚拟导航的高度

情况二、没有底部虚拟按键

没有软件盘,日志如下:

有软键盘:日志如下

通过上面的日志,我们可以得出

(1)虽然没有显示虚拟按键,但是计算出来了虚拟按键的高度。

(2)屏幕的高度 = 状态栏高度 + 可见区域的高度 + 软键盘高度

(3)软键盘高度 = 屏幕高度 - 状态栏高度 - 可见区域高度

情况三:没有底部虚拟按键、没有顶部的状态栏

没有软键盘,日志如下:

有了软键盘,日志如下:

通过上面的日志,我们可以得到如下结论:

在没有虚拟导航栏和顶部状态栏的情况下,

(1)页面高度 =  可见区域高度 + 软键盘高度

(2)软键盘高度 = 页面高度 - 可见区域高度

结论

通过上面的分析

(1)可见区域的大小受到顶部状态栏软件盘底部虚拟按键的影响。

(2)软键盘高度计算的模拟公式      

 result;
 screenHeight;//屏幕高度 
 if(如果有导航栏){
    result = screenHeight - 导航栏的高度    
 }
 if(如果有虚拟按键栏){
    result = screenHeight - 虚拟按键的高度
 }
 result - 可见区域的大小

完成代码

package com.example.test;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.lang.reflect.Field;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private MainActivity context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        final EditText et = findViewById(R.id.et);
        final Button btn = findViewById(R.id.btn);
        context = this;

        et.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){

            //当键盘弹出隐藏的时候会 调用此方法。
            @Override
            public void onGlobalLayout() {
                final int screenHeight =  context.getWindow().getDecorView().getRootView().getHeight();
                Log.e(TAG,"屏幕高度:" + screenHeight);
                Log.e(TAG,"是否有状态栏:" + isStatusBarShown(context));
                Log.e(TAG,"状态栏的高度:" + getStatusBarHeight(context));
                Log.e(TAG,"是否有虚拟导航栏:" + hasVirtualNavigationBar(context));
                Log.e(TAG,"虚拟导航栏的高度:" + getNavigationBarHeight(context));

                //获得可见区域的大小
                Rect r = new Rect();
                et.getWindowVisibleDisplayFrame(r);
                Log.e(TAG,"可见区域的高度:" + r.height());
                Log.e(TAG,"可见区域的left:" + r.left);
                Log.e(TAG,"可见区域的top:" + r.top);
                Log.e(TAG,"可见区域的right:" + r.right);
                Log.e(TAG,"可见区域的bottom:" + r.bottom);

                int result = screenHeight;
                if (hasVirtualNavigationBar(context)){
                    result = screenHeight - getNavigationBarHeight(context);
                }
                if (isStatusBarShown(context)){
                    result = result - getStatusBarHeight(context);
                }
                result = result - r.height();
                Log.e(TAG,"软键盘的高度:" + result);

                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) btn.getLayoutParams();
                layoutParams.bottomMargin = result;
                btn.setLayoutParams(layoutParams);
            }
        });
    }

    public  boolean isStatusBarShown(Activity context) {
        WindowManager.LayoutParams params = context.getWindow().getAttributes();
        int paramsFlag = params.flags & (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
        return paramsFlag == params.flags;
    }

    /**
     * 获得状态栏的高度
     * @param context
     * @return
     */
    public int getStatusBarHeight(Context context){
        Class<?> c = null;
        Object obj = null;
        Field field = null;
        int x = 0, statusBarHeight = 0;
        try {
            c = Class.forName("com.android.internal.R$dimen");
            obj = c.newInstance();
            field = c.getField("status_bar_height");
            x = Integer.parseInt(field.get(obj).toString());
            statusBarHeight = context.getResources().getDimensionPixelSize(x);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        return statusBarHeight;
    }

    /**
     * 判断手机是否含有虚拟按键(底部导航栏)  99%
     */
    public  boolean hasVirtualNavigationBar(Context context) {
        boolean hasSoftwareKeys = true;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Display d = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

            DisplayMetrics realDisplayMetrics = new DisplayMetrics();
            d.getRealMetrics(realDisplayMetrics);

            int realHeight = realDisplayMetrics.heightPixels;
            int realWidth = realDisplayMetrics.widthPixels;

            DisplayMetrics displayMetrics = new DisplayMetrics();
            d.getMetrics(displayMetrics);

            int displayHeight = displayMetrics.heightPixels;
            int displayWidth = displayMetrics.widthPixels;

            hasSoftwareKeys = (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
            boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
            hasSoftwareKeys = !hasMenuKey && !hasBackKey;
        }

        return hasSoftwareKeys;
    }

    /**
     * 获取导航栏高度,有些没有虚拟导航栏的手机也能获取到,建议先判断是否有虚拟按键
     */
    public int getNavigationBarHeight(Context context) {
        int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        return resourceId > 0 ? context.getResources().getDimensionPixelSize(resourceId) : 0;
    }

}