一、需求
效果图
文字说明
当软键盘弹出的时候,希望底部的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;
}
}