Hybrid app is mixing native and web stuff toghther. Normal approch is using the cordova plugin to make javascript call native code to implement some native functions like open camera or retrive gps coordinates. But in this case is we need a webview and native view show on the same screen for a hybrid app. Like show an native mapview on screen but the navigation bar or other view on top of it are still using webview.
We can make some of the webview area to be transparent, and put the native view under that area with exact same size. The main challenges including for this solution:
For the first changllene, we can put the native map under a html tag with same position and size.
var pageRect = getPageRect(); var rect = div.getBoundingClientRect(); var divRect = [rect.left + pageRect.left, rect.top + pageRect.top, rect.width, rect.height];
while (div.parentNode) { div.style.backgroundColor = 'transparent'; div = div.parentNode; }
var children = getAllChildren(div); for (var i = 0; i < children.length; i++) { element = children[i]; elemId = "elementId" + i; elements.push({ id: elemId, rect: getDivRect(element) }); i++; } //native part, store every tag area touchLayout.addOverlayHtml(idStr, new RectF(marginLeft, marginTop, marginLeft + w, marginTop + h));
Web side is setted up, Now, we start preparing the native side:
touchLayout = new TouchLayout(cordova.getActivity(), mWebView); removeAllViews(); touchLayout.addView(mWebView); nativeViewContainer.addView(touchLayout, -1, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); //* @param index: the position at which to add the child or -1 to add last, from bottom to top, so if index is 0 it will put to bottom
addViewToNative(mapView, 0, lp);
After all these steps, the map can be show under the html tag which is transparent. So the next step is to solve the second problem:
For this task we need understand the basics inside View of Android:
public boolean dispatchTouchEvent(MotionEvent ev) //Pass the touch screen motion event down to the target view, or this view if it is the target.
public boolean onInterceptTouchEvent(MotionEvent ev) // only ViewGroup has it, ViewGroup is subclass of View //Implement this method to intercept all touch screen motion events. This method is called inside dispatchTouchEvent().
Events will be received in the following order:
public boolean onTouchEvent(MotionEvent ev) // Implement this method to handle touch screen motion events. Return value: True if the event was handled, false otherwise.
So we can override the function of onInterceptTouchEvent() and verify if the touch event happends inside the mapView. Even inside the mapView, we also need to verify if the click event happens on some html tags which are the children of the map tag.(on top of the map).
public boolean onInterceptTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); Rect rectf = new Rect(); MarginLayoutParams lp = (MarginLayoutParams) mMapView.getLayoutParams(); mMapView.getLocalVisibleRect(rectf); rectf.offset(lp.leftMargin, lp.topMargin); boolean notContains = true; if (rectf.contains(x, y)) { // Is the touch point on any HTML elements? Set elements = this.htmlNodes.entrySet(); Iterator> iterator = elements.iterator(); Map.Entry entry; while (iterator.hasNext()) { entry = iterator.next(); if (entry.getValue().contains(x, y)) { notContains = false; break; } } if (!notContains) { mWebView.requestFocus(View.FOCUS_DOWN); } return notContains; } return false; //outside of mapView } public boolean onTouchEvent(MotionEvent ev) { return false; // do not consume the event so it can pass to the next layer of ViewGroup }
So the logical is clear, if onInterceptTouchEvent return true, the event will be handled by the viewGroup so that the following event will be handled by onTouchEvent(). Here we hard coded onTouchEvent to return false, so it won't consume the event so it can pass to the next layer of ViewGroup
For the details about view event dispatch can reference here ViewGroup event dispatch