[How to] Fixed page width on Android WebView or Cordova/PhoneGap (and disabling double click/tap to zoom)

The problem

While working on Scan Wars / Alien Blitz I came across a problem with fixed HTML page width in a WebView (and later on I had the same problem with PhoneGap).

Basically what I want in my UI is to have a fixed 800px width, so the screen should be stretched accordingly to the true mobile screen resolution.

I don’t want to use responsive design as I think it’s pretty useless for a UI constructed with images. It is very useful if your UI is made mostly thanks to CSS, but when using images everywhere (all the buttons, frames, … are made using 9 patch images) then you have to deliver resized images for all DPI (basically 3-4 sets of images resized to different resolution). It’s a lot of work, and it’s pretty useless.

The “solution”

Theoretically setting the page width is very easy, just google that problem, solution is to add a viewport meta tag in HTML:

<meta name="viewport" content="width=800"/>

And all should go smoothly on PhoneGap, default WebView users must also add this to their Java code:

WebSettings settings = myWebView.getSettings();
settings.setUseWideViewPort(true);

Ok, so everything’s fine… no ?

Welcome to the wonderful world of Android development

No, everything’s not fine at all, the major problem is that enabling the UseWideViewPort option will also enable… double tap to zoom… Why ? I have no idea, it doesn’t make any sense to me.

Activating fixed width on all devices

Basically we will try to activate fixed page width by every mean possible, I will focus on Cordova/PhoneGap, if you are using a WebView it is almost the same code, just a bit simplified (change reference of CordovaWebView to WebView, remove Cordova/PhoneGap specific code)

There are two methods provided, you should try the “defaultFixedViewport” solution first and see if it suits your needs. I personally had trouble with it on Android 4.4 when the page is larger than viewport width (ie when I want some horizontal scrolling), and the  “forceFixedViewport” works better in this case.

Setting WebView configuration

    private int getScale()
    {
        Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        int width = display.getWidth();
        Double val = new Double(width) / 800d;
        val = val * 100d;
        return val.intValue();
    }

    private void forceFixedViewport()
    {
        CordovaWebView myWebView = appView;
        WebSettings settings = myWebView.getSettings();

        settings.setLoadWithOverviewMode(false);
        // Activating viewport on Android 2.x will deactivate stretching
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
        {
            // Set default zoom to unzoom, no setting this will sometimes trigger zoom 100% on some phone (like double tap)
            // It seems to glitch on Android 2.x, a white screen will appear after enabling this option
            //settings.setDefaultZoom(WebSettings.ZoomDensity.FAR);
            // Force using viewport html statement, sadly it activates double tap to zoom
            settings.setUseWideViewPort(true);
        }
        // Try not to use default zoom (useful ?)
        settings.setSupportZoom(false);
        settings.setBuiltInZoomControls(false);
        // Set scale on devices that supports it
        myWebView.setPadding(0, 0, 0, 0);

        //Enable DOM storage, and tell Android where to save the Database
        //settings.setDatabasePath("/data/data/" + this.getPackageName() + "/databases/");

        int percentScale = getScale();
        myWebView.setInitialScale(percentScale);
    }

    private void defaultFixedViewport()
    {
        CordovaWebView myWebView = appView;
        WebSettings settings = myWebView.getSettings();
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        this.init();
        super.loadUrl(Config.getStartUrl());
        
        //Choose between two methods
        defaultFixedViewport();
        //forceFixedViewport();
    }

On PhoneGap the file to be modified is located in /platforms/android/src/…/, it’s called HelloWorld.java by default
This modification is local, and can’t be used with remote building directly

Deactivating double click/tap to zoom

To deactivate double click you will quickly find a solution on Google that makes use of a custom GestureListener, problem is it is not 100% reliable, sometimes zoom will still be triggered, I don’t know why (seems to happens randomly, more often if two thumbs are on screen, and it seems to be ok if selection is disabled). The other problem is that if user clicks a lot on something, all clicks will be ignored, and I don’t want that for Alien Blitz (I want the user to be able to click a lot on the “buy ammo” button for example, some clicks can be ignored, but not all).

In case everything fails (which shouldn’t happen), we will also detect zoom change and revert zoom when it occurs. Problem is that sometimes it will mess up with the UI (especially on VM), and it’s a bad user experience (page will zoom in and quickly zoom out)

PhoneGap/Cordova : override default WebView

First we need to extend the generic WebView (or CordovaWebView), it can be done easily if you are not using PhoneGap/Cordova. On these platforms you need to override the init method

    @Override
    public void init()
    {
        CordovaWebView webView = new SWWebView(this);
        CordovaWebViewClient webViewClient;
        webViewClient = new SWWebViewClient(this, webView);
        this.init(webView, webViewClient, new CordovaChromeClient(this, webView));
    }

