RecyclerView嵌套WebView的兩種解決方案

前言

眾所周知仪召,RecyclerView是可以上下滑動(dòng)的(當(dāng)然根據(jù)對(duì)LayoutManager設(shè)置的不同也可以左右滑動(dòng))乍钻,而WebView也是可以上下左右滑動(dòng)的即纲。如果RecyclerView和WebView相互嵌套(即RecyclerView的一個(gè)條目為WebView),就會(huì)產(chǎn)生滑動(dòng)沖突浆劲。具體表現(xiàn)就是只有RecyclerView能滑動(dòng)砂沛,WebView的滑動(dòng)事件被攔截了烫扼。原因也很好理解曙求,如果你是RecyclerView的設(shè)計(jì)者碍庵,你也不會(huì)默認(rèn)把滑動(dòng)事件交給itemView去處理,因?yàn)檫@樣很容易亂套悟狱,會(huì)出現(xiàn)很多奇奇怪怪的bug静浴。RecyclerView對(duì)事件具體的處理策略,可以自行查看其源碼挤渐。下面直接開門見山的說解決方案苹享。

解決方案一

方案一其實(shí)很簡單,在布局里面設(shè)置WebView的高度為“wrap_content”浴麻。至于為什么這樣就可以得问,我們先來復(fù)習(xí)一下wrap_content和match_parent

  • wrap_content
    wrap content翻譯成漢語就是“包裹內(nèi)容”,WebView的內(nèi)容就是網(wǎng)頁的內(nèi)容软免,如果WebView的高度設(shè)置為“wrap_content”宫纬,那WebView的高度就是網(wǎng)頁內(nèi)容的高度。
  • match_parent
    match parent即匹配父窗體膏萧,父控件有多高漓骚,高度設(shè)置成match_parent的View就有多高。

我們假設(shè)RecyclerView有兩個(gè)條目(其中一個(gè)是WebView)榛泛。此時(shí)對(duì)WebView的高度設(shè)成wrap_content和match_parent時(shí)蝌蹂,對(duì)比如下:

match_parent和wrap_content.png

當(dāng)WebView高度設(shè)置成wrap_content時(shí),WebView加載的網(wǎng)頁的內(nèi)容在WebView里全部展現(xiàn)了曹锨,只需要滑動(dòng)RecyclerView孤个,就可以查看到未顯示的內(nèi)容了。
如果設(shè)置成match_parent沛简,網(wǎng)頁的內(nèi)容并沒有全部展示在WebView當(dāng)中硼身,需要滑動(dòng)WebView來展示沒有展現(xiàn)的剩下的內(nèi)容;而此時(shí)WebView并不會(huì)獲得滑動(dòng)事件覆享,所以剩下的內(nèi)容永遠(yuǎn)也沒有展現(xiàn)的機(jī)會(huì)了佳遂。

既然wrap_content能完美解決,又如此簡單撒顿,就用這種方案好了丑罪,為什么還會(huì)有方案二呢?wrap_content會(huì)有些問題,就我發(fā)現(xiàn)的:
1吩屹,如果網(wǎng)頁會(huì)有彈窗跪另,彈窗會(huì)顯示在網(wǎng)頁的正中間,也就是WebView的正中間煤搜,對(duì)照上圖(1)免绿,正常情況下不會(huì)顯示在屏幕范圍內(nèi),需要向上滑動(dòng)一段才能看見彈窗擦盾,這樣對(duì)用戶是不友好的嘲驾;
2,會(huì)造成部分JS代碼執(zhí)行錯(cuò)誤迹卢。
如果設(shè)置成match_parent就沒有這些問題辽故;下面剩下的問題就是解決滑動(dòng)沖突,在合適的時(shí)候?qū)ecyclerView的事件傳遞給WebView腐碱。即下面的解決方案二誊垢。

解決方案二

其實(shí)思路很簡單,重寫RecyclerView的onTouchEvent方法症见,在合適的時(shí)候?qū)⑹录鬟f給WebView喂走。但是這樣做需要寫個(gè)自定義的RecyclerView然后覆蓋onTouchEvent方法,比較麻煩谋作。
View對(duì)外提供有setOnTouchListener的接口芋肠,只需要傳一個(gè)OnTouchListener的對(duì)象,實(shí)現(xiàn)onTouch方法瓷们,對(duì)事件進(jìn)行處理即可业栅。
OnTouchListener的優(yōu)先級(jí)比onTouchEvent的優(yōu)先級(jí)要高,可以參見View的源碼的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
        //代碼省略
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        //優(yōu)先調(diào)用 li.mOnTouchListener.onTouch(this, event)谬晕,如果返回true碘裕,就不會(huì)調(diào)用onTouchEvent了
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //
        if (!result && onTouchEvent(event)) {
            result = true;
        }
        //省略代碼
        return result;
}

接下來的難點(diǎn)就是在合適的時(shí)候將事件傳遞給WebView了。直接看代碼注釋吧:

private class RecyclerViewOnTouchListener implements View.OnTouchListener {

