FR9.0 Android虛擬鍵的適配

????9.0App的時(shí)候很多布局的大小都是通過(guò)計(jì)算屏幕寬高煤辨、狀態(tài)欄高度裳涛、工具欄高度等計(jì)算得到的,這種計(jì)算方法在可動(dòng)態(tài)顯示或隱藏虛擬按鍵的Android手機(jī)的顯示上會(huì)有問(wèn)題众辨,最典型的有以下情況:

1.虛擬鍵顯示時(shí)端三,打開(kāi)全屏界面(如掃碼、批注等界面)鹃彻,隱藏虛擬鍵未填充界面郊闯,會(huì)留白。
隱藏虛擬鍵留白

隱藏虛擬鍵未填充屏幕
2.先隱藏虛擬鍵再展開(kāi)時(shí),app底部或者右側(cè)可能會(huì)被遮擋团赁。
頁(yè)面被遮擋

????一般采用自適應(yīng)寬高的View不受影響育拨,比如設(shè)置flex:1,這類(lèi)視圖會(huì)根據(jù)屏幕有效區(qū)域自動(dòng)刷新欢摄。
????但某些布局會(huì)有特殊要求熬丧,使用具體的寬高作為屬性,就需要考慮到虛擬鍵的因素怀挠。

解決方法

解決思路:

  • 首先原生監(jiān)聽(tīng)虛擬按鍵的變化析蝴,計(jì)算出去除虛擬鍵尺寸后屏幕的可用寬高,然后將寬高發(fā)送給RN端绿淋。
  • RN端更新實(shí)際可用的屏幕尺寸闷畸,最后去通知各個(gè)需要適配虛擬鍵的頁(yè)面進(jìn)行更新。


    去除虛擬鍵的區(qū)域
  1. 獲得實(shí)際虛擬鍵的高度
