Better scrolling
MODIFY
plugin.xml
plugin.xml
<?xml version='1.0' encoding='utf-8'?>
-<plugin id="cordova-plugin-googlemaps" version="2.1.2-beta-20171115-1911" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
+<plugin id="cordova-plugin-googlemaps" version="2.1.2-beta-20171117-2005" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>cordova-plugin-googlemaps</name>
<js-module name="BaseClass" src="www/BaseClass.js">
<runs />

MODIFY
src/android/plugin/google/maps/CordovaGoogleMaps.java
src/android/plugin/google/maps/CordovaGoogleMaps.java
@@ -334,7 +334,6 @@ public class CordovaGoogleMaps extends CordovaPlugin implements ViewTreeObserver
return;
}
mPluginLayout.isSuspended = true;
- Log.e(TAG, "---->pause");
callbackContext.success();
}
public void resume(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
@@ -342,7 +341,6 @@ public class CordovaGoogleMaps extends CordovaPlugin implements ViewTreeObserver
callbackContext.success();
return;
}
- Log.e(TAG, "---->resume");
if (mPluginLayout.isSuspended) {
mPluginLayout.isSuspended = false;
synchronized (mPluginLayout.timerLock) {

MODIFY
src/android/plugin/google/maps/MyPlugin.java
src/android/plugin/google/maps/MyPlugin.java
@@ -97,7 +97,6 @@ public class MyPlugin extends CordovaPlugin implements MyPluginInterface {
// this.create(args, callbackContext);
// return true;
- Log.d(TAG, "(debug)action=" + action + " containsKey=" + methods.containsKey(action));
if (methods.containsKey(action)) {
if (self.mapCtrl.mPluginLayout.isDebug) {
try {

MODIFY
src/android/plugin/google/maps/MyPluginLayout.java
src/android/plugin/google/maps/MyPluginLayout.java
@@ -76,7 +76,6 @@ public class MyPluginLayout extends FrameLayout implements ViewTreeObserver.OnSc
private class ResizeTask extends TimerTask {
@Override
public void run() {
- Log.d(TAG, "--->ResizeTask : isSuspended = " +isSuspended);
if (isSuspended) {
//Log.d(TAG, "--->ResizeTask : isSuspended = " +isSuspended);
synchronized (timerLock) {
@@ -492,8 +491,10 @@ public class MyPluginLayout extends FrameLayout implements ViewTreeObserver.OnSc
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (isSuspended || pluginMaps == null || pluginMaps.size() == 0) {
+ if (pluginMaps == null || pluginMaps.size() == 0) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
webView.loadUrl("javascript:if(window.cordova){cordova.fireDocumentEvent('plugin_touch', {});}");
+ }
return false;
}
MyPluginLayout.this.stopFlag = true;
@@ -578,7 +579,7 @@ public class MyPluginLayout extends FrameLayout implements ViewTreeObserver.OnSc
clickPoint.x <= (htmlElementRect.right) &&
clickPoint.y >= htmlElementRect.top &&
clickPoint.y <= htmlElementRect.bottom) {
- Log.d(TAG, "---> hit = " + domId);
+ //Log.d(TAG, "---> hit = " + domId);
isMapAction = false;
break;
}
@@ -594,8 +595,10 @@ public class MyPluginLayout extends FrameLayout implements ViewTreeObserver.OnSc
if (!isMapAction) {
browserView.requestFocus(View.FOCUS_DOWN);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
webView.loadUrl("javascript:if(window.cordova){cordova.fireDocumentEvent('plugin_touch', {});}");
}
+ }
MyPluginLayout.this.stopFlag = false;
return isMapAction;

MODIFY
src/ios/GoogleMaps/CordovaGoogleMaps.h
src/ios/GoogleMaps/CordovaGoogleMaps.h
- (void)removeMap:(CDVInvokedUrlCommand *)command;
- (void)pause:(CDVInvokedUrlCommand *)command;
- (void)resume:(CDVInvokedUrlCommand *)command;
-- (void)pauseResizeTimer:(CDVInvokedUrlCommand *)command;
-- (void)resumeResizeTimer:(CDVInvokedUrlCommand *)command;
@end

MODIFY
src/ios/GoogleMaps/CordovaGoogleMaps.m
src/ios/GoogleMaps/CordovaGoogleMaps.m
- (void)getMap:(CDVInvokedUrlCommand *)command {
if (self.pluginLayer != nil) {
self.pluginLayer.isSuspended = false;
- self.pluginLayer.pauseResize = false;
}
dispatch_async(dispatch_get_main_queue(), ^{
}
}
- self.pluginLayer.pauseResize = false;
- self.pluginLayer.isSuspended = false;
self.pluginLayer.isSuspended = false;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.pluginLayer resizeTask:nil];
}];
}
-- (void)resumeResizeTimer:(CDVInvokedUrlCommand *)command {
- if (self.pluginLayer != nil) {
- self.pluginLayer.pauseResize = false;
- }
- CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
- [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
-
-}
-- (void)pauseResizeTimer:(CDVInvokedUrlCommand *)command {
- if (self.pluginLayer != nil) {
- self.pluginLayer.pauseResize = true;
- }
- CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
- [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
-}
- (void)putHtmlElements:(CDVInvokedUrlCommand *)command {
[self.executeQueue addOperationWithBlock:^{

MODIFY
src/ios/GoogleMaps/MyPluginLayer.h
src/ios/GoogleMaps/MyPluginLayer.h
@property (nonatomic) MyPluginScrollView *pluginScrollView;
@property (nonatomic) NSTimer *redrawTimer;
@property (nonatomic) BOOL isSuspended;
-@property (nonatomic) BOOL pauseResize;
@property (nonatomic) BOOL stopFlag;
@property (nonatomic) NSOperationQueue *executeQueue;

MODIFY
src/ios/GoogleMaps/MyPluginLayer.m
src/ios/GoogleMaps/MyPluginLayer.m
// Assumes all touches for the browser
return;
}
- if (self.stopFlag || self.pauseResize) {
+ if (self.stopFlag) {
return;
}
self.stopFlag = YES;
}
dispatch_async(dispatch_get_main_queue(), ^{
- mapCtrl.isRenderedAtOnce = YES;
+ if (mapCtrl.isRenderedAtOnce) {
+ [UIView animateWithDuration:0.075f animations:^{
[mapCtrl.view setFrame:rect];
-
rect.origin.x = 0;
rect.origin.y = 0;
[mapCtrl.map setFrame:rect];
+ }];
+ } else {
+ [mapCtrl.view setFrame:rect];
+ rect.origin.x = 0;
+ rect.origin.y = 0;
+ [mapCtrl.map setFrame:rect];
+ mapCtrl.isRenderedAtOnce = YES;
+ }
+
});
}
}
- if (self.isSuspended || self.pluginScrollView.debugView.mapCtrls == nil || self.pluginScrollView.debugView.mapCtrls.count == 0) {
+ if (self.pluginScrollView.debugView.mapCtrls == nil || self.pluginScrollView.debugView.mapCtrls.count == 0) {
// Assumes all touches for the browser
- [self execJS:@"javascript:if(window.cordova){cordova.fireDocumentEvent('plugin_touch', {});}"];
return [self.webView hitTest:point withEvent:event];
}
UIView *hitView =[self.webView hitTest:point withEvent:event];
//NSLog(@"--> (hit test) hit = %@", hitView.class);
- [self execJS:@"javascript:(cordova && cordova.fireDocumentEvent('plugin_touch', {}));"];
return hitView;
}

MODIFY
www/Common.js
www/Common.js
@@ -274,6 +274,13 @@ function shouldWatchByNative(node) {
// Get z-index order
// http://stackoverflow.com/a/24136505
var internalCache = {};
+function _clearInternalCache() {
+ internalCache = undefined;
+ internalCache = {};
+}
+function _removeCacheById(elemId) {
+ delete internalCache[elemId];
+}
function getZIndex(dom) {
if (dom === document.body) {
internalCache = undefined;
@@ -320,7 +327,12 @@ function getDomDepth(dom, idx, parentDepth, floorLevel) {
}
// In order to handle this value as double anytime, add 0.01 (for Android)
var result = parentDepth + (getZIndex(dom) + 1 + idx) / (1 << floorLevel) + 0.01;
+/*
+ var currentDepth = parseFloat(dom.getAttribute("_depth")) || 0;
+ if (currentDepth != result) {
dom.setAttribute("_depth", result); // for debugging
+ }
+*/
return result;
}
@@ -703,6 +715,8 @@ function quickfilter(domPositions, minMapDepth) {
}
module.exports = {
+ _clearInternalCache: _clearInternalCache,
+ _removeCacheById: _removeCacheById,
getZIndex: getZIndex,
getDomDepth: getDomDepth,
deleteFromObject: deleteFromObject,

MODIFY
www/Map.js
www/Map.js
@@ -157,12 +157,10 @@ Map.prototype.getMap = function(mapId, div, options) {
}
- cordova.fireDocumentEvent('plugin_touch', {});
-
exec.call({
_isReady: true
}, function() {
- cordova.fireDocumentEvent('plugin_touch', {});
+ self._onTouchEvents();
//------------------------------------------------------------------------
// Clear background colors of map div parents after the map is created
@@ -170,6 +168,15 @@ Map.prototype.getMap = function(mapId, div, options) {
var div = self.get("div");
if (common.isDom(div)) {
while (div.parentNode) {
+ if (common.getStyle(div, "-webkit-overflow-scrolling") === "touch") {
+ // Disable scrolling, becase the `scroll` events are not fired.
+ div.style["-webkit-overflow-scrolling"] = "auto";
+ div.addEventListener('touchstart', self._onTouchEvents.bind(self));
+ div.addEventListener('touchmove', self._onTouchEvents.bind(self));
+ div.addEventListener('touchend', self._onTouchEvents.bind(self));
+ div.addEventListener('touchcancel', self._onTouchEvents.bind(self));
+ div.addEventListener('touchleave', self._onTouchEvents.bind(self));
+ }
div.style.backgroundColor = 'rgba(0,0,0,0)';
// prevent multiple readding the class
@@ -186,6 +193,7 @@ Map.prototype.getMap = function(mapId, div, options) {
// In order to work map.getVisibleRegion() correctly, wait a little.
//------------------------------------------------------------------------
setTimeout(function() {
+ self._onTouchEvents();
Object.defineProperty(self, "_isReady", {
value: true,
writable: false
@@ -628,6 +636,15 @@ Map.prototype.setDiv = function(div) {
while (div.parentNode) {
div.style.backgroundColor = 'rgba(0,0,0,0)';
+ if (common.getStyle(div, "-webkit-overflow-scrolling") === "touch") {
+ // Disable scrolling, becase the `scroll` events are not fired.
+ div.style["-webkit-overflow-scrolling"] = "auto";
+ div.addEventListener('touchstart', self._onTouchEvents.bind(self));
+ div.addEventListener('touchmove', self._onTouchEvents.bind(self));
+ div.addEventListener('touchend', self._onTouchEvents.bind(self));
+ div.addEventListener('touchcancel', self._onTouchEvents.bind(self));
+ div.addEventListener('touchleave', self._onTouchEvents.bind(self));
+ }
// prevent multiple readding the class
if (div.classList && !div.classList.contains('_gmaps_cdv_')) {
div.classList.add('_gmaps_cdv_');
@@ -639,7 +656,7 @@ Map.prototype.setDiv = function(div) {
}
}
exec.call(this, function() {
- cordova.fireDocumentEvent('plugin_touch', {});
+ cordova.fireDocumentEvent('plugin_touch', {force: true});
self.refreshLayout();
}, self.errorHandler, self.id, 'setDiv', args, {sync: true});
return self;
@@ -1518,4 +1535,8 @@ Map.prototype._onCameraEvent = function(eventName, cameraPosition) {
this.trigger(eventName, cameraPosition, this);
}
};
+
+Map.prototype._onTouchEvents = function() {
+ this.trigger('touchevent');
+};
module.exports = Map;

MODIFY
www/googlemaps-cdv-plugin.js
www/googlemaps-cdv-plugin.js
@@ -128,54 +128,95 @@ if (!cordova) {
var followPositionTimer = null;
var followPositionTimerCnt = 0;
+ var prevMapRects = {};
+ var scrollEndTimer = null;
function followMapDivPositionOnly() {
var mapRects = {};
var mapIDs = Object.keys(MAPS);
+ var changed = false;
+
mapIDs.forEach(function(mapId) {
var map = MAPS[mapId];
if (map && map.getVisible() && map.getDiv() && common.shouldWatchByNative(map.getDiv())) {
var mapDiv = map.getDiv();
var divId = mapDiv.getAttribute("__pluginDomId");
mapRects[divId] = {
- size: common.getDivRect(mapDiv),
+ size: mapDiv.getBoundingClientRect(),
depth: cacheDepth[divId]
};
+ if (!changed && prevMapRects && (divId in prevMapRects) && (
+ prevMapRects[divId].size.left !== mapRects[divId].size.left ||
+ prevMapRects[divId].size.top !== mapRects[divId].size.top ||
+ prevMapRects[divId].size.width !== mapRects[divId].size.width ||
+ prevMapRects[divId].size.height !== mapRects[divId].size.height ||
+ prevMapRects[divId].depth !== mapRects[divId].depth)) {
+ changed = true;
+ }
}
});
- cordova_exec(function() {
- cordova_exec(null, null, 'CordovaGoogleMaps', 'pause', []);
- isSuspended = true;
- }, null, 'CordovaGoogleMaps', 'updateMapPositionOnly', [mapRects]);
+ prevMapRects = mapRects;
+ if (changed) {
+ cordova_exec(null, null, 'CordovaGoogleMaps', 'updateMapPositionOnly', [mapRects]);
+ }
}
- document.body.addEventListener("scroll", followMapDivPositionOnly, true);
+ document.body.addEventListener("followMapDivPositionOnly",followMapDivPositionOnly, true);
+ document.body.addEventListener("transitionend",followMapDivPositionOnly, true);
+ document.body.addEventListener("scroll", function(e) {
+ if (scrollEndTimer) {
+ clearTimeout(scrollEndTimer);
+ }
+ scrollEndTimer = setTimeout(onScrollEnd, 100);
+ followMapDivPositionOnly();
+ }, true);
+ function onScrollEnd() {
+ isThereAnyChange = true;
+ idlingCnt = -1;
+ longIdlingCnt = -1;
+ common.nextTick(putHtmlElements);
+ }
function removeDomTree(node, options) {
options = options || {};
var children = node.querySelectorAll('[__pluginDomId]');
- if (!children) {
+ if (!children || children.length === 0) {
return;
}
children.forEach(function(child) {
- if (child.nodeType !== Node.ELEMENT_NODE) {
- return;
- }
var elemId = child.getAttribute('__pluginDomId');
if (!options.keepDomId) {
child.removeAttribute('__pluginDomId');
+ if (child.hasAttribute('__pluginMapId')) {
+ // If no div element, remove the map.
+ var mapId = child.getAttribute('__pluginMapId');
+ if (mapId in MAPS) {
+ MAPS[mapId].remove();
+ }
}
+ }
+ common._removeCacheById(elemId);
delete domPositions[elemId];
delete prevDomPositions[elemId];
delete cacheDepth[elemId];
+ delete prevFinal[elemId];
});
if (node.hasAttribute("__pluginDomId")) {
var elemId = node.getAttribute('__pluginDomId');
if (!options.keepDomId) {
- child.removeAttribute('__pluginDomId');
+ node.removeAttribute('__pluginDomId');
+ if (node.hasAttribute('__pluginMapId')) {
+ // If no div element, remove the map.
+ var mapId = child.getAttribute('__pluginMapId');
+ if (mapId in MAPS) {
+ MAPS[mapId].remove();
}
+ }
+ }
+ common._removeCacheById(elemId);
delete domPositions[elemId];
delete prevDomPositions[elemId];
delete cacheDepth[elemId];
+ delete prevFinal[elemId];
}
}
//----------------------------------------------
@@ -189,38 +230,37 @@ if (!cordova) {
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
+ var targetCnt = 0;
if (mutation.type === "childList") {
- if (mutation.removeNodes) {
- mutation.removeNodes.forEach(function(node) {
+ if (mutation.addedNodes) {
+ mutation.addedNodes.forEach(function(node) {
if (node.nodeType !== Node.ELEMENT_NODE) {
return;
}
- var elemId = node.getAttribute("__pluginDomId");
- if (!elemId) {
+ targetCnt++;
+ setDomId(node);
+ });
+ }
+ if (mutation.removedNodes) {
+ mutation.removedNodes.forEach(function(node) {
+ if (node.nodeType !== Node.ELEMENT_NODE || !node.hasAttribute("__pluginDomId")) {
return;
}
-
+ targetCnt++;
removeDomTree(node);
-
- isThereAnyChange = true;
- idlingCnt = -1;
- longIdlingCnt = -1;
});
}
+ if (targetCnt > 0) {
isThereAnyChange = true;
idlingCnt = -1;
longIdlingCnt = -1;
-
common.nextTick(putHtmlElements);
- } else {
- if (mutation.target.nodeType !== Node.ELEMENT_NODE) {
- return;
}
- var elemId = mutation.target.getAttribute("__pluginDomId");
- if (!elemId) {
+ } else {
+ if (mutation.target.nodeType !== Node.ELEMENT_NODE ||
+ !mutation.target.hasAttribute("__pluginDomId")) {
return;
}
- //console.log('style', elemId, mutation);
removeDomTree(mutation.target, {
keepDomId: true
});
@@ -228,6 +268,8 @@ if (!cordova) {
idlingCnt = -1;
longIdlingCnt = -1;
common.nextTick(putHtmlElements);
+ //var elemId = mutation.target.getAttribute("__pluginDomId");
+ //console.log('style', elemId, common.shouldWatchByNative(mutation.target), mutation);
}
});
@@ -235,13 +277,23 @@ if (!cordova) {
observer.observe(document.body.parentElement, {
attributes : true,
childList: true,
- subtree: true,
- attributeOldValue: true,
- attributeFilter : ['style']
+ subtree: true
});
})();
+ function setDomId(element) {
+ var elemId = element.getAttribute("__pluginDomId");
+ if (!elemId) {
+ elemId = "pgm" + Math.floor(Math.random() * Date.now());
+ element.setAttribute("__pluginDomId", elemId);
+ }
+ for (var i = 0; i < element.children.length; i++) {
+ setDomId(element.children[i]);
+ }
+ }
+
+
//----------------------------------------------
// Send the DOM hierarchy to native side
//----------------------------------------------
@@ -256,7 +308,21 @@ if (!cordova) {
return;
}
if (!isThereAnyChange || mapIDs.length === 0) {
+ if (!isSuspended) {
cordova_exec(null, null, 'CordovaGoogleMaps', 'pause', []);
+ }
+ if (mapIDs.length === 0) {
+ // If no map, release the JS heap memory
+ domPositions = undefined;
+ prevDomPositions = undefined;
+ cacheDepth = undefined;
+ prevFinal = undefined;
+ domPositions = {};
+ prevDomPositions = {};
+ cacheDepth = {};
+ prevFinal = {};
+ common._clearInternalCache();
+ }
isSuspended = true;
isThereAnyChange = false;
isChecking = false;
@@ -322,7 +388,6 @@ if (!cordova) {
isThereAnyChange = false;
isChecking = false;
cordova_exec(null, null, 'CordovaGoogleMaps', 'pause', []);
- console.log('--->!shouldUpdate && idlingCnt > -1');
return;
}
idlingCnt = 0;
@@ -344,9 +409,11 @@ if (!cordova) {
var ele = document.querySelector("[__pluginMapId='" + mapId + "']");
if (!ele) {
// If no div element, remove the map.
+ if (mapId in MAPS) {
MAPS[mapId].remove();
}
}
+ }
domPositions[elemId] = {
size: {
@@ -378,16 +445,21 @@ if (!cordova) {
return currentKeys.indexOf(prevKey) === -1;
});
if (diff.length === 0) {
+ if (!isSuspended) {
+ cordova_exec(null, null, 'CordovaGoogleMaps', 'pause', []);
+ }
isSuspended = true;
isChecking = false;
- cordova_exec(null, null, 'CordovaGoogleMaps', 'pause', []);
return;
}
}
//-----------------------------------------------------------------
// Pass information to native
//-----------------------------------------------------------------
+ if (isSuspended) {
cordova_exec(null, null, 'CordovaGoogleMaps', 'resume', []);
+ isSuspended = false;
+ }
cordova_exec(function() {
prevDomPositions = domPositions;
mapIDs.forEach(function(mapId) {
@@ -436,9 +508,8 @@ if (!cordova) {
depth = common.getDomDepth(element, domIdx, parentDepth, floorLevel);
cacheDepth[elemId] = depth;
}
- rect = common.getClickableRect(element, parentRect);
-
// Calculate dom clickable region
+ rect = common.getClickableRect(element, parentRect);
// Stores dom bounds and depth
domPositions[elemId] = {
@@ -491,10 +562,8 @@ if (!cordova) {
depth = common.getDomDepth(element, domIdx, parentDepth, floorLevel);
cacheDepth[elemId] = depth;
}
- rect = common.getClickableRect(element, parentRect);
- } else {
- removeDomTree(element);
}
+ removeDomTree(element);
}
} else {
doNotTrace = true;
@@ -515,18 +584,26 @@ if (!cordova) {
}
}
}
- };
+ }
// This is the special event that is fired by the google maps plugin
// (Not generic plugin)
- function resetTimer() {
- if (!isMutationObserver) {
+ function resetTimer(opts) {
+ opts = opts || {};
+ if (!isMutationObserver || opts.force) {
idlingCnt = -1;
longIdlingCnt = -1;
+ if (!isMutationObserver) {
cacheDepth = {};
cacheZIndex = {};
+ }
isThereAnyChange = true;
}
- common.nextTick(putHtmlElements);
+ common.nextTick(function() {
+ putHtmlElements();
+ if (opts.force) {
+ followMapDivPositionOnly();
+ }
+ });
}
document.addEventListener("deviceready", putHtmlElements, {
@@ -534,11 +611,8 @@ if (!cordova) {
});
document.addEventListener("plugin_touch", resetTimer);
window.addEventListener("orientationchange", function() {
- isThereAnyChange = true;
- idlingCnt = -1;
- longIdlingCnt = -1;
cordova_exec(null, null, 'CordovaGoogleMaps', 'resume', []);
- common.nextTick(putHtmlElements);
+ resetTimer({force: true});
});
//----------------------------------------------------
@@ -596,7 +670,7 @@ if (!cordova) {
}
};
- }());
+
/*****************************************************************************
* Name space
@@ -661,11 +735,28 @@ if (!cordova) {
MAPS[mapId].destroy();
delete MAPS[mapId];
map = undefined;
+
+ if ((Object.keys(MAPS)).length === 0) {
+ // If no map, release the JS heap memory
+ domPositions = undefined;
+ prevDomPositions = undefined;
+ cacheDepth = undefined;
+ prevFinal = undefined;
+ domPositions = {};
+ prevDomPositions = {};
+ cacheDepth = {};
+ prevFinal = {};
+ common._clearInternalCache();
+
+ isSuspended = true;
+ cordova_exec(null, null, 'CordovaGoogleMaps', 'pause', []);
+ }
});
+ map.on('touchevent', followMapDivPositionOnly);
MAP_CNT++;
MAPS[mapId] = map;
isThereAnyChange = true;
- cordova.fireDocumentEvent('plugin_touch', {});
+ //cordova.fireDocumentEvent('plugin_touch', {});
var args = [mapId];
for (var i = 0; i < arguments.length; i++) {
@@ -690,6 +781,9 @@ if (!cordova) {
}
};
+
+ }());
+
cordova.addConstructor(function() {
if (!window.Cordova) {
window.Cordova = cordova;

Better scrolling