Zooming gestures on Android

2539
10
03-02-2012 11:50 AM
YohanBienvenue1
New Contributor
Hi,

I'm currently making an hybrid webapp using jQueryMobile 1.1.0-RC1, PhoneGap 1.4.1 and the ArcGIS API 2.7
I'm not able to make zooming gestures work on my Samsung Galaxy Tab 10.1 with Android 3.2.

Is this something that is documented not to be supported right now?
If so, does anyone know if this is something that can be worked around using PhoneGap?

Thanks
0 Kudos
10 Replies
YohanBienvenue1
New Contributor
Ok let me rephrase my question.

Does the map element of the ArcGIS API for JavaScript handle touch events (e.g. touchStart, touchEnd) in a way that multi-touch pinch zoom should be recognized and the map zoom automatically, or do I have to handle these touch events myself and manually zoom through the map zoom methods?

Thanks
0 Kudos
YohanBienvenue1
New Contributor
Assuming the API does not handle some touch events for the map (still a mystery), I was able to manually add pinch zoom in/out in my webapp.

The latest version of Jquery Mobile (1.1.0-RC1) does not support pinch touch events, but you can add them with a plugin, like Fidget.

This is what I have for now:

$("#mapDiv").fidget({ pinch: function(event, fidget){           
   switch(fidget.pinch.direction){
       case "in": map.setLevel(map.getLevel() - 1); break;
       case "out": map.setLevel(map.getLevel() + 1); break;
       case "unknown": break;
   }
 } })


It's still rudimentary of course, but I just wanted to prove you can use pinch gestures in hybrid mobile applications in this way.
I still need to figure out how to center the map at the location of the pinch (instead of simply zooming), currently experimenting with the centerAndZoom method.

Hopefully this can help someone trying to do something similar.
0 Kudos
EricPollard2
New Contributor III
I would like to try and add the pinch and zoom to my app as well. I am not as experienced with building apps, but I have been able to work my way through some changes. Can you tell me where to add the code you posted for this and where the plugin code needs to go to achieve the pinch and zoom functionality? I am working with the "basic viewer" downloadable javascript template from arcgis online.
I appreciate the help! !
-Eric
0 Kudos
YohanBienvenue1
New Contributor
I would like to try and add the pinch and zoom to my app as well. I am not as experienced with building apps, but I have been able to work my way through some changes. Can you tell me where to add the code you posted for this and where the plugin code needs to go to achieve the pinch and zoom functionality? I am working with the "basic viewer" downloadable javascript template from arcgis online.
I appreciate the help! !
-Eric


Hi Eric,

Well like I said in my previous post, this is pretty experimental code. I just got it working today and I'm still making adjustments and figuring things out as we speak. If you want to try too be my guest, but don't expect everything to work perfectly.

Also note that I prefer using jQuery when possible. ESRI prefers using Dojo, so we have to use it too when doing certain things with the API. It's up to you what you want to use in your own project. Both can be used simultaneously too, it's all just JavaScript.

So, basically you just download the fidget plugin from here http://www.simonboak.co.uk/fidget/ and put the jquery.fidget.beta.1.js file somewhere in your web project. Then in your index.html you include it after jQuery (the plugin extends it) by including the script the usual way. Here's my <head> right now, it should give you an idea:

<!DOCTYPE HTML>
<html>

