輕框 webView 支持軟鍵盤


 1.常用軟鍵盤模式API

  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到項目中,編譯,安裝,啟動。打開用輕框承載的頁面,激動的點下,鍵盤彈起來了。哇塞,問題解決了。多次幾次后發現還是存在一些問題:

  1. 有時候頁面無法滾動起來;
  1. 鍵盤彈起后,落下的時候,可以見到底部有一塊黑色陰影,這個是很影響用戶體驗的;
  1. 目前的修改方案將返回條也頂上來了;
 

3.4 完善方案

問題1

針對3.3中的問題1,猜測是失去焦點了,導致沒有把焦點的 view 平移出來。可是為啥有時候有問題,有時候沒問題呢,不覺得這個是很詭異的事情嗎?

於是又開始瘋狂百度和Google,結果發現竟然沒有人跟我有一樣的遭遇。這,為何只有我需要承受這種傷害。最終在經歷了兩天多的各種嘗試和自我懷疑后,突然想到我現在使用的是T7內核,那我如果不使用呢?很快,我在 debug 模式下禁用了 T7 內核,使用原生內核,在嘗試后,竟然沒有問題了,喜大普奔啊。於是,我將問題反饋給內核同學,后來內核同學發現代碼在處理焦點邏輯的時候存在問題,但是具體解決方案短期還是無法給出。

問題2

針對3.3中的問題2,之所以會出現黑色背景,是因為我們將 contentview 的大小給改變了,導致漏出來的黑色背景。解決這個問題有兩種方式

  1. 一種是設置背景顏色,但是這樣的話,代碼使用上有點打折扣。
  1. 另一種方案是只改變 webview 外層容器 container 的高度,這樣即使container高度變了, 還是有背景的。

問題3

其實這個問題跟問題2的第二種解法類似,就是改變 webview 外層容器 container 的高度,不要去更改包含返回條的view即可;

問題4

解決上述三個問題后,提測。

QA測試中又發現一個新的問題,在部分屏幕高度有限的手機中,底部的舉報按鈕被返回條遮擋了,但是無法滑動。

 

如上圖所示,可以看到底部的提交按鈕被返回條遮擋了,並且頁面不能滑動。

那么頁面為啥不能滑動了呢?

原因:當你用網上給的解決方案修改內層view的時候,最后是將當前window的可見高度賦給了內層view,這個高度和其原始高度並不一定是相等的。當你多給了一些高度,會讓原本不可見的內容變成了可見,於是view就變成不可滑動的呢。

解決這個問題的方案也很簡單,那就是恢復其原來的高度。

  1. 第一種修復方案是,當鍵盤收起之后,將 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軟鍵盤的問題就解決了。方案經過多次優化后,已經變得比較完美了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM