美文网首页AndroidAndroid developing tipsandroid实用技术
Viewpager与webview滑动冲突的解决方案

Viewpager与webview滑动冲突的解决方案

作者: 老衲法号能吃 | 来源:发表于2017-01-24 16:25 被阅读4519次

    场景描述

    最近在接触h5与android混合开发时遇到一个问题,在一个activity使用ViewPager+Fragment结构,某个Fragment包含了一个webview。而在这个webview展示的h5里有一个横屏轮播的元素,此时当我们横向滑动的时候,大多数情况下是ViewPager在滑动(这里说是大多数情况是不考虑网页可以横向滑动的特殊情况)。因此我们需要判断并处理事件。

    如下图所示,蓝色线条中间的部分是一个轮播。而整个首页的结构是一个Viewpager。


    Paste_Image.png

    问题分析

    其实h5的轮播基本都会支持手滑动事件(指JS控制轮播的滑动),我们要做的就是判断什么时候,touch事件由h5来处理,什么时候由ViewPager来处理,这里就不再讲述android的事件机制,有兴趣的同学可以去老衲之前发布的文章中去找。这里只用到了其中一个知识点,即child如何影响parent的事件处理。所涉及到的方法就是

    //当result为true的时候,child会阻止parent获取touch事件,反之则不会影响。
    requestDisallowInterceptTouchEvent(result);
    

    而这个方法参数result的值是true还是false,就是今天要踩得坑之一。

    Paste_Image.png

    主要思路

    1. 首先我们需要确定的是,需要重写谁的onTouch方法,webview还是viewpager,当然是webview,通过webview来主动控制viewpager的事件获取权限。

    2. 接下来,为了判断,我们还需要轮播控件的坐标和范围,这个可以有h5和JS来实现

    3. 范围有了。接下来就是要真正进行touch的坐标判断了。

    踩坑1之Java与JS的之间相互调用的顺序

    关于android内的java方法与html内的js方法相互调用请大家自行百度。这里要提醒大家的是,当JS与java在进行交互的时候,他们并不是同步执行的。举个栗子

    public void doCheck() {
        String call = "javascript:getViewPagerInfo()";
        webview.loadUrl(call);
    }
    

    当通过上述方法调用JS的getViewPagerInfo方法时,而JS的getViewPagerInfo方法内部又调用了java的下列方法时。

        @JavascriptInterface
        public void getH5ViewPagerInfo(int x ,int y , int width , int height){
            mPagerDesc = new PagerDesc(y,x,x+width,y+height , 0);
        }
    

    假如我们需要按顺序执行如下两个方法

    doCheck();
    showToast();
    

    当执行完doCheck的loadurl方法之后,他会去执行showToast,不会等JS回调java的getH5ViewPagerInfo方法执行完再执行。

    踩坑2之JS滑动

    刚开始接到这个需求的时候,会想的太多,导致刚开始考虑h5的时候顺带把网页的滑动也计算进去了。这个是没有必要的。见踩坑3内的代码,可以兼容滑动的情况。

    踩坑3之h5获取轮播控件的坐标与宽高

    没啥技术亮点,直接看代码

    //获取轮播控件的宽高以及相对于原点的位置
    function getViewPagerInfo() {
        var width = img.clientWidth;
        var height = img.clientHeight;
        var elem = getElementRect(img);
        //调用android代码
        window.controller.getH5ViewPagerInfo(elem.x,elem.y,width,height);
    }
    //获取元素的坐标
    function getElementRect(e){
        var box = e.getBoundingClientRect();
        var x = box.left;
        var y = box.top;
        console.log("x::" + x);
        console.log("y::" + y);
        return {x:x , y: y};
    }
    

    踩坑4之轮播宽高坐标的获取时机

    因为h5页面的高度是不确定的。很有可能是可以上下滑动的。所以我们轮播的区域也是会变化的,而且!!!轮播的区域可能不止一个,这个需要注意。轮播区域的获取时机有两个,一个是刚加载h5页面的时候,另外一个就是滑动的时候,在js代码里写

    window.onload = function(){
      ...
    }
    
    window.onscroll = function(){
      ...
    }
    

    踩坑6之h5与android坐标系的转换

    该需求最大的坑就在于h5与android坐标系的换算,h5的坐标系与android的坐标系的不同在于

    1. h5的坐标系以webview左上角的点为准,而android得坐标系以屏幕左上角的点为准。因此,这里要将通知栏的高度计算进去。
    2. h5的坐标系采用的是css的像素,而android是采用的设备的像素值。这两个像素需要进行换算,换算的规则也很简单,与设备的像素密度相关。

    假设我们现在拿到了轮播的坐标,以及宽高,并且通过js回传给了java,,此时,我们就可以进行最重要的touch事件的判断了。

    首先我们定义一个内部类用来封装轮播的宽高和坐标

     class PagerDesc {
        private int top;
        private int left;
        private int right;
        private int bottom;
    
        public PagerDesc(int top, int left , int right ,int bottom ) {
            this.top = top;
            this.bottom = bottom;
        }
    }
    

    考虑到目前多数的轮播都是横向充满全屏,因此这里我们只考虑touch事件在y轴上的坐标。

    接下来,需要通过js将上述类创建所需要的数据回传给java用来创建对象。

      private  PagerDesc mPagerDesc;
    
        @JavascriptInterface
        public void getH5ViewPagerInfo(int x ,int y , int width , int height){
            mPagerDesc = new PagerDesc(y,x,x+width,y+height);
        }
    

    最关键的一步,我们要在webview的onTouchListener进行如下处理。

            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                //获取y轴坐标
                float y = motionEvent.getRawY();
                switch (motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        getHTMLPosition();
                        if (null != mPagerDesc) {
                            int top = mPagerDesc.top;
                            int bottom = top + (mPagerDesc.bottom - mPagerDesc.top);
                            //将css像素转换为android设备像素并考虑通知栏高度
                            top = (int) (top * metric.density) + height 
                            bottom = (int) (bottom * metric.density) + height    
                            //如果触摸点的坐标在轮播区域内,则由webview来处理事件,否则由viewpager来处理
                            if (y > top && y < bottom) {
                                webview.requestDisallowInterceptTouchEvent(true);
                            } else {
                                webview.requestDisallowInterceptTouchEvent(false);
                            }
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                    case MotionEvent.ACTION_MOVE:
                        break;
                }
    

    至此,轮播和ViewPager的滑动冲突及解决方案已经介绍完了。这个问题考察的点还是挺多的,需要开发者有一定的JS基础,需要懂得JS与java的相互调用,以及深入理解touch事件的传递及拦截机制。当然解决的过程就是踩坑与提高的过程,希望本文能给遇到该问题的小伙伴一个思路。

    相关文章

      网友评论

      • fec66b3ed5ef:收藏之..坑的是我们的h5是第三方提供的页面..无法给我们提供轮播图的function...
        老衲法号能吃:这个可以去争取嘛。。当时我也是找的前端
      • 骑驴去看海:还没具体试过,不过先mark。

      本文标题:Viewpager与webview滑动冲突的解决方案

      本文链接:https://www.haomeiwen.com/subject/jxoxvttx.html