API 場景:#Activity \ #AlertDialog \ #PopupWindow \ ...
getWindow().setSoftInputMode(int flag {@link WindowManager.LayoutParams.#softInputMode));
1.SOFT_INPUT_ADJUST_PAN
系統會選擇一種合理的方式平移window,使得獲取焦點的輸入框可見,不會改變布局大小。
2.SOFT_INPUT_ADJUST_RESIZE
軟鍵盤彈出時window大小可被調整,使得可見內容不被覆蓋,該屬性與SOFT_INPUT_ADJUST_PAN互斥,不能同時使用。
注:API中指出,window添加屬性中存在 {@link #WindowManager.LayoutParams.# FLAG_FULLSCREEN}情況,
系統會忽略SOFT_INPUT_ADJUST_RESIZE屬性,解決方案見下文。
3.SOFT_INPUT_ADJUST_NOTHING
軟鍵盤彈出時window不被平移也不會改變大小。
4.SOFT_INPUT_ADJUST_UNSPECIFIED
系統會根據window內容選擇一種顯示方式,一般不推薦,不同的Android版本可能存在差異,會導致頁面兼容性問題。
2.問題場景 & 解決方案
1.Android文本可編輯控件<EditText>場景,例如 評論、登錄、個人中心 ...
推薦使用# SOFT_INPUT_ADJUST_PAN模式,系統根據當前焦點文本框的位置平移window,交互體驗較好。
2.WebView應用場景
- a. WebView場景中由Android文本編輯控件<EditText>觸發軟鍵盤,此時與場景1沒有區別,使用#SOFT_INPUT_ADJUST_PAN模式;
- b. WebView場景中由H5元素觸發軟鍵盤,此時使用#SOFT_INPUT_ADJUST_RESIZE模式,使得window改變大小,保證WebView內容不被遮擋。
3.全屏窗口使用場景<#FLAG_FULLSCREEN>
FULLSCREEN效果是全屏顯示(SystemUI StatusBar 會被置於底層,activity的window置於上層),此時如果涉及軟鍵盤彈出遮擋問題則不能使用SOFT_INPUT_ADJUST_RESIZE方式開解決。
3. 輕框 webview 解決方案
3.1 背景:
目前線上 webview 里的 H5 點擊輸入框沒有反應,因為webview被放在全屏模式下了。但是你也不可能去更改模式,這樣影響會比較大。目前就是需要在保證業務邏輯不受影響的情況下去,如何讓輕框彈出軟鍵盤。
3.2 解決方案:
百度一搜會發現很多webview都出現了類似問題,但是最早的問題可以見 WebView adjustResize windowSoftInputMode breaks when activity is fullscreen 一文,這是開發者最早在 android 源碼上提出來的bug,不過bug級別為 P3,一直也沒有給解決。
在 stackoverflow 上也有用戶提出了問題,Android How to adjust layout in Full Screen Mode when softkeyboard is visible,文章后面有人給出了答案:
public class AndroidBug5497Workaround {
// For more information, see https://issuetracker.google.com/issues/36911528
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity (Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}
使用方式是在對應的 activity 頁面的 onCreate 中進行調用即可:
AndroidBug5497Workaround.assistActivity(this);
3.3 方案應用
看到解決方案后,內心狂喜,就這么簡單啊。開心的將其copy到項目中,編譯,安裝,啟動。打開用輕框承載的頁面,激動的點下,鍵盤彈起來了。哇塞,問題解決了。多次幾次后發現還是存在一些問題:
- 鍵盤彈起后,落下的時候,可以見到底部有一塊黑色陰影,這個是很影響用戶體驗的;
3.4 完善方案
問題1
針對3.3中的問題1,猜測是失去焦點了,導致沒有把焦點的 view 平移出來。可是為啥有時候有問題,有時候沒問題呢,不覺得這個是很詭異的事情嗎?
於是又開始瘋狂百度和Google,結果發現竟然沒有人跟我有一樣的遭遇。這,為何只有我需要承受這種傷害。最終在經歷了兩天多的各種嘗試和自我懷疑后,突然想到我現在使用的是T7內核,那我如果不使用呢?很快,我在 debug 模式下禁用了 T7 內核,使用原生內核,在嘗試后,竟然沒有問題了,喜大普奔啊。於是,我將問題反饋給內核同學,后來內核同學發現代碼在處理焦點邏輯的時候存在問題,但是具體解決方案短期還是無法給出。
問題2
針對3.3中的問題2,之所以會出現黑色背景,是因為我們將 contentview 的大小給改變了,導致漏出來的黑色背景。解決這個問題有兩種方式
- 一種是設置背景顏色,但是這樣的話,代碼使用上有點打折扣。
- 另一種方案是只改變 webview 外層容器 container 的高度,這樣即使container高度變了, 還是有背景的。
問題3
其實這個問題跟問題2的第二種解法類似,就是改變 webview 外層容器 container 的高度,不要去更改包含返回條的view即可;
問題4
解決上述三個問題后,提測。
QA測試中又發現一個新的問題,在部分屏幕高度有限的手機中,底部的舉報按鈕被返回條遮擋了,但是無法滑動。
如上圖所示,可以看到底部的提交按鈕被返回條遮擋了,並且頁面不能滑動。
那么頁面為啥不能滑動了呢?
原因:當你用網上給的解決方案修改內層view的時候,最后是將當前window的可見高度賦給了內層view,這個高度和其原始高度並不一定是相等的。當你多給了一些高度,會讓原本不可見的內容變成了可見,於是view就變成不可滑動的呢。
解決這個問題的方案也很簡單,那就是恢復其原來的高度。
- 第一種修復方案是,當鍵盤收起之后,將 view 的高度變成 ViewGroup.LayoutParams.MATCH_PARENT,這時候會觸發重新測量等邏輯。
frameLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
但是這種方案不推薦就是這么改可能會導致繪制次數變多,影響頁面性能;
2、第二種修復方案是,在鍵盤彈起的時候,記錄此時需要改變的 container 的高度,在鍵盤收起后,恢復原始高度即可。
到這里,應該是比較完美了。
但是這里還有個問題,就是他會監聽每次繪制,然后觸發再次繪制,可能會導致線上繪制次數變多。好的辦法就是只有在鍵盤彈起和收起之間去做這個操作。
/** 屏幕高度比例,0.85來源{@link AbsBdFrameView} */
private static final float RATE = 0.85f;
/** 上一次的可見高度 */
private int mLastVisibleHeight;
/** 保存視圖鍵盤未彈出時高度值 */
private int mViewHeight;
/** 布局參數 */
private ViewGroup.LayoutParams mParams;
/** OnGlobalLayoutListener */
private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener;
/** rootView */
private ViewGroup mRootView;
/** Resume 生命周期狀態碼 */
private boolean mIsResuming;
public KeyBoardWorkaround(@NonNull ViewGroup rootView) {
mRootView = rootView;
addOnGlobalLayoutListener();
}
/**
* 添加layout回調
*/
private void addOnGlobalLayoutListener() {
mOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
resizeRootView();
}
};
mRootView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
mParams = mRootView.getLayoutParams();
}
/**
* 調整rootView的大小
*/
private void resizeRootView() {
// 屏幕的高度 * 0.85得到一個處於輸入法彈起后頁面可見的高度(0.85來源{@link AbsBdFrameView})
int hasKeyboardHeight = (int) (DeviceUtils.ScreenInfo.getRealScreenHeight(mRootView.getContext()) * RATE);
// webView + 輸入法的狀態下,保證恢復高度的layout不會被過濾掉而出現白屏問題{@link AbsBdFrameView}
if (mLastVisibleHeight >= hasKeyboardHeight && !mIsResuming) {
return;
}
Rect r = new Rect();
// 這里獲取的是window的可見高度,而不是mRootView的可見高度
mRootView.getWindowVisibleDisplayFrame(r);
int displayHeight = r.bottom - r.top;
int rootViewHeight = mRootView.getHeight();
// view可見高度&尺寸均大於鍵盤彈出時的高度,說明此時無鍵盤彈出和收起情況,直接返回
if (Math.min(displayHeight, rootViewHeight) > hasKeyboardHeight) {
return;
}
// 鍵盤狀態變更
if (displayHeight != rootViewHeight && displayHeight > 0 && rootViewHeight > 0) {
// 該條件為true時表示鍵盤彈出
if (displayHeight < hasKeyboardHeight) {
mParams.height = displayHeight;
// 記錄view原始高度
mViewHeight = rootViewHeight;
} else {
mParams.height = mViewHeight;
}
mLastVisibleHeight = mParams.height;
mRootView.requestLayout();
}
}
到這里,關於webview軟鍵盤的問題就解決了。方案經過多次優化后,已經變得比較完美了。