        private int mLastY;
        private int mCurrentY;
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            WebViewHolder webViewHolder = mAdapter.getWebViewHolder();
            if(webViewHolder == null) {
                return false;
            }
            //獲取WebView對(duì)象攒钳,以便將事件傳遞給他
            WebView webView = (WebView) webViewHolder.itemView.findViewById(R.id.web_view);
            //獲取WebView所在item的頂部相對(duì)于其父控件(即RecyclerView的父控件)的距離
            int itemViewTop = webViewHolder.itemView.getTop();
            if(itemViewTop > 0) {
                return false;
            }
            if(itemViewTop < 0) {
                webViewHolder.itemView.scrollTo(0, 0);
                return false;
            }

            //計(jì)算dy帮孔,用來判斷滑動(dòng)方向。dy<0-->向上滑動(dòng)不撑;dy>0-->向下滑動(dòng)文兢。
            int dy = 0;
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastY = (int) event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mCurrentY = (int) event.getY();
                    dy = mCurrentY - mLastY;
                    mLastY = mCurrentY;
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    dy = (int) (event.getY() - mLastY);
                    mLastY = 0;
                    mCurrentY = 0;
                    break;
            }
            Log.d(TAG, "dy = " + dy);
            Log.d(TAG, "itemViewTop = " + itemViewTop);
            
            //如果WebView頂部距離其父控件距離未0,即WebView頂部滑動(dòng)到RecyclerView父控件頂部重合時(shí)焕檬,
            // 此時(shí)需要攔截滑動(dòng)事件交給WebView處理姆坚。
            if(itemViewTop == 0) {
                if(shouldIntercept(webView, dy)) {
                    webView.onTouchEvent(event);
                    return true;
                }
            }
            return false;
        }

        /**
         * 是否攔截滑動(dòng)事件,判斷的邏輯是:<br/>
         * 1,如果是向上滑動(dòng)实愚,并且webview能夠向上滑動(dòng)兼呵,則攔截事件兔辅;<br/>
         * 2,如果是向下滑動(dòng),并且webview能夠向下滑動(dòng)击喂,則攔截事件维苔。
         * @param view 判斷能夠滑動(dòng)的view
         * @param dy 滑動(dòng)間距
         * @return true攔截,false不攔截懂昂。
         */
        private boolean shouldIntercept(View view, int dy) {
            //canScrollVertically方法的第二個(gè)參數(shù)direction介时,傳1時(shí)返回是否能夠向上滑動(dòng),傳-1時(shí)返回能否向下滑動(dòng)凌彬。
            //dy<0-->向上滑動(dòng)沸柔;dy>0-->向下滑動(dòng)。
            boolean scrollUp = dy < 0 && ViewCompat.canScrollVertically(view, 1);
            boolean scrollDown = dy > 0 && ViewCompat.canScrollVertically(view, -1);
            return scrollUp || scrollDown || dy == 0;
        }
    }

接下來把該OnTouchListener設(shè)置給RecyclerView就可以了饿序。

recyclerView.setOnTouchListener(new RecyclerViewOnTouchListener());

具體邏輯代碼注釋已經(jīng)寫的很清楚了勉失,這里就不再啰嗦了羹蚣。

結(jié)語

方案二邏輯比較復(fù)雜原探,沒有完整測試,不知道有沒有bug顽素。方案一比較簡單直接咽弦,如果你要加載的網(wǎng)頁環(huán)境比較簡單,沒有彈窗胁出,就直接用方案一吧型型,開發(fā)工作量也要小很多。
另外本人才疏學(xué)淺全蝶,可能有表述不當(dāng)甚至理解錯(cuò)誤的地方闹蒜,歡迎指正,共同進(jìn)步抑淫。

江湖規(guī)矩绷落,源碼見 github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市始苇,隨后出現(xiàn)的幾起案子砌烁,更是在濱河造成了極大的恐慌,老刑警劉巖催式,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件函喉,死亡現(xiàn)場離奇詭異,居然都是意外死亡荣月,警方通過查閱死者的電腦和手機(jī)管呵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哺窄,“玉大人捐下,你說我怎么就攤上這事顿天。” “怎么了蔑担?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵牌废,是天一觀的道長。 經(jīng)常有香客問我啤握,道長鸟缕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任排抬,我火速辦了婚禮懂从,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹲蒲。我一直安慰自己番甩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布届搁。 她就那樣靜靜地躺著缘薛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卡睦。 梳的紋絲不亂的頭發(fā)上宴胧,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音表锻,去河邊找鬼恕齐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瞬逊,可吹牛的內(nèi)容都是我干的显歧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼确镊,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼士骤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骚腥,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤敦间,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后束铭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廓块,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年契沫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了带猴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡懈万,死狀恐怖拴清,靈堂內(nèi)的尸體忽然破棺而出靶病,到底是詐尸還是另有隱情,我是刑警寧澤口予,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布娄周,位于F島的核電站,受9級(jí)特大地震影響沪停,放射性物質(zhì)發(fā)生泄漏煤辨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一木张、第九天 我趴在偏房一處隱蔽的房頂上張望众辨。 院中可真熱鬧,春花似錦舷礼、人聲如沸鹃彻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛛株。三九已至,卻和暖如春旋奢,著一層夾襖步出監(jiān)牢的瞬間泳挥,已是汗流浹背然痊。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國打工至朗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剧浸。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓锹引,卻偏偏與公主長得像,于是被迫代替她去往敵國和親唆香。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫌变,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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