RecyclerView嵌套WebView的两种解决方案

前言

众所周知,RecyclerView是可以上下滑动的(当然根据对LayoutManager设置的不同也可以左右滑动),而WebView也是可以上下左右滑动的。如果RecyclerView和WebView相互嵌套(即RecyclerView的一个条目为WebView),就会产生滑动冲突。具体表现就是只有RecyclerView能滑动,WebView的滑动事件被拦截了。原因也很好理解,如果你是RecyclerView的设计者,你也不会默认把滑动事件交给itemView去处理,因为这样很容易乱套,会出现很多奇奇怪怪的bug。RecyclerView对事件具体的处理策略,可以自行查看其源码。下面直接开门见山的说解决方案。

解决方案一

方案一其实很简单,在布局里面设置WebView的高度为“wrap_content”。至于为什么这样就可以,我们先来复习一下wrap_content和match_parent

  • wrap_content
    wrap content翻译成汉语就是“包裹内容”,WebView的内容就是网页的内容,如果WebView的高度设置为“wrap_content”,那WebView的高度就是网页内容的高度。
  • match_parent
    match parent即匹配父窗体,父控件有多高,高度设置成match_parent的View就有多高。

我们假设RecyclerView有两个条目(其中一个是WebView)。此时对WebView的高度设成wrap_content和match_parent时,对比如下:

match_parent和wrap_content.png

当WebView高度设置成wrap_content时,WebView加载的网页的内容在WebView里全部展现了,只需要滑动RecyclerView,就可以查看到未显示的内容了。
如果设置成match_parent,网页的内容并没有全部展示在WebView当中,需要滑动WebView来展示没有展现的剩下的内容;而此时WebView并不会获得滑动事件,所以剩下的内容永远也没有展现的机会了。

既然wrap_content能完美解决,又如此简单,就用这种方案好了,为什么还会有方案二呢?wrap_content会有些问题,就我发现的:
1,如果网页会有弹窗,弹窗会显示在网页的正中间,也就是WebView的正中间,对照上图(1),正常情况下不会显示在屏幕范围内,需要向上滑动一段才能看见弹窗,这样对用户是不友好的;
2,会造成部分JS代码执行错误。
如果设置成match_parent就没有这些问题;下面剩下的问题就是解决滑动冲突,在合适的时候将RecyclerView的事件传递给WebView。即下面的解决方案二。

解决方案二

其实思路很简单,重写RecyclerView的onTouchEvent方法,在合适的时候将事件传递给WebView。但是这样做需要写个自定义的RecyclerView然后覆盖onTouchEvent方法,比较麻烦。
View对外提供有setOnTouchListener的接口,只需要传一个OnTouchListener的对象,实现onTouch方法,对事件进行处理即可。
OnTouchListener的优先级比onTouchEvent的优先级要高,可以参见View的源码的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
        //代码省略
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        //优先调用 li.mOnTouchListener.onTouch(this, event),如果返回true,就不会调用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;
}

接下来的难点就是在合适的时候将事件传递给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对象,以便将事件传递给他
            WebView webView = (WebView) webViewHolder.itemView.findViewById(R.id.web_view);
            //获取WebView所在item的顶部相对于其父控件(即RecyclerView的父控件)的距离
            int itemViewTop = webViewHolder.itemView.getTop();
            if(itemViewTop > 0) {
                return false;
            }
            if(itemViewTop < 0) {
                webViewHolder.itemView.scrollTo(0, 0);
                return false;
            }

            //计算dy,用来判断滑动方向。dy<0-->向上滑动;dy>0-->向下滑动。
            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顶部滑动到RecyclerView父控件顶部重合时,
            // 此时需要拦截滑动事件交给WebView处理。
            if(itemViewTop == 0) {
                if(shouldIntercept(webView, dy)) {
                    webView.onTouchEvent(event);
                    return true;
                }
            }
            return false;
        }

        /**
         * 是否拦截滑动事件,判断的逻辑是:<br/>
         * 1,如果是向上滑动,并且webview能够向上滑动,则拦截事件;<br/>
         * 2,如果是向下滑动,并且webview能够向下滑动,则拦截事件。
         * @param view 判断能够滑动的view
         * @param dy 滑动间距
         * @return true拦截,false不拦截。
         */
        private boolean shouldIntercept(View view, int dy) {
            //canScrollVertically方法的第二个参数direction,传1时返回是否能够向上滑动,传-1时返回能否向下滑动。
            //dy<0-->向上滑动;dy>0-->向下滑动。
            boolean scrollUp = dy < 0 && ViewCompat.canScrollVertically(view, 1);
            boolean scrollDown = dy > 0 && ViewCompat.canScrollVertically(view, -1);
            return scrollUp || scrollDown || dy == 0;
        }
    }

接下来把该OnTouchListener设置给RecyclerView就可以了。

recyclerView.setOnTouchListener(new RecyclerViewOnTouchListener());

具体逻辑代码注释已经写的很清楚了,这里就不再啰嗦了。

结语

方案二逻辑比较复杂,没有完整测试,不知道有没有bug。方案一比较简单直接,如果你要加载的网页环境比较简单,没有弹窗,就直接用方案一吧,开发工作量也要小很多。
另外本人才疏学浅,可能有表述不当甚至理解错误的地方,欢迎指正,共同进步。

江湖规矩,源码见 github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容