/**
     * 獲取虛擬鍵高度(無(wú)論是否隱藏)
     * @param context
     * @return
     */
    public static int getNavigationBarHeight(Context context){
        int result = 0;
        int resourceId = context.getResources().getIdentifier("navigation_bar_height","dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
/**
     * 虛擬按鍵是否打開(kāi)
     * @param activity
     * @return
     */
    public static boolean isNavigationBarShown(Activity activity){
        //虛擬鍵的view,為空或者不可見(jiàn)時(shí)是隱藏狀態(tài)
        View view  = activity.findViewById(android.R.id.navigationBarBackground);
        if(view == null){
            return false;
        }
        int visible = view.getVisibility();
        if(visible == View.GONE || visible == View.INVISIBLE){
            return false ;
        }else{
            return true;
        }
    }
/**
     * 獲取當(dāng)前虛擬鍵高度(隱藏后高度為0)
     * @param activity
     * @return
     */
    public static int getCurrentNavigationBarHeight(Activity activity){
        if(isNavigationBarShown(activity)){
            return getNavigationBarHeight(activity);
        }else{
            return 0;
        }
    }
  1. 計(jì)算屏幕可用寬高(去除虛擬鍵顯示高度)
/**
     * 獲取可用屏幕高度躬它,排除虛擬鍵
     * @param context 上下文
     * @return 返回高度
     */
    public static int getContentHeight(Activity context){
        int contentHeight;
        if(isPortrait(context)){
            contentHeight = getRealScreenHeight(context) - getCurrentNavigationBarHeight(context);
        }else{
            contentHeight = getRealScreenHeight(context);
        }
        return contentHeight;
    }

    /**
     * 獲取可用屏幕寬度腾啥,無(wú)論是否有虛擬鍵
     * @param context 上下文
     * @return 返回高度
     */
    public static int getContentWidth(Activity context){
        int contentWidth;
        if(isLandScape(context)){
            contentWidth = getRealScreenWidth(context) - getCurrentNavigationBarHeight(context);
        }else{
            contentWidth = getRealScreenWidth(context);
        }
        return contentWidth;
    }
  1. 監(jiān)聽(tīng)虛擬鍵的變化,并將最新屏幕可用寬高傳給RN
    //監(jiān)聽(tīng)虛擬鍵變化
    @Override
    public void onGlobalLayout() {
        if (!mLayoutComplete){
            return;
        }
        Activity activity  = getCurrentActivity();
        if(activity == null){
            return;
        }
        //嚴(yán)格保證是虛擬鍵的變化引起的onGlobalLayout時(shí)才去通知RN
        if(IFAppUtils.isNavigationShow() != DeviceUtils.isNavigationBarShown(activity)){
            updateContentSize();
        }
        IFAppUtils.setIsNavigationShow(DeviceUtils.isNavigationBarShown(activity));
    }
//發(fā)送消息給RN
private void updateContentSize(boolean navigationBarChanged){
     WritableMap writableMap = Arguments.createMap();
     writableMap.putInt("width", getCurrentActivity() == null ? 0 : DeviceUtils.getContentWidth(getCurrentActivity()));
     writableMap.putInt("height", getCurrentActivity() == null ? 0 : DeviceUtils.getContentHeight(getCurrentActivity()));
     writableMap.putBoolean("navigationBarChanged", navigationBarChanged);
     context.getJSModule(RCTNativeAppEventEmitter.class).emit(SCREENSIZE_CHANGE, writableMap);
}

4.RN端需要適配虛擬鍵的按鈕要先注冊(cè)監(jiān)聽(tīng):

addScreenSizeChangeListener(key, listener){
    if(deviceType.android()){
         screenChangeListenerMap.set(key,listener);
    }
},

removeScreenSizeChangeListener(key) {
     if(deviceType.android()) {
         screenChangeListenerMap && screenChangeListenerMap.delete(key);
     }
},

5.RN端接收虛擬鍵變化的消息冯吓,然后去通知所有注冊(cè)過(guò)的頁(yè)面去刷新布局倘待。

if(platform.isAndroidPlatform()){
    let eventEmitter = new NativeEventEmitter(NativeModules.FCTScreenManager);
    eventEmitter.addListener(SCREENSIZE_CHANGE,(sizeInfo)=>{
        let newWidth = sizeInfo.width / PixelRatio.get();
        let newHeight = sizeInfo.height / PixelRatio.get();
        let navigationBarChanged = sizeInfo.navigationBarChanged;
        //布局改變,更新布局
        screenWidth = newWidth;
        screenHeight = newHeight;
        //是虛擬鍵引起的布局變化    
        if(navigationBarChanged){
             screenChangeListenerMap.forEach((value, key)=>{
                 let listener = screenChangeListenerMap.get(key);
                listener && listener();
         });
        }
    });
}

6.刷新布局

componentDidMount(){
    Device.addScreenSizeChangeListener(GUIDE, ()=>{
        this._guideUI.forceUpdate();
    });
}

componentWillUnmount() {
     Device.removeScreenSizeChangeListener(GUIDE);
}

通過(guò)上述方法组贺,可以適配絕大多數(shù)頁(yè)面的虛擬鍵問(wèn)題凸舵。但仍然有一些特殊情況需要特殊處理。

特殊情況1:

????在CommonView/OfflinePage(常用頁(yè)/離線頁(yè))展開(kāi)虛擬鍵失尖,列表被擠壓成兩列啊奄。因?yàn)槊宽?xiàng)的寬度是屏幕寬度的1/3,虛擬鍵展開(kāi)后顯示不下被擠壓成兩行。


常用頁(yè)

??????頁(yè)面添加了虛擬鍵監(jiān)聽(tīng)掀潮,虛擬鍵變化后執(zhí)行了forceUpdate()方法菇夸,重新計(jì)算每個(gè)item寬度進(jìn)行繪制,但此時(shí)列表并沒(méi)有重新繪制:

componentDidMount() {
   Device.addScreenSizeChangeListener(OFFLINE_PAGE, ()=>{
         //重新渲染
         this.forceUpdate();
   })
}

??????幾經(jīng)調(diào)試之后仪吧,發(fā)現(xiàn)ListView會(huì)判斷每行當(dāng)前數(shù)據(jù)和之前的數(shù)據(jù)是否有相同庄新,如果完全相同,則不會(huì)重新執(zhí)行renderRow()方法薯鼠。為了解決此問(wèn)題择诈,需要重新定義ListView的DataSource:

static getNewDataSource(): Object {
        return new SwipeableListViewDataSource({
            getRowData: (data, sectionID, rowID) => data[sectionID][rowID],
            getSectionHeaderData: (data, sectionID) => data[sectionID],
            //之前是row1!==row2
            rowHasChanged: (row1, row2) => true,
            sectionHeaderHasChanged: (s1, s2) => s1 !== s2
        });
    }
<List
     ref={ref => this._view = ref}            
     ds={OfflinePage.getNewDataSource()}
/>
特殊情況2:

????pad的二級(jí)目錄頁(yè),同樣在虛擬鍵變化之后執(zhí)行forceUpdate()方法刷新目錄的整個(gè)控件出皇,但刷新之后羞芍,目錄的位置會(huì)出現(xiàn)偏差,可能左右偏移郊艘。

目錄位置偏移

