检查Android WebView是否正在消耗触摸事件

62

简述

如何检测Android WebView是否消耗了触摸事件?onTouchEvent总是返回true,而WebViewClientonUnhandledInputEvent从未被触发。

详细描述

我有多个WebViews在TwoDScrollView中。正如其名称所示,TwoDScrollView可以同时垂直和水平滚动。TwoDScrollView的内容可以缩放。当用户拖动手指或使用捏合手势缩放时,我希望将触摸事件分派到:

  • 如果其内容可滚动/可缩放,则将其分派到WebView(即仅WebView内部将滚动/缩放)
  • 如果上述条件不成立(TwoDScrollView的所有内容都将滚动/缩放),则将其分派到TwoDScrollView。

我通过使用canScrollHorizontallycanScrollVertically方法部分实现了此目标。但是这些方法仅适用于“本机滚动”。但是,在某些情况下,WebView内部的一些JavaScript会消耗触摸事件,例如Google地图。在这种情况下,该方法返回false。有没有办法找出WebView的内容是否消耗了触摸事件,即是否可滚动/缩放?我无法更改WebView的内容,因此我的问题与此问题不同。

我考虑通过使用evaluateJavaScript方法在Webview内执行一些JavaScript来检查触摸处理程序,但是根据这个答案,没有简单的方法可以实现这一点,而且页面可能有一些其他嵌套的iframes。非常感谢您提供任何帮助。

