per-message zoom using JavaScript + CSS 3D transforms
When Auto-fit is off, disable built-in WebView zooming, which
is only capable of zooming an entire document (the entire
conversation).
Use ScaleGestureDetector to trigger a CSS 3D transform to zoom
in/out just a single message div.
During the gesture, the elements above and below the message
being scaled are untouched. This is ugly.
TODO: they should either move away (tricky) or at least fade
out.
When the gesture is complete, to avoid leaving overlapping
elements, we force-scale the height and reset the transform
origin to the top left.
TODO: auto-scroll to the correct place to counteract this
perceived jump.
Double-tap is not yet implemented. We'll have to do it
ourselves.
Bug: 7478834
Change-Id: I114e4977304c7060d499d116cc75bc0488967448
diff --git a/src/com/android/mail/browse/ConversationWebView.java b/src/com/android/mail/browse/ConversationWebView.java
index 857db7a..f17ca3c 100644
--- a/src/com/android/mail/browse/ConversationWebView.java
+++ b/src/com/android/mail/browse/ConversationWebView.java
@@ -23,6 +23,8 @@
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.webkit.WebView;
import com.android.mail.R;
@@ -145,6 +147,8 @@
private ContentSizeChangeListener mSizeChangeListener;
+ private ScaleGestureDetector mScaleDetector;
+
private int mCachedContentHeight;
private final int mViewportWidth;
@@ -188,6 +192,13 @@
mSizeChangeListener = l;
}
+ public void setOnScaleGestureListener(OnScaleGestureListener l) {
+ if (l == null) {
+ mScaleDetector = null;
+ } else {
+ mScaleDetector = new ScaleGestureDetector(getContext(), l);
+ }
+ }
@Override
public int computeVerticalScrollRange() {
@@ -259,7 +270,13 @@
break;
}
- return super.onTouchEvent(ev);
+ final boolean handled = super.onTouchEvent(ev);
+
+ if (mScaleDetector != null) {
+ mScaleDetector.onTouchEvent(ev);
+ }
+
+ return handled;
}
public boolean isHandlingTouch() {
@@ -297,4 +314,5 @@
public float webPxToScreenPxError(int webPx) {
return webPx * getInitialScale() - webPxToScreenPx(webPx);
}
+
}
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 28dd44f..c1ebd4a 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -30,7 +30,9 @@
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.LayoutInflater;
+import android.view.ScaleGestureDetector;
import android.view.View;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.webkit.ConsoleMessage;
@@ -126,6 +128,8 @@
private final WebViewClient mWebViewClient = new ConversationWebViewClient();
+ private final ScaleInterceptor mScaleInterceptor = new ScaleInterceptor();
+
private ConversationViewAdapter mAdapter;
private boolean mViewsCreated;
@@ -143,6 +147,8 @@
private int mMaxAutoLoadMessages;
+ private int mSideMarginPx;
+
/**
* If this conversation fragment is not visible, and it's inappropriate to load up front,
* this is the reason we are waiting. This flag should be cleared once it's okay to load
@@ -264,6 +270,10 @@
mMaxAutoLoadMessages = getResources().getInteger(R.integer.max_auto_load_messages);
+ mSideMarginPx = getResources().getDimensionPixelOffset(
+ R.dimen.conversation_view_margin_side) + getResources().getDimensionPixelOffset(
+ R.dimen.conversation_message_content_margin_side);
+
mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(getActivity()));
// set this up here instead of onCreateView to ensure the latest Account is loaded
@@ -596,11 +606,7 @@
final int convHeaderPos = mAdapter.addConversationHeader(mConversation);
final int convHeaderPx = measureOverlayHeight(convHeaderPos);
- final int sideMarginPx = getResources().getDimensionPixelOffset(
- R.dimen.conversation_view_margin_side) + getResources().getDimensionPixelOffset(
- R.dimen.conversation_message_content_margin_side);
-
- mTemplates.startConversation(mWebView.screenPxToWebPx(sideMarginPx),
+ mTemplates.startConversation(mWebView.screenPxToWebPx(mSideMarginPx),
mWebView.screenPxToWebPx(convHeaderPx));
int collapsedStart = -1;
@@ -926,14 +932,17 @@
}
private void setupOverviewMode() {
+ // for now, overview mode means use the built-in WebView zoom and disable custom scale
+ // gesture handling
final boolean overviewMode = isOverviewMode(mAccount);
final WebSettings settings = mWebView.getSettings();
settings.setUseWideViewPort(overviewMode);
settings.setSupportZoom(overviewMode);
+ settings.setBuiltInZoomControls(overviewMode);
if (overviewMode) {
- settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
}
+ mWebView.setOnScaleGestureListener(overviewMode ? null : mScaleInterceptor);
}
private class ConversationWebViewClient extends AbstractConversationWebViewClient {
@@ -1348,4 +1357,38 @@
int heightBefore) {
mDiff = (expanded ? 1 : -1) * Math.abs(i.getHeight() - heightBefore);
}
+
+ private class ScaleInterceptor implements OnScaleGestureListener {
+
+ private float getFocusXWebPx(ScaleGestureDetector detector) {
+ return (detector.getFocusX() - mSideMarginPx) / mWebView.getInitialScale();
+ }
+
+ private float getFocusYWebPx(ScaleGestureDetector detector) {
+ return detector.getFocusY() / mWebView.getInitialScale();
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ mWebView.loadUrl(String.format("javascript:onScale(%s, %s, %s);",
+ detector.getScaleFactor(), getFocusXWebPx(detector),
+ getFocusYWebPx(detector)));
+ return false;
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ mWebView.loadUrl(String.format("javascript:onScaleBegin(%s, %s);",
+ getFocusXWebPx(detector), getFocusYWebPx(detector)));
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mWebView.loadUrl(String.format("javascript:onScaleEnd(%s, %s);",
+ getFocusXWebPx(detector), getFocusYWebPx(detector)));
+ }
+
+ }
+
}