????調(diào)試發(fā)現(xiàn)pad二級(jí)目錄初始位置在屏幕外荷科,展示是通過(guò)動(dòng)畫(huà)移動(dòng)到屏幕顯示區(qū)域唯咬,最終從屏幕外展示到屏幕內(nèi)。動(dòng)畫(huà)的最終位置計(jì)算是通過(guò)屏幕尺寸等一系列參數(shù)進(jìn)行計(jì)算的步做。由于屏幕的尺寸在虛擬鍵變化后發(fā)生了變化副渴,動(dòng)畫(huà)的最終位置計(jì)算不準(zhǔn),導(dǎo)致目錄頁(yè)顯示位置有偏差全度。
????解決方法是,在二級(jí)目錄展示并且目錄更新之后斥滤,需要重新執(zhí)行動(dòng)畫(huà)調(diào)整目錄最終的位置将鸵。

componentDidUpdate() {
 if(this.state.showSide){
     this._subView.transitionTo({
        translateX: - (this._width - this._rowWidth)
     }, 0)
  }
}
關(guān)于報(bào)表頁(yè)面的虛擬鍵適配:
問(wèn)題1:

??????放大報(bào)表時(shí),虛擬鍵變化佑颇,RN計(jì)算報(bào)表的縮放系數(shù)傳給原生重新繪制顶掉,導(dǎo)致報(bào)表恢復(fù)原本大小。

解決方法:

????判斷是否是虛擬鍵變化導(dǎo)致的報(bào)表重新繪制挑胸,并將該字段傳遞給原生代碼痒筒。

Device.addScreenSizeChangeListener(SINGLE_PAGE_VIEW,()=>{
       this.causeByNavigationBarChanged = true;
});
UIManager.dispatchViewManagerCommand(
    ReactNative.findNodeHandle(this.reportRef),
    UIManager.FCTReportView.Commands.setGridZoomScale,
    paramsArray
);

????原生如果是虛擬鍵導(dǎo)致的放大系數(shù)改變,則只保存放大縮放茬贵,不會(huì)真的進(jìn)行縮放簿透。如果不是虛擬鍵導(dǎo)致的放大系數(shù)改變,則進(jìn)行縮放解藻。

public void setGridZoomScale(float scale, float maxScale, float minScale, boolean causeByNavigationBarChanged) {
    this.maxScale = maxScale;
    this.minScale = minScale;
    //虛擬鍵變化導(dǎo)致縮放系數(shù)的變化時(shí)不進(jìn)行縮放老充,只保存縮放系數(shù)
    if(!causeByNavigationBarChanged){
         scaleReport(scale);
    }
}
問(wèn)題2:

????部分Android手機(jī)虛擬按鍵隱藏時(shí),打開(kāi)鍵盤(pán)會(huì)順便帶出虛擬按鍵螟左。報(bào)表點(diǎn)擊輸入框時(shí)會(huì)出現(xiàn)情況是:每次點(diǎn)擊輸入框控件鍵盤(pán)剛彈出來(lái)就會(huì)隱藏啡浊。

  • 點(diǎn)擊輸入框控件,鍵盤(pán)彈出帶出虛擬按鍵
  • 報(bào)表被虛擬按鍵擠壓胶背,需要調(diào)整大小進(jìn)行刷新巷嚣。
  • 報(bào)表刷新導(dǎo)致輸入框控件失去焦點(diǎn),鍵盤(pán)隱藏钳吟。
解決方法:

????對(duì)鍵盤(pán)的顯示和隱藏進(jìn)行監(jiān)聽(tīng):在鍵盤(pán)打開(kāi)時(shí)廷粒,無(wú)論虛擬鍵是否變化,都不會(huì)調(diào)整報(bào)表大小砸抛。這樣也就避免了因?yàn)閳?bào)表的刷新導(dǎo)致的焦點(diǎn)失去评雌、鍵盤(pán)隱藏的問(wèn)題。只有鍵盤(pán)隱藏是直焙,才會(huì)根據(jù)虛擬鍵的變化調(diào)整報(bào)表大小景东。