Custom WebView

Create a new class (named SWWebView in our example) that extends WebView or CordovaWebView. I will let you work on the details (default constructor,…) and we will override the onTouchEvent function:

    private long    lastEventTime        = -1;

    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        long eventTime = ev.getEventTime();
        int action = ev.getAction();

        switch (action)
        {
        case MotionEvent.ACTION_MOVE:
        {
            return super.onTouchEvent(ev);
        }
        case MotionEvent.ACTION_DOWN:
        {
            return super.onTouchEvent(ev);
        }
        case MotionEvent.ACTION_UP:
        {
            if (System.currentTimeMillis() - lastEventTime < 1000)
            {
                //Cancel all touch actions
                ev.setAction(MotionEvent.ACTION_CANCEL);
                super.onTouchEvent(ev);
                //Restart the current action
                ev.setAction(MotionEvent.ACTION_DOWN);
                super.onTouchEvent(ev);
            }
            ev.setAction(MotionEvent.ACTION_UP);
            lastEventTime = System.currentTimeMillis();
            return super.onTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }

This should cancel all double tap events, preventing zooming. This code will send a cancel event to the WebView whenever two taps occur within 1000ms (We don’t use ViewConfiguration.getDoubleTapTimeout() as it doesn’t really seem reliable). As all events have been canceled a down event is sent back before restoring the original up event. This means all information about the previous down event (position,…) are lost in the process. This could be improved but I didn’t have the need to do it.

Custom WebViewClient

Second thing to do is to make sure the zoom is canceled whenever it is triggered (even if it shouldn’t be)

Create a new class named SWWebViewClient in this example, it must extends WebViewClient or CordovaWebViewClient. Do the details (constructor,…) and let’s override the onScaleChanged function:

    @Override
    public void onScaleChanged(WebView view, float oldScale, float newScale)
    {
        view.zoomOut();
    }

It should reset zoom whenever it is changed, with some little drawbacks (interface might be badly updated, bad user experience).

Conclusion

As far as I know this code should work on all 2.3+ devices. But it still needs some more testing and as not been widely tested, so I will update this post accordingly whenever I encounter a problem/solution.

Like all my posts on this blog comments are disabled (I got tired of having to deal with Spam), but you can contact me on Twitter if you need some more info : @Haedri
My email should also be available somewhere on this website (in the About section)

Source sample (Cordova/PhoneGap)

Here is a sample of the above modifications on the default HelloWorld project of PhoneGap

Download sample source folder

These 3 files are supposed to go to the /platforms/android/src folder. This will activate fixed width + deactivate double tap to zoom.
Warning! If you want to apply these modifications manually on your own project don’t forget to change the “super.init();” to “this.init();” of the default onCreate method.

PDF

Download the PDF version of this page here : android-fixed-width-20140131

Document history

2014-02-02

I noticed some problems with the easy solution, on Android 4.4 when the page is larger than viewport width (when there should be horizontal scrolling) then the viewport width seems to be ignored. So I’ve restored old solution and you can now choose between both.

There are still problems on Samsung devices…

2014-01-31

Added current PDF + sample source zip.

2014-01-30

Changed the code to lock page width; after some more testing it seems this new code works even better (and I can’t reproduce anymore the old problems I had on some devices, I have absolutely no idea why).
In case you get some errors don’t hesitate to contact me with your test reports, I’ve saved the old page in this PDF : android-fixed-width-20140130

2014-01-29

It seems this page is interesting for some people, so I’ve decided to put a license text (bottom of the page). It’s just a simple CC-by-attribution license, do what you want with this tutorial, but please give me some credit.

2013-12-06

Typo

2013-11-29

Spent a few hours digging the Android SDK source, and finally came up with a reliable solution to disable double tap to zoom, and not losing events (previous solution forbade tapping twice in less than 500ms, the webview was receiving only one tap event). We use a cancel event when needed to cancel all previous events.

2013-11-26

Added some links and a better explanation for GestureListener problem

Added a correct double tap delay using ViewConfiguration.getDoubleTapTimeout()

2013-11-24

Corrected code for Android 2.x, activating viewport on Android 2.x seems to deactivate completely stretching, and the viewport tag seems to be ignored.

I’ve also removed the default zoom on Android 2.x, once activated it displays a white screen instead of the page (which can be removed by tapping )

2013-11-21

First version

License

Creative Commons License
This page is licensed under a Creative Commons Attribution 4.0 International License.

Basically you can do almost anything you want as long as you give the correct attribution (link to my domain, my twitter, or this page for example)

The code itself (classes, functions,…) is not copyrighted (CC0), you can do whatever you want with it

Comments are closed.