WebView getting rid of double tap zoom.
Asked Answered
C

6

10

I read many tickets on the topic of Zooming in WebViews and didnt came to an answer for my case.

Here´s my setup:

I´m using a custom webview with generally these settings:

getSettings().setBuiltInZoomControls(false);
getSettings().setSupportZoom(false);
getSettings().setUseWideViewPort(true);
getSettings().setLoadWithOverviewMode(true);

Let me note right here that i depend on OverviewMode and as well on WideViewPort to Scale my WebView.

I´m also Overriding my OnTouchEvent and and delegate all suitable events to an Gesture detector:

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (gestureDetector.onTouchEvent(event)) return true;
    return super.onTouchEvent(event);
  }

Here is its listeners implementation which is intercepting all doubleTap events:

  @Override
  public boolean onDoubleTapEvent(MotionEvent e) {
    // Do nothing! 
    return true;
  }

  @Override
  public boolean onDoubleTap(MotionEvent e) {
    // Do nothing! 
    return true;
  }

  @Override
  public boolean onSingleTapConfirmed(MotionEvent e) {
    // Do nothing! 
    return true;
  }

Also i overrode these 2 WebView methods related to zoom:

  @Override
  public boolean zoomIn() {
    return true;
  }

  @Override
  public boolean zoomOut() {
    return true;
  }

Notherless of all these options a certain tap frequence will cause my webview to zoom in/out. I havent found an option that disables this kind of zooming, the MotionEvent for this Zoom doesnt seem to be applicable for the GestureDetector and the override zoomIn() zoomOut() methods have no effect either.

Can anyone help me out with a way to avoid this double tap zoom behaivior of WebView?

Continually answered 6/9, 2012 at 13:58 Comment(1)
meta tags in the html see: developer.mozilla.org/en-US/docs/Mozilla/Mobile/…Metamer
E
7

There are two methods to achieve your goal:

Method 1

Implement the GestureDetector.OnDoubleTapListener like this:

@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
  return false; //Nothing
}

@Override
public boolean onDoubleTap(MotionEvent e) {
  //Indicates that this implementation has handled the double tap.
  return true;
}

@Override
public boolean onDoubleTapEvent(MotionEvent e) {
  //Indicates that this implementation has handled the double tap.
  return true;
}

and attach it to your GestureDetector like this:

gestureDetector.setOnDoubleTapListener(this);

Method 2

You can also use the WebSettings.setUseWideViewPort(false); and calculate the size of your view manually.

These methods should help you to achieve non-zoomable webviews that display everything.

public int getWindowWidth(Activity activity) {
  Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  Point size = new Point();
  display.getSize(size);
  int width = size.x;
  return width;
}

public int getInitialScale(Activity activity, int websiteWidth) {
  return (getWindowWidth(activity) / websiteWidth) * 100;
}
Erlene answered 7/9, 2012 at 11:26 Comment(3)
Unfortunaly the result is the same, the GestureDetector doesnt seem to work on a certain kind of motion that causes the webview to zoomIn/Out.Continually
Sorry, isnt turning out to be a substitution for my case. I think its because of the Overview mode does only work in combination with wideviewport.Continually
but how to use these function??Encomiastic
E
2

There's a fantastic solution for your issue, based on Javascript (so you'll have to have access to the HTML/JS code in the remote side, if it's the case).

Using the FastClick library, all you need to do is to add the .js file and then call it:

<script type="application/javascript" src="fastclick.js"></script>

<script language="javascript">

window.addEventListener('load', function() {
    FastClick.attach(document.body);
}, false);

</script>

This will get rid of the double tap zoom, and there's still (in my opinion) a huge bonus: all taps get 0.3 seconds faster, due to the fact the system doesn't have to wait anymore for a double tap! Check this example on Android to see the difference: Practical Example

Well, I don't know if this solution will fit in your case, but it was a perfect solution for my Webview projects. Hope it helps!

ps1: you have to add the code above in all pages and frames

ps2: the pinch zoom will keep working normally

Emcee answered 28/2, 2014 at 5:22 Comment(0)
P
1

You need to override OnTouchListener on your WebView by

    wv.setOnTouchListener(this);

and inside method onTouch just check that if it detect double tab then ignore the zoom in webview by force to return true

@Override
public boolean onTouch(View v, MotionEvent event) {
    // TODO Auto-generated method stub
    onTouchEvent(event);

    if (doubletab)
        return true;

    return false;
}

You can see full code like this: MainActivity.java

package com.example.testwebview;

import android.os.Bundle;
import android.app.Activity;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.View.OnTouchListener;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.TextView;