<head>
   <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
   <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
   <title>Title</title>
   
   <script>
      var dojoConfig = {
         //Required if you have dojo widgets (aka.dijits) in your HTML markup 
         parseOnLoad: true
      };
   </script>   
   
   <!-- ArcGIS JSAPI 2.7, includes Dojo 1.6.1 and Dojo Widgets -->
   <link href="http://serverapi.arcgisonline.com/jsapi/arcgis/2.7/js/dojo/dijit/themes/claro/claro.css" rel="stylesheet" type="text/css" />
   <link rel="stylesheet" type="text/css" href="http://serverapi.arcgisonline.com/jsapi/arcgis/2.7/js/esri/dijit/css/Popup.css"/>
   <script src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.7" type="text/javascript"></script>      
   
   <!-- jQuery Mobile 1.1.0-RC1 and jQuery 1.7.1  -->
   <link rel="stylesheet" href="jQuery/jquery.mobile-1.1.0-rc.1.min.css" />
   <link rel="stylesheet" href="jQuery/jquery-mobile-custom.css" />     <!-- [YB 2012/02/29 We can use this file to modify the default css of jQuery Mobile] -->
   <script src="jQuery/jquery-1.7.1.min.js"></script>
   <!--<script src="jQuery/jQuery-mobile-custom.js"></script>-->        <!-- [YB 2012/02/27 We can use this file to modify the global defaults of jQuery Mobile] -->
   <script type="text/javascript">                                      <!-- [YB 2012/03/02 I tried putting this custom script in an external js file, but it doesn't work with PhoneGap, mobileinit is not fired. I can't figure out why right now] -->
      // Edit jQuery Mobile global defaults
      $(document).bind("mobileinit", function(){
   
         // Make sure a back button is always available on all pages
         $.mobile.page.prototype.options.addBackBtn=true;
         $.mobile.page.prototype.options.backBtnText = "Précédent";
         $.mobile.page.prototype.options.backBtnTheme = "a";
         $.mobile.page.prototype.options.backBtnIcon = "back";
         
         // 1.1.0-RC1 forces "fade" for page transitions on Android. This code puts back the "slide" transition.
         // [YB 2012/03/02 : I've actually disabled all page transitions for now, to maximize performance. Might re-enable them later, if future jQueryMobile releases improves performance]
         $.mobile.defaultPageTransition = "none";
         $.mobile.transitionFallbacks.slide = "none";
         
         // Disable any effect when displaying dialogs
         $.mobile.defaultDialogTransition = "none";
         
         // Disable tapToggle for headers and footers (DOESN'T WORK)
         $("[data-role=header]").fixedtoolbar({ tapToggle: false });
         $("[data-role=footer]").fixedtoolbar({ tapToggle: false });
      });
   </script>
   <script src="jQuery/jquery.mobile-1.1.0-rc.1.min.js"></script>  
   
   <!-- Mobiscroll plugin - DatePicker widget for mobiles -->
   <link href="plugins/mobiscroll/mobiscroll-1.5.3.min.css" rel="stylesheet" type="text/css" />
   <script src="plugins/mobiscroll/mobiscroll-1.5.3.min.js" type="text/javascript"></script>
     
   <!-- Fidget plugins for additional touch gestures (e.g. pinch) -->
   <script src="plugins/fidget/jquery.fidget.beta.1.js" type="text/javascript"></script>
   
   <!-- PhoneGap 1.4.1 -->
   <script type="text/javascript" charset="utf-8" src="phonegap/phonegap-1.4.1.js"></script>
   
   <!-- Our stuff -->
   <link rel="stylesheet" href="index.css" />
   <script type="text/javascript" charset="utf-8" src="extents.js"></script>
   <script type="text/javascript" charset="utf-8" src="index.js"></script>
   
</head>


Then you can put the code in the jQuery ready function (in my case this is in my index.js file).
The mapDiv element is the div used for your ESRI map object, so you're basically saying you want this handler to be called when the user pinches it.
I just manually zoom by 1 right now, but there's most likely a better way to do this. Work in progress.

$(document).ready(function(){
$("#mapDiv").fidget({ pinch: function(event, fidget){
      //var mapPoint = map.toMap(new esri.geometry.Point(event.x, event.y));
      
      switch(fidget.pinch.direction){
         case "in": map.setLevel(map.getLevel() - 1); fidget.pinch.direction = "unknown"; break;
         case "out": map.setLevel(map.getLevel() + 1); fidget.pinch.direction = "unknown"; break;
         //case "in": map.centerAndZoom(mapPoint, map.getLevel() - 1); break;
         //case "out": map.centerAndZoom(mapPoint, map.getLevel() + 1); break;
         case "unknown": break;
      }
   } });
});
0 Kudos
EricPollard2
New Contributor III
Ok I am going to be trying to implement this over the next few days. I appreciate all the help! Certainly learning a great deal through this forum. I will let you know how it goes ... Good luck with your app!
-Eric
0 Kudos
YohanBienvenue1
New Contributor
Ok I am going to be trying to implement this over the next few days. I appreciate all the help! Certainly learning a great deal through this forum. I will let you know how it goes ... Good luck with your app!
-Eric


Ok good luck too.

Btw if you use this plugin know that it will disable any click event you may have registered with your map element ( e.g  dojo.connect(map, "onClick", onMapClick);  ) because it calls event.preventDefault() in the touchstart event handler. What I have done to fix this (for now) is comment that line out in jquery.fidget.beta.1.js and call event.preventDefault() myself in my pinch handler function.

