62

TL;DR

How can I detect whether Android WebView consumed a touch event? onTouchEvent always returns true and WebViewClient's onUnhandledInputEvent is never triggered.

Detailed description

I have multiple WebViews inside a TwoDScrollView. As its name suggests, the TwoDScrollView can be scrolled both vertically and horizontally. The contents of TwoDScrollView can be zoomed in / out. When the user drags his finger or uses pinch-to-zoom, I want to dispatch the touch event to:

  • WebView if its content is scrollable / zoomable (i.e. only the inside of the WebView will scroll / zoom)
  • TwoDScrollView if the above condition is false (all contents of the TwoDScrollView will scroll / zoom)

I have partially achieved this by using the canScrollHorizontally and canScrollVertically methods. But these methods only work for "native scrolling". However, in some cases, some JavaScript inside the WebView consumes the touch event, for example Google Maps. In this case, the methods return false. Is there any way to find out whether the WebView's contents consumes the touch events, i.e. is scrollable / zoomable? I cannot change the contents of the WebView, therefore my question is different from this one.

I have considered checking touch handlers by executing some JavaScript inside the Webview by the evaluateJavaScript method, but according to this answer there is no easy way to achieve this and also the page can have some other nested iframes. Any help will be appreciated.

What I've already tried

  1. I overrode WebView's onTouchEvent and read super.onTouchEvent() which always returns true, no matter what.
  2. canScrollHorizontally and canScrollVertically only partially solve this problem, as mentioned above
  3. onScrollChanged isn't useful either
  4. WebViewClient.onUnhandledInputEvent is never triggered
  5. I considered using JavaScript via evaluateJavaScript, but it is a very complicated and ugly solution
  6. I tried to trace the MotionEvent by Debug.startMethodTracing. I found out it is propagated as follows:
    • 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
    • According to ContentViewCore's source code the touch event ends up in a native method nativeOnTouchEvent and I don't know what further happens with it. Anyway, onTouchEvent always returns true and even if it was possible to find out somewhere whether the event was consumed or not, it would require using private methods which is also quite ugly.

Note

I don't need to know how to intercept touch events sent to WebView, but whether the WebView is consuming them, i.e. is using them for doing anything, such as scrolling, dragging etc.

Miloš Černilovský
  • 3,846
  • 1
  • 28
  • 30
  • Have you tried looking at this link http://stackoverflow.com/questions/12578895/how-to-detect-a-swipe-gesture-on-webview. – Smashing May 04 '15 at 14:14
  • wait I'm not clear. have you tried just calling webview.setOnTouchListener ? – dabluck May 04 '15 at 14:45
  • @Smashing: I know how intercept touch events to WebView, but I need to know whether the WebView consumed them. For example, if the user is dragging finger and some content in the WebView is using it (e.g. map is scrolling), I need to get "true". Otherwise, if nothing in the WebView uses the touch (no scrollable content etc.), I need to get "false" so that I can use the touch event for my own use. – Miloš Černilovský May 05 '15 at 07:37
  • @dabluck setOnTouchListener allows me getting touch events sent to WebView, but doesn't tell me, whether the WebView consumed these events. – Miloš Černilovský May 05 '15 at 07:39
  • Okay what about adding the following inside the ontouchlistener : WebView.HitTestResult hr = ((WebView) v).getHitTestResult(); Log.e("WEBVIEW", "getExtra = " + hr.getExtra() + "\t\t Type=" + hr.getType()); Then check whether the type might help you a bit?. – Smashing May 05 '15 at 08:10
  • if the touch events are consumed by the webview, they will not propagate to the parent. perhaps you can solve this by setting a touch listener on the parent and seeing which ones propagate? – dabluck May 05 '15 at 14:55
  • 1
    Here's a similar issue on the chromium issue tracker : https://code.google.com/p/chromium/issues/detail?id=515799 – Twibit Aug 24 '15 at 06:25
  • @MilošČernilovský I think you need to create an Interface inside your activity. Ontouch of an element just call the corresponding function from javascript to your apps activity. I have tried this to get data and event from web URL. – Mithilesh Izardar May 25 '16 at 11:38
  • Have you seen this solution? [http://stackoverflow.com/a/33095287/3617133](http://stackoverflow.com/a/33095287/3617133) Hope that helps =] – ErickBergmann Sep 20 '16 at 19:17
  • Nope, that code only detects whether a link was clicked in the WebView, I need to detect if the WebView consumed ANY touch event, which can also be some kind of scrolling or other interaction with the website's content. – Miloš Černilovský Sep 20 '16 at 19:26

5 Answers5

1

According to this issue report, not possible. If the web code is under your control, you can implement some JavaScriptInterface to workaround this. If not, I am afraid there is no solution here.

hqzxzwb
  • 4,661
  • 2
  • 14
  • 15
0

You can pass all touch events to GestureDetector by overriding onTouchEvent of WebView, so you can know when Android WebView is consuming touch events anywhere, anytime by listening to GestureDetector.

Try like this:

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();
    }
}

I hope you be inspired.

SilentKnight
  • 13,761
  • 19
  • 49
  • 78
  • I understand GestureDetector helps me transforming raw series of MotionEvents into some gesture such as fling, scroll double tap etc., but it doesn't solve my problem, which is determining whether WebView used the touch event for some internal handling. – Miloš Černilovský May 11 '15 at 12:53
-1

This code will handle your scrolling events in a webview. This catch the click down and the click up events, and compares the positions of each one. It never minds that the content within the webview is scrollable, just compare the coordinates in the area of 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;
    }
}

You can also try to control the speed of the swipe, to detect it as a real swipe or scrolling.

I hope that helps you.

Juan C. V.
  • 537
  • 5
  • 19
-1

Try to set the android:isClickable="true" in the XML and create an onClickListener in the Java code.

johnnyRose
  • 7,310
  • 17
  • 40
  • 61
Hradschek
  • 11
  • 2
-1

Actually now Touch Actions are not supported for webview. But some workarounds are available; I am going to show it with a longpress example : I am using Pointoption because i will get the coordinate of element and will use it for longpress.

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();
}

For to get the coordinate of element i designed below methood

 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;
}

now you have a longpress(PointOption po) and getElementLocation(Webelement element) methods that gives you po. Now everything is ready and you can use them as below..

    longpress(getElementLocation(driver.findElement(By.id("the selector can be any of them(xpath,css,classname,id etc.)")));
     
 
Hacci56
  • 39
  • 5