我已经尝试过的

  1. 我重写了WebView的onTouchEvent并阅读了super.onTouchEvent(),但它总是返回true。
  • canScrollHorizontallycanScrollVertically只能部分解决这个问题,正如上面提到的那样。
  • onScrollChanged也没有用。
  • WebViewClient.onUnhandledInputEvent从来没有触发过。
  • 我考虑使用evaluateJavaScript通过JavaScript实现,但这是一种非常复杂且丑陋的解决方案。
  • 我尝试通过Debug.startMethodTracing跟踪MotionEvent。我发现它是这样传播的:
    • android.webkit.WebView.onTouchEvent
    • com.android.webview.chromium.WebViewChromium.onTouchEvent
    • com.android.org.chromium.android_webview.AwContents.onTouchEvent
    • com.android.org.chromium.android_webview.AwContents$AwViewMethodsImpl.onTouchEvent
    • com.android.org.chromium.content.browser.ContentViewCore.onTouchEventImpl
    • 根据ContentViewCore源代码,触摸事件最终进入一个本地方法nativeOnTouchEvent,我不知道它后面会发生什么。无论如何,onTouchEvent始终返回true,即使可以找到某个地方确定事件是否被消耗,也需要使用私有方法,这也相当丑陋。
  • 注意事项

    我不需要知道如何拦截发送给WebView的触摸事件,而是需要知道WebView是否正在消耗它们,即是否将其用于滚动、拖动等操作。


    你有没有试过看这个链接 https://dev59.com/y2cs5IYBdhLWcg3wym4h。 - Smashing
    等等,我不太清楚。你尝试过调用 webview.setOnTouchListener 吗? - dabluck
    好的,那么将以下内容添加到ontouchlistener中怎么样: WebView.HitTestResult hr = ((WebView) v).getHitTestResult(); Log.e("WEBVIEW", "getExtra = " + hr.getExtra() + "\t\t Type=" + hr.getType()); 然后检查一下类型是否能对你有所帮助? - Smashing
    如果触摸事件被WebView消耗,它们将不会传播到父级。也许您可以通过在父级上设置触摸监听器并查看哪些事件传播来解决这个问题? - dabluck
    1
    这里有一个类似的问题在Chromium问题跟踪器上:https://code.google.com/p/chromium/issues/detail?id=515799 - Twibit
    显示剩余5条评论
    5个回答

    1
    根据这个 问题报告,不可能实现。如果网页代码在您的控制下,您可以实现一些JavaScript接口来解决此问题。否则,恐怕这里没有解决办法。

    0
    你可以通过重写 WebView 的 onTouchEvent 方法,将所有 touch 事件传递给 GestureDetector,这样你就可以通过监听 GestureDetector 来知道 Android WebView 何时在任何地方消耗 touch 事件。
    试试这样做:
    public class MyWebView extends WebView {
        private Context context;
        private GestureDetector gestDetector;
    
        public MyWebView(Context context) {
            super(context);
    
            this.context = context;
            gestDetector = new GestureDetector(context, gestListener);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return gd.onTouchEvent(event);
        }
    
        GestureDetector.SimpleOnGestureListener gestListener= new GestureDetector.SimpleOnGestureListener() {
            public boolean onDown(MotionEvent event) {
                return true;
            }
    
            public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) {
                //if (event1.getRawX() > event2.getRawX()) {
                //    show_toast("swipe left");
                //} else {
                //    show_toast("swipe right");
                //}
                //you can trace any touch events here
                return true;
            }
        };
    
        void show_toast(final String text) {
            Toast t = Toast.makeText(context, text, Toast.LENGTH_SHORT);
            t.show();
        }
    }
    

    希望你能获得灵感。


    我理解GestureDetector可以将原始的MotionEvents序列转换为一些手势,例如fling、scroll、double tap等,但它并不能解决我的问题,即确定WebView是否使用了触摸事件进行某些内部处理。 - Miloš Černilovský

    -1
    尝试在XML中设置android:isClickable="true",并在Java代码中创建一个onClickListener

    -1

    这段代码将处理您在Webview中的滚动事件。它捕获点击下和点击上事件,并比较每个事件的位置。它不会考虑Webview内部的内容是否可滚动,只需比较Webview区域内的坐标即可。

    public class MainActivity extends Activity implements View.OnTouchListener, Handler.Callback {
    
        private float x1,x2,y1,y2; //x1, y1 is the start of the event, x2, y2 is the end.
        static final int MIN_DISTANCE = 150; //min distance for a scroll event
    
        private static final int CLICK_ON_WEBVIEW = 1;
        private static final int CLICK_ON_URL = 2;
        private static final int UP_ON_WEBVIEW = 3;
    
    
        private final Handler handler = new Handler(this);
    
        public WebView webView;
        private WebViewClient client;
        private WebAppInterface webAppInt = new WebAppInterface(this);
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            webView = (WebView)findViewById(R.id.myWebView);
            webView.setOnTouchListener(this);
    
            client = new WebViewClient();
            webView.setWebViewClient(client);        
            webView.loadDataWithBaseURL("file:///android_asset/", "myweb.html", "text/html", "UTF-8", "");
    
        }
    
    //HERE START THE IMPORTANT PART
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (v.getId() == R.id.myWebView && event.getAction() == MotionEvent.ACTION_DOWN){
                x1 = event.getX();
                y1 = event.getY();
                handler.sendEmptyMessageDelayed(CLICK_ON_WEBVIEW, 200);
            } else if (v.getId() == R.id.myWebView && event.getAction() == MotionEvent.ACTION_UP){
                x2 = event.getX();
                y2 = event.getY();
    
                handler.sendEmptyMessageDelayed(UP_ON_WEBVIEW, 200);
            }
    
            return false;
        }
    
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == CLICK_ON_URL){ //if you clic a link in the webview, thats not a scroll
                handler.removeMessages(CLICK_ON_WEBVIEW);
                handler.removeMessages(UP_ON_WEBVIEW);
                return true;
            }
            if (msg.what == CLICK_ON_WEBVIEW){
                //Handle the click in the webview
                Toast.makeText(this, "WebView clicked", Toast.LENGTH_SHORT).show();
                return true;
            }
            if (msg.what == UP_ON_WEBVIEW){
                float deltaX = x2 - x1; //horizontal move distance
                float deltaY = y2 - y1; //vertical move distance
                if ((Math.abs(deltaX) > MIN_DISTANCE) && (Math.abs(deltaX) > Math.abs(deltaY)))
                {
                    // Left to Right swipe action
                    if (x2 > x1)
                    {
                        //Handle the left to right swipe
                        Toast.makeText(this, "Left to Right swipe", Toast.LENGTH_SHORT).show ();
                    }
    
                    // Right to left swipe action
                    else
                    {
                        //Handle the right to left swipe
                        Toast.makeText(this, "Right to Left swipe", Toast.LENGTH_SHORT).show ();
                    }
    
                }
                else if ((Math.abs(deltaY) > MIN_DISTANCE) && (Math.abs(deltaY) > Math.abs(deltaX)))
                {
                    // Top to Bottom swipe action
                    if (y2 > y1)
                    {
                        //Handle the top to bottom swipe
                        Toast.makeText(this, "Top to Bottom swipe", Toast.LENGTH_SHORT).show ();
                    }
    
                    // Bottom to top swipe action -- I HIDE MY ACTIONBAR ON SCROLLUP
                    else
                    {
                        getActionBar().hide();
                        Toast.makeText(this, "Bottom to Top swipe [Hide Bar]", Toast.LENGTH_SHORT).show ();
                    }
                }
                return true;
            }
            return false;
        }
    }
    

    您还可以尝试控制滑动的速度,以将其检测为真正的滑动或滚动。

    希望这能帮到您。


    -1
    实际上,现在 Webview 不支持 Touch Actions。但是有一些解决方法可用; 我将通过一个长按的例子来展示它:我使用 Pointoption 因为我会获取元素的坐标并将其用于长按。
    public void longpress(PointOption po) {
       //first you need to switch to native view
        driver.switchToNativeView();
        TouchAction action = new TouchAction((PerformsTouchActions) driver);
        action.longPress(po).waitAction(WaitOptions.waitOptions(Duration.ofSeconds(2)));
        action.release();
        action.perform();
        driver.switchToDefaultWebView();
    }
    

    为了获取元素 i 的坐标,可以按照下面的方法进行设计。
     public PointOption getElementLocation(WebElement element) {
        int elementLocationX;
        int elementLocationY;
    
        //get element location in webview
        elementLocationX = element.getLocation().getX();
        elementLocationY = element.getLocation().getY();
    
        //get the center location of the element
        int elementWidthCenter = element.getSize().getWidth() / 2;
        int elementHeightCenter = element.getSize().getHeight() / 2;
        int elementWidthCenterLocation = elementWidthCenter + elementLocationX;
        int elementHeightCenterLocation = elementHeightCenter + elementLocationY;
    
        //calculate the ratio between actual screen dimensions and webview dimensions
        float ratioWidth = device.getDeviceScreenWidth() / ((MobileDevice) device)
                .getWebViewWidth().intValue();
        float ratioHeight = device.getDeviceScreenHeight() / ((MobileDevice) device)
                .getWebViewHeight().intValue();
    
        //calculate the actual element location on the screen , if needed you can increase this value,for example i used 115 for one of my mobile devices.
        int offset = 0;  
    
    
        float elementCenterActualX = elementWidthCenterLocation * ratioWidth;
        float elementCenterActualY = (elementHeightCenterLocation * ratioHeight) + offset;
        float[] elementLocation = {elementCenterActualX, elementCenterActualY};
    
        int elementCoordinateX, elementCoordinateY;
        elementCoordinateX = (int) Math.round(elementCenterActualX);
        elementCoordinateY = (int) Math.round(elementCenterActualY);
        PointOption po = PointOption.point(elementCoordinateX, elementCoordinateY);
        return po;
    }
    

    现在你有一个longpress(PointOption po)和getElementLocation(Webelement element)方法,它们会给你po。现在一切准备就绪,你可以按照以下方式使用它们...
        longpress(getElementLocation(driver.findElement(By.id("the selector can be any of them(xpath,css,classname,id etc.)")));
         
     
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接