背景
最近在做一个笔记相关的项目,涉及到输入法与页面布局间的切换,以前最多就是控制输入法显示隐藏,所以在做的过程中遇到一些闪屏的问题,在此记录一下。
如图,在输入法上方悬浮一个tab栏,在点击切换字体时会出现闪屏。看起来很影响视觉,说好的无缝切换呢?在说解决方法之前先说一下键盘和布局之间的一些交互。
SoftInputMode
软键盘弹出可能会遮挡住activity中的EditView或者一部分界面,我们经常会在AndroidManifest.xml 中指定android:windowSoftInputMode="stateVisible|adjustPan"来解决
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
或者可以在activity中设置setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
}
}
让activity布局上移整体,使EditView显示出来。windowSoftInputMode常用属性:
- adjustPan:Activity 窗口(DecorView) 大小不变。当获取到焦点的 EditText 位于屏幕下方,软键盘弹出会遮挡到 EditText 时,整个 DecorView 会往上移动,让EditText显示出来
- adjustResize:Activity 窗口会自动调整大小,留出键盘的显示空间。在这种模式下我们能获取到软键盘的高度,软键盘弹出后 contentView 高度变化的差值即为软键盘高度
- adjustNothing:Activity 窗口不做任何调整,软键盘直接显示在Activity上面
- adjustUnspecified:默认设置,通常由系统自行决定是隐藏还是显示。
- stateAlwaysHidden:当该Activity主窗口获取焦点时,软键盘总是被隐藏的。
- stateAlwaysVisible:用户选择Activity时,软键盘总是显示的状态。
- stateUnspecified:软键盘的状态并没有指定,系统将选择一个合适的状态或依赖于主题的设置。
- stateUnchanged:当这个Activity出现时,软键盘将一直保持在上一个Activity里的状态,无论是隐藏还是显示。
- stateHidden:用户选择Activity时,软键盘总是被隐藏。
- stateVisible:软键盘通常是可见的
为了实现图中tab栏的悬浮效果我们需要设置setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
获取键盘高度
设置adjustResize后,在切换时,键盘高度和我们布局高度不一致,会造成布局上下跳动的问题,所以需要获取软键盘的高度,把软键盘的高度设置成布局的最小高度setMinimumHeight,这样就可以避免这个问题。
public class SoftKeyBoardListener {
/**
* activity的根视图
*/
private View rootView;
/**
* 纪录根视图当前的显示高度
*/
private int rootViewVisibleHeight;
/**
* 纪录根视图未显示软键盘之前高度
*/
private int rootHeight;
private OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener;
private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//获取当前根视图在屏幕上显示的大小
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int visibleHeight = r.height();
System.out.println("" + visibleHeight);
if (rootViewVisibleHeight == 0) {
rootViewVisibleHeight = visibleHeight;
rootHeight = visibleHeight;
return;
}
if (rootHeight != visibleHeight) {
onSoftKeyBoardChangeListener.keyBoardHeightChange(rootHeight - visibleHeight);
}
//根视图显示高度没有变化,可以看作软键盘显示/隐藏状态没有改变
if (rootViewVisibleHeight == visibleHeight) {
return;
}
//根视图显示高度变大超过200,可以看作软键盘显示了
if (rootViewVisibleHeight - visibleHeight > 200) {
if (onSoftKeyBoardChangeListener != null) {
onSoftKeyBoardChangeListener.keyBoardShow();
}
rootViewVisibleHeight = visibleHeight;
return;
}
//根视图显示高度变大超过200,可以看作软键盘隐藏了
if (visibleHeight - rootViewVisibleHeight > 200) {
rootViewVisibleHeight = visibleHeight;
//这里做个兼容,有的键盘内部切换布局时会出现高度减少的情况,但软键盘并未隐藏,需要排除这种情况
if (rootHeight - rootViewVisibleHeight < 200) {
if (onSoftKeyBoardChangeListener != null) {
onSoftKeyBoardChangeListener.keyBoardHide();
}
}
}
}
};
public SoftKeyBoardListener(Activity activity) {
//获取activity的根视图
rootView = activity.getWindow().getDecorView();
//监听视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变
rootView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
}
public void setOnSoftKeyBoardChangeListener(OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {
this.onSoftKeyBoardChangeListener = onSoftKeyBoardChangeListener;
}
public void destroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
rootView.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);
}
rootView = null;
}
public interface OnSoftKeyBoardChangeListener {
/**
* 键盘显示
*/
void keyBoardShow();
/**
* 键盘隐藏
*/
void keyBoardHide();
/**
* 键盘高度变化
*/
void keyBoardHeightChange(int height);
}
}
使用
softKeyBoardListener = new SoftKeyBoardListener(this);
softKeyBoardListener.setOnSoftKeyBoardChangeListener(new SoftKeyBoardListener.OnSoftKeyBoardChangeListener() {
@Override
public void keyBoardShow(int height) {
//键盘显示,设置选中Tab
viewModel.currentPageIndex.set(NoteEditViewModel.INDEX_KEYBOARD);
softKeyBoardShowed = true;
}
@Override
public void keyBoardHide(int height) {
//键盘隐藏,如果当前选择的是键盘tab,设置选中无效
if (viewModel.currentPageIndex.get() == NoteEditViewModel.INDEX_KEYBOARD) {
viewModel.currentPageIndex.set(NoteEditViewModel.INDEX_INVALID);
}
softKeyBoardShowed = false;
}
@Override
public void keyBoardHeightChange(int height) {
if (softKeyBoardShowed) {
keyBoardHeight = height;
}
}
});
闪屏
终于到本文的重点,像开头说的,在从键盘切换到字体栏时布局会闪烁一下。造成这种问题的原因是,在显示字体栏时,输入法还没消失,因此字体栏会出现在输入法上面,当输入法消失时,字体栏的位置又被重新调整到底部,因此会造成布局闪烁。解决的办法就是动态设置setSoftInputMode
// 设置输入法弹起时自动调整布局,使之在输入法之上
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// 设置输入法弹起时不调整当前布局
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
在初始化状态我们还是要设置SOFT_INPUT_ADJUST_RESIZE
public void initParam() {
super.initParam();
// 起初的布局可自动调整大小
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
切换字体栏
private void switchTab(ViewStubProxy stubProxy) {
...
//设置SoftInputMode为adjustNothing
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
//设置字体栏最小高度为键盘高度
binding.bottomContent.setMinimumHeight(keyBoardHeight);
//显示字体栏
changeMenuVisibility(view);
//隐藏软键盘
showSoftInputFromWindow(false);
}
显示键盘
private void showSoftInputFromWindow(boolean show) {
//获取当前EidtText
final EditText editText = binding.richTextEditor.getCurrentEditText();
if (show) {
//显示软键盘
Utils.showSoftInputFromWindow(editText);
//延迟250毫秒让键盘完全,再调整布局随软键盘自动调整大小,并隐藏菜单界面,这两个动作要
//一起不然也会出现闪烁
handler.postDelayed(new Runnable() {
@Override
public void run() {
//重置布局的最小显示高度
binding.bottomContent.setMinimumHeight(0);
//隐藏字体栏
changeMenuVisibility(null);
//调整布局随软键盘自动调整大小
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
}, 250);
} else {
Utils.hideSoftKeyboard(this, false);
}
}
附上键盘显示、隐藏的代码
public static void showSoftInputFromWindow(EditText editText) {
editText.setFocusable(true);
editText.setFocusableInTouchMode(true);
editText.requestFocus();
InputMethodManager inputManager =
(InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInput(editText, 0);
}
public static void hideSoftKeyboard(Activity activity) {
hideSoftKeyboard(activity, true);
}
public static void hideSoftKeyboard(Activity activity, boolean clearFocus) {
View view = activity.getCurrentFocus();
if (view != null) {
InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
if (clearFocus) {
view.clearFocus();
}
}
}
最后效果