解決ScrollView與軟鍵盤與Toolbar與EditText的問題,ToolBar被擠上去,ScrollView不能滑動
轉載請註明原創出處,謝謝!
主要看第二種需求
解決問題:
- 彈出軟鍵盤後,ToolBar被擠上去
- 彈出軟鍵盤後,ScrollView不能滑動
專案用到的技術:
- 軟鍵盤監聽,設定
- 對Activity的xml佈局進行重繪
- 狀態列高度獲取
感謝《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佈局進行了重繪,所以佈局可能出現一些小的偏差。