//SinglePageView
_onLayout(e) {
        const layout = e.nativeEvent.layout;
        const {width, height} = layout;
        //fixme MOBILE-8163 安卓虛擬按鍵會(huì)導(dǎo)致containerSize產(chǎn)生輕微變化,
        // 尤其是小米 OPPO部分全面屏機(jī)型在隱藏虛擬按鍵的情況下奔誓,填報(bào)時(shí)輸入法會(huì)把虛擬按鍵帶出來(lái)導(dǎo)致height變小斤吐,觸發(fā)重新布局搔涝,重新發(fā)送options給原生,導(dǎo)致焦點(diǎn)立即失去和措,無(wú)法填報(bào)
        // 補(bǔ)充:如果鍵盤(pán)處于彈出狀態(tài)庄呈,虛擬鍵變化時(shí),不觸發(fā)重新布局派阱。這個(gè)不影響MOBILE-8463中鍵盤(pán)彈出帶出虛擬鍵的情況诬留,同時(shí)解決報(bào)表不能根據(jù)虛擬鍵的變化進(jìn)行自適應(yīng)的情況。
        if (Device.isAndroidPlatform() && this.orientation === orientation && this.keyboardDidShow) {
            this.causeByNavigationBarChanged = false;
            return;
        }
        this.setState({
            containerSize: {width, height}
        });
    }
問(wèn)題2.1:

????解決問(wèn)題2的關(guān)鍵是監(jiān)聽(tīng)鍵盤(pán)的變化贫母。Android鍵盤(pán)是通過(guò)監(jiān)聽(tīng)布局被擠壓文兑,然后通過(guò)計(jì)算來(lái)獲得鍵盤(pán)彈出和鍵盤(pán)隱藏的事件的。
????但在橫屏的時(shí)候腺劣,鍵盤(pán)在全新的界面彈出绿贞,不會(huì)擠壓主布局,暫時(shí)無(wú)法監(jiān)聽(tīng)到鍵盤(pán)的彈出和隱藏事件橘原。

解決方法:

????暫時(shí)通過(guò)監(jiān)聽(tīng)輸入框焦點(diǎn)的變化來(lái)判斷鍵盤(pán)狀態(tài)籍铁。(此方法有bug的情況:橫屏->點(diǎn)擊輸入框->點(diǎn)鍵盤(pán)的回收按鈕隱藏鍵盤(pán),輸入框仍然有焦點(diǎn)->此時(shí)會(huì)被誤認(rèn)為鍵盤(pán)時(shí)彈出狀態(tài)趾断,報(bào)表不會(huì)根據(jù)虛擬鍵的變化伸縮)

 onReceiveMessage({row, column, data = {}}) {
    //Fixme 這里臨時(shí)處理android橫屏虛擬鍵問(wèn)題拒名,通過(guò)輸入框的焦點(diǎn)來(lái)判斷鍵盤(pán)是否打開(kāi),可以保證橫屏填報(bào)可用歼冰。但還存在隱藏鍵盤(pán)后報(bào)表的放大系數(shù)不對(duì)的問(wèn)題靡狞。
    if(Device.android() && Orientation.isLandscape()){
        this.resolveTextFocusEvent(data);
    }    
}

resolveTextFocusEvent (data){
    //輸入框有焦點(diǎn)就認(rèn)為鍵盤(pán)彈出了
    if(data.onTextFocus === true){
        this.keyboardDidShow = true;
    }else if(data.onTextFocus === false){
        this.keyboardDidShow = false;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市隔嫡,隨后出現(xiàn)的幾起案子甸怕,更是在濱河造成了極大的恐慌,老刑警劉巖腮恩,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梢杭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡秸滴,警方通過(guò)查閱死者的電腦和手機(jī)武契,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荡含,“玉大人咒唆,你說(shuō)我怎么就攤上這事∈鸵海” “怎么了全释?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)误债。 經(jīng)常有香客問(wèn)我浸船,道長(zhǎng)妄迁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任李命,我火速辦了婚禮登淘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘封字。我一直安慰自己黔州,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布阔籽。 她就那樣靜靜地躺著辩撑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仿耽。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天各薇,我揣著相機(jī)與錄音项贺,去河邊找鬼。 笑死峭判,一個(gè)胖子當(dāng)著我的面吹牛开缎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播林螃,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奕删,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了疗认?” 一聲冷哼從身側(cè)響起完残,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎横漏,沒(méi)想到半個(gè)月后谨设,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缎浇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年扎拣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片素跺。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡二蓝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出指厌,到底是詐尸還是另有隱情刊愚,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布仑乌,位于F島的核電站百拓,受9級(jí)特大地震影響琴锭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜衙传,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一决帖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蓖捶,春花似錦地回、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至并闲,卻和暖如春细睡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帝火。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工溜徙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人犀填。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓蠢壹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親九巡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子图贸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,262評(píng)論 25 707
  • 文字:雪人圖片:來(lái)自網(wǎng)絡(luò) 教師節(jié)贊歌九月 風(fēng)輕云淡 秋高氣爽九月 艷陽(yáng)高照 五谷飄香在這美好的日子里我們迎來(lái)了2...
    霧都花兒閱讀 434評(píng)論 0 5