function onMapPinch(event, fidget){
   
   if (fidget.pinch.status == "end"){
      switch(fidget.pinch.direction){
         case "in": map.setLevel(map.getLevel() - 1); fidget.pinch.direction = "unknown"; break;
         case "out": map.setLevel(map.getLevel() + 1); fidget.pinch.direction = "unknown"; break;
         case "unknown": break;
      }
   }
   else if (fidget.pinch.status == "move"){
      event.preventDefault();
   }
   
   //$("#pinchStatus").html($("#pinchStatus").html() + fidget.pinch.status + ", ");
}


In other words, I want to make sure that if I detect a pinch then I don't want the map click handler to be called, but if the user does just tap the screen then it should be called.

But anyway, still work in progress.
0 Kudos
AndyGup
Esri Regular Contributor
In addition to what has been posted above, this is an FYI with background info for others looking to implement pinch gestures with the Android browser and JavaScript. At this time, support for multi-touch is not consistent on the Android browser across various phones and Android OS versions. It is, however, available on many of the latest phones using Android v3+. Note that jQuery and Sencha, just to mention a few, face the similar problems in supporting this.

So, for now, Dojo mobile best practices are to disable scaling in the browser via the viewport metatag. You can test that pinch sitll works on a particular phone by simply removing the meta tag and re-testing your app:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>


Also, if you haven't already seen it here's a "find nearby" example of implementing Dojo mobile with the ArcGIS API for Javascript.

Addt'l References:

http://code.google.com/p/android/issues/detail?id=11909

http://www.sitepen.com/blog/2011/12/07/touching-and-gesturing-on-iphone-android-and-more/

-Andy
0 Kudos
YohanBienvenue
Occasional Contributor II
Thanks for the update Andy, I understand multi-touch wasn't really available in previous versions of Android. I'm hoping that by the end of the summer most devices (that can support it) will use ICS and support this feature.

In the meantime I've continued with my own custom solution. I'm still planning to allow users to zoom manually, because I understand I can't rely on pinch exclusively atm.
I've improved it a bit, zooming in now takes consideration of the position of the fingers when doing the pinch. Anyone like me who wants to play with this, maybe this can inspire you:

However note I'm only testing on a Samsung Galaxy Tab 10.1 with Android 3.2, so ymmv

When the map is ready:
$("#mapDiv").fidget({ pinch: function(){ _this.onMapPinch.apply(_this, arguments); } });


The pinch handler
MapPage.prototype.onMapPinch = function(event, fidget){
   
   if (fidget.pinch.status == "end"){
           
      switch(fidget.pinch.direction){

         case "out": // Zoom in
            
            var midpoint = this.calculateMidpoint(fidget.pinch.touches);            
            midpoint = this.map.toMap( new esri.geometry.Point(midpoint.x, midpoint.y) );            
            this.map.centerAndZoom(midpoint, this.map.getLevel() + 1);
            fidget.pinch.direction = "unknown";
            break;
      
         case "in":  // Zoom out
                       
            this.map.setLevel(this.map.getLevel() - 1);
            fidget.pinch.direction = "unknown";
            break;
           
         case "unknown": break;
      }
   }
   else if (fidget.pinch.status == "move" || fidget.pinch.status == "start"){
      event.preventDefault();
   }
};

MapPage.prototype.calculateMidpoint = function(touches){
   var midx = 0;
   var midy = 0;
   if (touches.length > 1) {
      midx = (touches[0].pageX + touches[1].pageX) / 2;
      midy = (touches[0].pageY + touches[1].pageY) / 2;
      midx -= $("#mapDiv").offset().left;
      midy -= $("#mapDiv").offset().top;
   }
   return { x: midx, y: midy };
};


The 2 changes I made in the fidget plugin:
1- Commented out first line of the fidgetStart function

  //event.preventDefault();

2- Saved the start position of the two pinch "fingers":

function callPinchHandler() {
 if (defaults.pinch) {
  //if (defaults.zoomThis) {
   //var zoomFactor = calculateDistance() / fidget.pinch.startDistance;
   //console.log(fidget.pinch.distance + ' ' + calculateDistance());
   //$this.css('webkitTransform', 'scale(' + zoomFactor + ')');
                //}
      
  if (fidget.pinch.status == "start"){
   fidget.pinch.touches = event.touches;          // [YB 2012/05/10 : Save the positions so we can calculate the midpoint later, for our map pinch zoom]
  }
      
  defaults.pinch.call($this, event, fidget);
         }
}
0 Kudos
AndyGup
Esri Regular Contributor
Hi Yohan, sweet! Any chance you have the app running somewhere that's publicly accessible, even if it's still a bit rough? I could give it a spin on a couple different devices.

-Andy
0 Kudos