Bug 1271998 - Part 4 - Use a touch delegate to increase the clickable area of the URL bar. r?walkingice,jwu
Originally, the listeners that trigger editing mode and the URL bar's context menu were attached to the BrowserToolbar itself. As this doesn't work properly in conjunction with wrapping the URL TextView into a ScrollView, the listeners were moved onto the TextView itself.
Bug 1389164 reduced the height of the TextView in order to better support lightweight themes with the new toolbar design, which in conjunction with the changes to support the ScrollView has the unfortunate side effect of also reducing the URL bar's hit target area.
Therefore, we increase it back to its old levels by using a TouchDelegate on the ScrollView. Because Android's ScrollView implementation doesn't support TouchDelegates, we have to add the missing bits of logic back in from the default View implementation.
MozReview-Commit-ID: 1nTrrNGvBza
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
@@ -4,16 +4,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.toolbar;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
+import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Telemetry;
@@ -31,16 +32,17 @@ import org.mozilla.gecko.tabs.TabHistory
import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnStopListener;
import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnTitleChangeListener;
import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.UpdateFlags;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.MenuUtils;
import org.mozilla.gecko.util.WindowUtil;
import org.mozilla.gecko.widget.AnimatedProgressBar;
+import org.mozilla.gecko.widget.TouchDelegateWithReset;
import org.mozilla.gecko.widget.themed.ThemedImageButton;
import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
@@ -214,16 +216,33 @@ public abstract class BrowserToolbar ext
prefs = new ToolbarPrefs();
urlDisplayLayout.setToolbarPrefs(prefs);
urlEditLayout.setToolbarPrefs(prefs);
// ScrollViews are allowed to have only one child.
final View scrollChild = urlDisplayScroll.getChildAt(0);
+ urlDisplayScroll.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ final int width = urlDisplayScroll.getWidth();
+ final int height = urlDisplayScroll.getHeight();
+ final int oldWidth = oldRight - oldLeft;
+ final int oldHeight = oldBottom - oldTop;
+
+ if (width != oldWidth || height != oldHeight) {
+ final Rect r = new Rect();
+ r.right = width;
+ r.bottom = height;
+ urlDisplayScroll.setTouchDelegate(new TouchDelegateWithReset(r, scrollChild));
+ }
+ }
+ });
+
scrollChild.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
// Do not show the context menu while editing
if (isEditing()) {
return;
}
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FadedHorizontalScrollView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/FadedHorizontalScrollView.java
@@ -11,39 +11,46 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
import android.view.View;
import android.widget.HorizontalScrollView;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.R;
/**
* A {@link HorizontalScrollView} implementation with a more efficient fadingEdge drawing strategy
* than the built-in version provided by Android. The width of the fade effect can be controlled via
* <code>gecko:fadeWidth</code>. To control in how far the fading effect should affect any views
* further up in the View hierarchy, place this view or one of its parents onto a separate layer
* using <code>android:layerType</code>. Currently, only horizontal fading is supported.
+ * <p>
+ * Additionally, {@link TouchDelegate} support (which isn't provided for in Android's ScrollView
+ * implementation) has been enabled.
*/
public class FadedHorizontalScrollView extends HorizontalScrollView {
// Width of the fade effect from end of the view.
private final int mFadeWidth;
private final boolean mPreMarshmallow;
private final FadePaint mFadePaint;
private float mFadeTop;
private float mFadeBottom;
private boolean mVerticalFadeBordersDirty;
+ private boolean mInterceptingTouchEvents;
+
public FadedHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mPreMarshmallow = Versions.preMarshmallow;
mFadePaint = new FadePaint();
mVerticalFadeBordersDirty = true;
addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
@@ -151,9 +158,35 @@ public class FadedHorizontalScrollView e
public FadePaint() {
matrix = new Matrix();
fade = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
setShader(fade);
setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
}
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Once we intercept an event, this method stops being called until the intercept state
+ // is reset at the start of the following gesture. Therefore we can reset the tracking
+ // variable to false each time this method is being called.
+ mInterceptingTouchEvents = false;
+
+ final boolean intercept = super.onInterceptTouchEvent(ev);
+ if (intercept) {
+ mInterceptingTouchEvents = true;
+ }
+ return intercept;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mInterceptingTouchEvents) {
+ final TouchDelegate touchDelegate = getTouchDelegate();
+ if (touchDelegate != null && touchDelegate.onTouchEvent(ev)) {
+ return true;
+ }
+ }
+
+ return super.onTouchEvent(ev);
+ }
}