Android EditText被软键盘遮挡的解决办法
1. 问题描述
最近实现一个效果的时候遇到了一些问题,布局很简单,就是一个EditText、周围一些ImageView之类的小控件。布局很快写完了,问题主要出在软键盘弹出时UI会往上推移,此时输入框会被挡住一部分。
以简易布局还原一下情况:
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden">
</activity>
<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:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/splitView"
android:background="#CC000000" />
<View
android:id="@+id/splitView"
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_alignParentBottom="true"
android:background="#0DABA0" />
</RelativeLayout>
初始效果图如下:
点击EditText,软键盘弹出后效果如下,很明显可见,editText有一部分区域被遮挡住了,骚气的底部view也被挡到了。
2. 失败尝试
这个问题困扰了我一天的时间,看网上blog,有一些是建议修改AndroidManifest.xml中windowSoftInputMode属性的值。
adjustResize adjustPan都尝试过,但是没有效果。
在上述演示项目中,将windowSoftInputMode设置为adjustResize,倒是能使editText完全可见,但不适用于我需求中的情况。
后续又想到,editText被遮挡,我可以获取软键盘的高度,给editText设置一个margin使其显示在
软键盘之上呀。软键盘弹出时会导致ViewTree发生变化,影响Activity对应Windwow的大小,因此可以给Activity设置ViewTreeObserver监听,计算出软键盘高度。
private int windowHeight;
private final ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect rect = new Rect();
//获取当前窗口实际的可见区域
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
if (windowHeight == 0) {
windowHeight = rect.height();
} else {
// 说明当前window大小发生变化
if (windowHeight != rect.height()) {
int keyboardHeight = windowHeight - rect.height();
Log.e(TAG, "onGlobalLayout: " + keyboardHeight);
}
}
}
};
// onCreate方法中添加监听
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("===", "onCreate: ");
r = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(r.getRoot());
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
initView();
initListen();
}
// onDestory方法中移除监听,防止内存泄漏
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("===", "onDestroy: ");
getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);
}
当然,这只是一个简易的方法,能引起window大小变化的不止软键盘的弹出。不管怎么说,终于拿到了软键盘的高度,这下子能解决editText被遮挡的问题了吧?兴高采烈的去尝试,结果发现还是不行。
前面已经说了,软键盘弹出时默认情况下会推动UI向上移动,如果在软键盘弹出时给editText设置等同于键盘高度的底部margin,editText位置会突变。将windowSoftInputMode设置为adjustNothing可以禁止页面UI上移,但此时监听同样不会触发。因为属性值设置为adjustNothing时,软键盘弹出,Activity window的大小并不会受到影响。
3. 解决方法
最后,通过查阅各种博客,终于使用DialogFragment解决了这个问题,网上关于DialogFragment的教程也比较多,大家可以自行检索查阅。
最终效果如下:(终于是能够正常显示了,泪目)
相关代码如下:
<style name="DialogStyle" parent="Base.Theme.AppCompat.Dialog">
<item name="android:windowBackground">@null</item>
<item name="android:windowMinWidthMajor">97%</item>
<item name="android:windowMinWidthMinor">97%</item>
<item name="android:windowSoftInputMode">stateAlwaysVisible</item>
</style>
public class TestDialogFragment extends DialogFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.DialogStyle);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Window window = getDialog().getWindow();
// 解决未全屏显示的问题
window.getDecorView().setPadding(0, 0, 0, 0);
WindowManager.LayoutParams wlp = window.getAttributes();
wlp.gravity = Gravity.BOTTOM;
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
return inflater.inflate(R.layout.dialog_test, container);
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
}
}
dialog_test.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_above="@id/splitView"
android:background="#CC000000"
android:inputType="text"
android:imeOptions="actionDone"/>
<View
android:id="@+id/splitView"
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_alignParentBottom="true"
android:background="#0DABA0" />
</RelativeLayout>
需要注意的是,使用DialogFragment 有内存泄漏的危险,看到一个帖子这样设置可以避免内存泄漏,是否有效有待验证:
使用时:
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.add(TabsFragment.newInstance(), "Fragment");
ft.addToBackStack(null);
ft.commit();
重写DialogFragment onDismiss方法:
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (fragmentManager.getBackStackEntryCount() > 0)
fragmentManager.popBackStack();
fragmentTransaction.commit();
}
最后,值得一提的是,在DialogFragment中,软键盘的弹出和收起逻辑略有不同,在此给出我实现弹出和隐藏时有效的方法:
private void openKeyboard() {
InputMethodManager imm =
(InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null)
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
}
private void closeKeyboard() {
InputMethodManager imm =
(InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}
}
可通过设置setCancelable(true)实现点击其他区域隐藏dialogFragment。
4. 总结
本文归根结底,也只是找到了一种解决editText被遮挡的方法,博主也不清楚涉及到的底层原理,有大佬有所了解的可以补充一下,不胜感激。最后,贴一下TestDialogFragment的完整代码,希望能帮到大家。
package com.example.customview.view;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.example.customview.R;
public class TestDialogFragment extends DialogFragment {
private EditText editText;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.DialogStyle);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
setCancelable(true);
Window window = getDialog().getWindow();
// 解决未全屏显示的问题
window.getDecorView().setPadding(0, 0, 0, 0);
WindowManager.LayoutParams wlp = window.getAttributes();
wlp.gravity = Gravity.BOTTOM;
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
View view = inflater.inflate(R.layout.dialog_test, container);
editText = view.findViewById(R.id.editText);
editText.requestFocus();
openKeyboard();
getDialog().setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// TODO 取消监听
}
});
return view;
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
closeKeyboard();
super.onDismiss(dialog);
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (fragmentManager.getBackStackEntryCount() > 0)
fragmentManager.popBackStack();
fragmentTransaction.commit();
}
private void openKeyboard() {
InputMethodManager imm =
(InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null)
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
}
private void closeKeyboard() {
InputMethodManager imm =
(InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}
}
}