今天遇到一个特别棘手的问题,android 内部原生嵌套webview h5页面时,软键盘被遮挡问题,苦寻半天,找不到是我这边的问题,还是前端的问题,最后这个网址在google浏览器打开,并且打开软键盘,键盘顶起是正常的先上效果图:
原始的位置:
正常不处理时软键盘弹起:
处理后软键盘弹起效果如下
在确定了问题是我的原因之后,开始找答案:
找到了一个大牛写的
AndroidBug5497Workaround
public class AndroidBug5497Workaround {
public static void assistActivity(Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private int contentHeight;
private boolean isfirst = true;
private Activity activity;
private int statusBarHeight;
private AndroidBug5497Workaround(Activity activity) {
//获取状态栏的高度
int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
this.activity = activity;
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
//界面出现变动都会调用这个监听事件
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (isfirst) {
contentHeight = mChildOfContent.getHeight();//兼容华为等机型
isfirst = false;
}
possiblyResizeChildOfContent();
}
});
frameLayoutParams = (FrameLayout.LayoutParams)
mChildOfContent.getLayoutParams();
}
//重新调整跟布局的高度
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
//当前可见高度和上一次可见高度不一致 布局变动
if (usableHeightNow != usableHeightPrevious) {
//int usableHeightSansKeyboard2 = mChildOfContent.getHeight();//兼容华为等机型
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
} else {
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
}
} else {
frameLayoutParams.height = contentHeight;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
/**
* 计算mChildOfContent可见高度 ** @return
*/
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}
用了之后发现问题,在一些虚拟键盘手机上,虚拟键会遮挡下面的布局
最后在上面进行了改良
kotlin代码
class GlobalLayoutUtils(activity: Activity, private var isImmersed: Boolean = true) {
// 当前界面根布局,就是我们设置的 setContentView()
private var mChildOfContent: View
private var frameLayoutParams: FrameLayout.LayoutParams
// 变化前的试图高度
private var usableHeightPrevious = 0
init {
val content: FrameLayout = activity.findViewById(android.R.id.content)
mChildOfContent = content.getChildAt(0)
// 添加布局变化监听
mChildOfContent.viewTreeObserver.addOnGlobalLayoutListener {
possiblyResizeChildOfContent(activity)
}
frameLayoutParams = mChildOfContent.layoutParams as FrameLayout.LayoutParams
}
private fun possiblyResizeChildOfContent(activity: Activity) {
// 当前可视区域的高度
val usableHeightNow = computeUsableHeight()
// 当前高度值和之前的进行对比,变化将进行重绘
if (usableHeightNow != usableHeightPrevious) {
// 获取当前屏幕高度
// Ps:并不是真正的屏幕高度,是当前app的窗口高度,分屏时的高度为分屏窗口高度
var usableHeightSansKeyboard = mChildOfContent.rootView.height
// 高度差值:屏幕高度 - 可视内容高度
var heightDifference = usableHeightSansKeyboard - usableHeightNow
// 差值为负,说明获取屏幕高度时出错,宽高状态值反了,重新计算
if (heightDifference < 0) {
usableHeightSansKeyboard = mChildOfContent.rootView.width
heightDifference = usableHeightSansKeyboard - usableHeightNow
}
when {
// keyboard probably just became visible
// 如果差值大于屏幕高度的 1/4,则认为输入软键盘为弹出状态
heightDifference > usableHeightSansKeyboard / 4 ->
// 设置布局高度为:屏幕高度 - 高度差
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference
// keyboard probably just became hidden
// 虚拟导航栏显示
heightDifference >= getNavigationBarHeight(activity) ->
frameLayoutParams.height = usableHeightSansKeyboard - getNavigationBarHeight(activity)
// 其他情况直接设置为可视高度即可
else -> frameLayoutParams.height = usableHeightNow
}
}
// 刷新布局,会重新测量、绘制
mChildOfContent.requestLayout()
// 保存高度信息
usableHeightPrevious = usableHeightNow
}
/**
* 获取可视内容区域的高度
*/
private fun computeUsableHeight(): Int {
val r = Rect()
// 当前窗口可视区域,不包括通知栏、导航栏、输入键盘区域
mChildOfContent.getWindowVisibleDisplayFrame(r)
return if (isImmersed) {
// 沉浸模式下,底部坐标就是内容有效高度
r.bottom
} else {
// 非沉浸模式下,去掉通知栏的高度 r.top(可用于通知栏高度的计算)
r.bottom - r.top
}
}
// 获取导航栏真实的高度(可能未显示)
private fun getNavigationBarHeight(context: Context): Int {
var result = 0
val resources = context.resources
val resourceId =
resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
result = resources.getDimensionPixelSize(resourceId)
}
return result
}
}
java代码
public class GlobalLayoutUtil {
private Activity activity;
private boolean isImmersed = false;
private View mChildOfContent;
private FrameLayout.LayoutParams frameLayoutParams;
private int usableHeightPrevious = 0;
public GlobalLayoutUtil(Activity activity) {
this.activity = activity;
init();
}
public void init() {
FrameLayout content = activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
//添加布局变化监听
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
possiblyResizeChildOfContent(activity);
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent(Activity activity) {
//当前可视区域的高度
int usableHeightNow = computeUsableHeight();
//当前高度值和之前的进行对比,变化将进行重新绘制
if (usableHeightNow != usableHeightPrevious) {
//获取当前屏幕高度
//Ps: 并不是真正的屏幕高度,是当前app的窗口高度,分屏时的高度为分屏窗口高度
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
// 高度差值:屏幕高度 - 可视内容高度
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
// 差值为负,说明获取屏幕高度时出错,宽高状态值反了,重新计算
if (heightDifference < 0) {
usableHeightSansKeyboard = mChildOfContent.getRootView().getWidth();
heightDifference = usableHeightSansKeyboard - usableHeightNow;
}
// keyboard probably just became visible
// 如果差值大于屏幕高度的 1/4,则认为输入软键盘为弹出状态
if (heightDifference > usableHeightSansKeyboard / 4) {
// 设置布局高度为:屏幕高度 - 高度差
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else if (heightDifference >= getNavigationBarHeight(activity)) {
// keyboard probably just became hidden
// 虚拟导航栏显示
frameLayoutParams.height = usableHeightSansKeyboard - getNavigationBarHeight(activity);
} else {// 其他情况直接设置为可视高度即可
frameLayoutParams.height = usableHeightNow;
}
}
// 刷新布局,会重新测量、绘制
mChildOfContent.requestLayout();
// 保存高度信息
usableHeightPrevious = usableHeightNow;
}
/**
* 获取可视内容区域的高度
*/
private int computeUsableHeight() {
Rect rect = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(rect);
if (isImmersed)
return rect.bottom;
else
return rect.bottom - rect.top;
}
/**
* 获取导航栏的真实高度
*
* @param context:
* @return: 导航栏高度
*/
private int getNavigationBarHeight(Context context) {
int result = 0;
Resources resources = context.getResources();
int resourceId =
resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0)
result = resources.getDimensionPixelSize(resourceId);
return result;
}
}
使用方法,将webview的activity对象传入即可
GlobalLayoutUtil(this);
如果遇到页面被底部导航栏覆盖,将webview的父布局设置撑满整个window
android:fitsSystemWindows="true"