public class MainActivity extends Activity implements OnGestureListener, OnTouchListener, GestureDetector.OnDoubleTapListener
{

    TextView tv;
    WebView wv;
    GestureDetector gd;

    boolean doubletab = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        gd = new GestureDetector(this);

        setContentView(R.layout.activity_main);

        tv = (TextView)findViewById(R.id.textView1);
        wv = (WebView)findViewById(R.id.webView1);

        WebSettings setting = wv.getSettings();
        setting.setBuiltInZoomControls(false);
        setting.setSupportZoom(false);
        setting.setUseWideViewPort(true);
        setting.setLoadWithOverviewMode(true);

        wv.setOnTouchListener(this);
        wv.loadUrl("http://www.sanook.com");
    }
    @Override
    public boolean onDoubleTap(MotionEvent arg0) {
        tv.setText("double tap");
        doubletab = true;
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent arg0) {
        tv.setText("double tap event");
        doubletab = true;
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent arg0) {
        tv.setText("single tap confirm");
        doubletab = false;
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent me)
    {
        return gd.onTouchEvent(me);
    }

    @Override
    public boolean onDown(MotionEvent arg0) {
        tv.setText("down");
        doubletab = false;
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,
            float arg3) {
        tv.setText("fling");
        doubletab = false;
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void onLongPress(MotionEvent arg0) {
        tv.setText("long press");
        doubletab = false;
        // TODO Auto-generated method stub

    }

    @Override
    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2,
            float arg3) {
        tv.setText("scroll");
        doubletab = false;
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void onShowPress(MotionEvent arg0) {
        // TODO Auto-generated method stub
        tv.setText("show press");
        doubletab = false;

    }


    @Override
    public boolean onSingleTapUp(MotionEvent arg0) {
        // TODO Auto-generated method stub
        tv.setText("single tab up");
        doubletab = false;
        return false;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        onTouchEvent(event);

        if (doubletab)
            return true;

        return false;
    }
}

and activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="TextView" />

    <WebView
        android:id="@+id/webView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/textView1" />

</RelativeLayout>

don't forget to add permission in manifest.xml

<uses-permission android:name="android.permission.INTERNET" />
Petronel answered 11/9, 2012 at 4:50 Comment(7)
This catches the exact same double-tap events i´m already handling unfortunaly there are double tap motionEvents that arent recognized by the listener and double tapping in a certain frequency still leads to zoomIn/Out.Continually
Could you explain more, about motionEvents that aren't recognized? If it doesn't recognize by frequency or delayed tap, it become the about the interval. you have 2 choices that are - to override GestureDetector and increasing their interval. - to detect by onSingleTap and make some thread to detect in timePetronel
Its like that gestureDetector.onTouchEvent(me); actually returns false on the event that´s causing the zoom. Overriding GestureDetector sounds like a possiblity I´m gonna take a closer look on GestureDetector when i find the time.Continually
If it answer your question, you should mark it as correct answer :]Petronel
I´m currently on a different project and cant find the time to follow that theoretical possibility, proof that and how it works and bounty as well as correct answer is yours! ;-)Continually
@Konstantin i also have a need to override double tap i tried but after that itself double tap works can you please give me any suitable code so that the double tap will not work in webviewEncomiastic
gd = new GestureDetector(getApplicationContext(), this);Metamer
V
0

Another approach (the only one that worked for me) would be to simulate distant intermediate taps between double taps so that the WebView doesn't recongnize them as consequent. Negative coordinates can be used for this purpose, although anything less than -1 would slow down or even break the process. So we need at least two points, say (0, -1) and (2*d+1, -1) where d is the maximum distance between taps for them to be considered double tap.

