转载请注明原创出处,谢谢!
- GitHub: @Ricco
主要看第二种需求
解决问题:
- 弹出软键盘后,ToolBar被挤上去
- 弹出软键盘后,ScrollView不能滑动
项目用到的技术:
- 软键盘监听,设置
- 对Activity的xml布局进行重绘
- 状态栏高度获取
感谢《Blankj/AndroidUtilCode》
感谢《android全屏/沉浸式状态栏下,各种键盘挡住输入框解决办法》
ps:最近在写一个界面,界面里面有非常多的EdItText,多到超出了手机的界面,当一个activity里要放的子控件比较多,就需要用都ScrollView,当用上ScrollView开始出现各种坑。比如弹出软键盘后,ToolBar被挤上去,ScrollView不能滑动等。
而需求一般分为俩种。
- 第一种:当EditText获得焦点的时候,弹出软键盘,当手指触摸非软键盘部分的时候,软键盘消失,开始上下滑动界面
这种实现只需要重写Activity的dispatchTouchEvent方法即可。
dispatchTouchEvent方法中的KeyboardUtils是使用AndroidUtilCode的KeyboardUtils.java中的方法。
/**
* 触摸事件
*/
@Override
public boolean dispatchTouchEvent(final MotionEvent ev) {
try {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
View v = getCurrentFocus();
if (KeyboardUtils.isShouldHideInput(v, ev)) {
KeyboardUtils.hideSoftInput(this);
}
return super.dispatchTouchEvent(ev);
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return onTouchEvent(ev);
}
- 第二种 如果你仔细观察第一种的界面效果,会发现,从一个EditText点击另一个EditText,软键盘会有明显的闪烁,这是由于,dispatchTouchEvent方法发现触摸会让软键盘消失,当EditText得到焦点的时候,软键盘又显示。所以造成了闪烁。
所以第二种的需求就是,滑动ScrollView的时候,让软键盘不会消失
如果你没有进行特殊的设置,会发现上面提到的俩个问题,当软键盘弹出的时候,Toolbar显示有问题,ScrollView不能向上滑动。所以我的代码主要解决这2个问题。
AndroidManifest.xml
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" />
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<!--状态栏-->
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="28dp"
android:background="#FFFFFF" />
<!--ToolBar-->
<include layout="@layout/tool_bar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!--默认获取焦点,不弹出软键盘-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:orientation="vertical">
<!--许许多多的其他控件,超过屏幕高度-->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
···
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
<!--提交按钮-->
<Button
android:id="@+id/button"
android:text="提交"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
// 省略findViewById
SoftHideKeyBoardUtil.assistActivity(this);
KeyboardUtils.registerSoftInputChangedListener(this, new KeyboardUtils.OnSoftInputChangedListener() {
@Override
public void onSoftInputChanged(int height) {
if (height > 0) {
button.setVisibility(View.GONE); // button是xml布局中的提交按钮
} else {
button.setVisibility(View.VISIBLE);
}
}
});
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
int statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
statusBarHeight = 0;
}
AppBarLayout.LayoutParams layoutParams = new AppBarLayout.LayoutParams(AppBarLayout.LayoutParams.MATCH_PARENT, statusBarHeight);
view.setLayoutParams(layoutParams); // view是xml布局中的状态栏
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
view.setBackgroundColor(Color.BLACK);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
KeyboardUtils.unregisterSoftInputChangedListener(this);
}
SoftHideKeyBoardUtil,基本上没有改变代码,只是加了状态栏高度的代码,原博客的代码,没有状态栏高度,一直是0。。。
/**
* 解决键盘档住输入框
*/
public class SoftHideKeyBoardUtil {
public static void assistActivity(Activity activity) {
new SoftHideKeyBoardUtil(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
// 为适应华为小米等手机键盘上方出现黑条或不适配
private int contentHeight; // 获取setContentView本来view的高度
private boolean isfirst = true; // 只用获取一次
private int statusBarHeight; // 状态栏高度
private SoftHideKeyBoardUtil(Activity activity) {
int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
statusBarHeight = 0;
}
// 1、找到Activity的最外层布局控件,它其实是一个DecorView,它所用的控件就是FrameLayout
FrameLayout content = activity.findViewById(android.R.id.content);
// 2、获取到setContentView放进去的View
mChildOfContent = content.getChildAt(0);
// 3、给Activity的xml布局设置View树监听,当布局有变化,如键盘弹出或收起时,都会回调此监听
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
// 4、软键盘弹起会使GlobalLayout发生变化
public void onGlobalLayout() {
if (isfirst) {
contentHeight = mChildOfContent.getHeight(); // 兼容华为,小米等机型
isfirst = false;
}
// 5、当前布局发生变化时,对Activity的xml布局进行重绘
possiblyResizeChildOfContent();
}
});
// 6、获取到Activity的xml布局的放置参数
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
// 获取界面可用高度,如果软键盘弹起后,Activity的xml布局可用高度需要减去键盘高度
private void possiblyResizeChildOfContent() {
// 1、获取当前界面可用高度,键盘弹起后,当前界面可用布局会减少键盘的高度
int usableHeightNow = computeUsableHeight();
// 2、如果当前可用高度和原始值不一样
if (usableHeightNow != usableHeightPrevious) {
// 3、获取Activity中xml中布局在当前界面显示的高度
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
// 4、Activity中xml布局的高度-当前可用高度
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
// 5、高度差大于屏幕1/4时,说明键盘弹出
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// 6、键盘弹出了,Activity的xml布局高度应当减去键盘高度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
} else {
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
}
} else {
frameLayoutParams.height = contentHeight;
}
// 7、 重绘Activity的xml布局
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
// 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度
return (r.bottom - r.top);
}
}
KeyboardUtils 专门为实现第二种简化的AndroidUtilCode
/**
* 软键盘监听
*/
public final class KeyboardUtils {
private static int sDecorViewInvisibleHeightPre;
private static OnGlobalLayoutListener onGlobalLayoutListener;
private static OnSoftInputChangedListener onSoftInputChangedListener;
private static int sDecorViewDelta = 0;
private KeyboardUtils() {
throw new UnsupportedOperationException("工具类,不要实例化。。。");
}
/**
* 注册软键盘监听
*/
public static void registerSoftInputChangedListener(final Activity activity, final OnSoftInputChangedListener listener) {
final int flags = activity.getWindow().getAttributes().flags;
if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
final FrameLayout contentView = activity.findViewById(android.R.id.content);
sDecorViewInvisibleHeightPre = getDecorViewInvisibleHeight(activity);
onSoftInputChangedListener = listener;
onGlobalLayoutListener = new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (onSoftInputChangedListener != null) {
int height = getDecorViewInvisibleHeight(activity);
if (sDecorViewInvisibleHeightPre != height) {
onSoftInputChangedListener.onSoftInputChanged(height);
sDecorViewInvisibleHeightPre = height;
}
}
}
};
contentView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
/**
* 取消软键盘监听
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public static void unregisterSoftInputChangedListener(final Activity activity) {
final View contentView = activity.findViewById(android.R.id.content);
contentView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
onSoftInputChangedListener = null;
onGlobalLayoutListener = null;
}
private static int getDecorViewInvisibleHeight(final Activity activity) {
final View decorView = activity.getWindow().getDecorView();
if (decorView == null) return sDecorViewInvisibleHeightPre;
final Rect outRect = new Rect();
decorView.getWindowVisibleDisplayFrame(outRect);
int delta = Math.abs(decorView.getBottom() - outRect.bottom);
if (delta <= getNavBarHeight()) {
sDecorViewDelta = delta;
return 0;
}
return delta - sDecorViewDelta;
}
private static int getNavBarHeight() {
Resources res = Resources.getSystem();
int resourceId = Resources.getSystem().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId != 0) {
return res.getDimensionPixelSize(resourceId);
} else {
return 0;
}
}
///////////////////////////////////////////////////////////////////////////
// interface //
///////////////////////////////////////////////////////////////////////////
public interface OnSoftInputChangedListener {
// 如果软键盘是关闭状态,height为0,软键盘弹出状态,height为软键盘高度
void onSoftInputChanged(int height);
}
}
为什么需要监听软键盘?
当我们打开局面的时候,activity的底部,应该显示提交按钮,或者其他控件,这些View是定死在页面底部的。当软键盘显示的时候,提交按钮或者其他控件应该做一个view.setVisibility(View.GONE)操作,放置提交按钮或者其他控件,显示到软键盘上面。
如果你想软键盘弹出的时候,底部的提交按钮或者其他控件不显示在软键盘上面,还要可以随着ScrollView的向下滑动滑出底部的提交按钮或者其他控件,你就需要在ScrollView中再写一套提交按钮或者其他控件。
也就是所页面中同时存在2套提交按钮或者其他控件,一套在ScrollView外面,用于当用户第一次打开的时候,给用户一个直观交互,另一套在ScrollView里面,用于当用户点击EditText的时候,向下滑动ScrollView的时候,显示提交按钮或者其他控件,从而造成一个提交按钮或者其他控件一直都在的效果。
所以这就是需要监听软键盘打开关闭的原因。在默认状态下,ScrollView外面的提交按钮或者其他控件显示,里面的隐藏,当软键盘打开的时候,ScrollView外面的提交按钮或者其他控件隐藏,而外面的显示。
要注意的是,为了解决问题,我们对Activity的xml布局进行了重绘,所以布局可能出现一些小的偏差。
网友评论