AndroidAndroid developing tipsandroid实用技术

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

2017-01-24  本文已影响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事件的传递及拦截机制。当然解决的过程就是踩坑与提高的过程,希望本文能给遇到该问题的小伙伴一个思路。

上一篇下一篇

猜你喜欢

热点阅读