webView.setOnTouchListener(new View.OnTouchListener() {
        private long lastUp = -1000;
        private float lastDownX = -1000;
        private float lastDownY = -1000;
        private int dtDistance = 0;         
        private int dtDistanceSquared = 0;
        private long dtTime = 0;

        private void performTap(View v, int x, int y, long t) {             
            MotionEvent e_down = MotionEvent.obtain(t, t, MotionEvent.ACTION_DOWN, x, y, 0);
            v.dispatchTouchEvent(e_down);
            e_down.recycle();

            MotionEvent e_up = MotionEvent.obtain(t, t, MotionEvent.ACTION_UP, x, y, 0);
            v.dispatchTouchEvent(e_up);
            e_up.recycle();
        }

        private int getRemoteX(float x) {
            return Math.round(x > dtDistance + 0.5 ? 0 : 2 * dtDistance + 1);
        }

        private boolean inRadius(int x0, int y0, float x1, float y1) {
            boolean result = (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1) <= dtDistanceSquared;
            return result;
        }

        @Override
        public boolean onTouch(View v, MotionEvent event) { 
            if (event.getY() >= 0)  // Otherwise it's a fake tap we simulated
            {               
                if (dtTime == 0)
                {
                    dtDistance = ViewConfiguration.get(v.getContext()).getScaledDoubleTapSlop();    // Maximum distance between taps for them to be considered double tap
                    dtDistanceSquared = dtDistance * dtDistance;
                    dtTime = ViewConfiguration.getDoubleTapTimeout();   // Maximum time elapsed between taps for them to be considered double tap
                }

                switch (event.getAction())
                {
                    case MotionEvent.ACTION_UP:
                        lastUp = event.getEventTime();
                        break;
                    case MotionEvent.ACTION_DOWN:
                        long t = event.getEventTime(); 
                        if (t - lastUp < dtTime * 4/3)  // Very rarely just (t - lastUp <= dtTime) doesn't work
                        {
                            int x = getRemoteX(event.getX());
                            if (inRadius(x, -1, lastDownX, lastDownY))
                                performTap(v, getRemoteX(lastDownX), -1, t);    // Otherwise our fake tap would constitute a double tap with the previous real tap
                            performTap(v, x, -1, t);                                
                        }
                        lastDownX = event.getX();
                        lastDownY = event.getY();
                        break;                      
                }
            }

            return false;                                   
        }
    });
Vapory answered 3/1, 2014 at 12:3 Comment(0)
B
0

If possible, replace the static meta tag in your html:

<script>
 var meta = document.createElement("meta");
 meta.setAttribute('name','viewport');
 meta.setAttribute('content','width=device-width, user-scalable=no');
 document.getElementsByTagName('head')[0].appendChild(meta); 
</script>

Additionally you can use a nice script: FastClick
It won't wait for tap events so its faster.

<script type="application/javascript" src="fastclick.js"></script>

    <script language="javascript">

        window.addEventListener('load', function() {
            FastClick.attach(document.body);
        }, false);

</script>

Then set a double tap listener to your gesture detector. (in custom WebView)

gestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() {

    @Override
    public boolean onSingleTapConfirmed( MotionEvent e ) {
        return false;
    }

    @Override
    public boolean onDoubleTapEvent( MotionEvent e ) {

        return true;
    }

    @Override
    public boolean onDoubleTap( MotionEvent e ) {

        return true;
    }
});

Override the onTouchEvent method (in custom WebView):

@Override
public boolean onTouchEvent( MotionEvent event ) {

    boolean gestureHandled = gestureDetector.onTouchEvent(event);
    int actionMask = event.getActionMasked() & MotionEvent.ACTION_MASK;
    int index = ( event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    int pointId = event.getPointerId(index);

    // ignore move events
    if (actionMask == MotionEvent.ACTION_MOVE) {

        return super.onTouchEvent(event);
    }

    // cancel detected double taps
    if (gestureDetector.onTouchEvent(event)) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        super.onTouchEvent(event);
        return true;
    }

    // if you want to ignore multi touch events/taps
    if (pointId != 0) {

        System.out.println("KEY multiple points detected");
        return true;
    }

    // use single taps
    if (event.getAction() == MotionEvent.ACTION_UP) {

        event.setAction(MotionEvent.ACTION_CANCEL);
        super.onTouchEvent(event);
        event.setAction(MotionEvent.ACTION_DOWN);
        super.onTouchEvent(event);
        event.setAction(MotionEvent.ACTION_UP);
        return true;
    }

return super.onTouchEvent(event);
}
Boyett answered 11/2, 2015 at 12:34 Comment(2)
Why exactly you have to dynamically change meta tag?Emcee
as a last resort, "user-scalable" has to be set to "no": user-scalable=noBoyett
M
-3

Try making the return values of onDoubleTapEvent onDoubleTap as false....

@Override
  public boolean onDoubleTapEvent(MotionEvent e) {
    // Do nothing! 
    return false;
  }

  @Override
  public boolean onDoubleTap(MotionEvent e) {
    // Do nothing! 
    return false;
  }
Moitoso answered 6/9, 2012 at 14:3 Comment(2)
true siganileses the event isnt consumed yet so it might be handled elsewhere. Tryed it out anyway, no effect as expected.Continually
Since this is more of a guess and doesn't help out I'd recommend you remove it.Continually

© 2022 - 2024 — McMap. All rights reserved.