diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java index c19084ca2ac9..b8779cc7a193 100644 --- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java @@ -841,6 +841,8 @@ public class GeckoViewActivity loadFromIntent(getIntent()); } + mGeckoView.setDynamicToolbarMaxHeight(findViewById(R.id.toolbar).getLayoutParams().height); + mToolbarView.getLocationView().setCommitListener(mCommitListener); mToolbarView.updateTabCount(); } diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewBottomBehavior.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewBottomBehavior.java new file mode 100644 index 000000000000..59edb2986ed6 --- /dev/null +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewBottomBehavior.java @@ -0,0 +1,27 @@ +package org.mozilla.geckoview_example; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import org.mozilla.geckoview.GeckoView; + +public class GeckoViewBottomBehavior extends CoordinatorLayout.Behavior { + public GeckoViewBottomBehavior(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, GeckoView child, View dependency) { + return dependency instanceof Toolbar; + } + + @Override + public boolean onDependentViewChanged(CoordinatorLayout parent, GeckoView child, View dependency) { + child.setVerticalClipping(Math.round(-dependency.getTranslationY())); + return true; + } + +} diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/NestedGeckoView.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/NestedGeckoView.java new file mode 100644 index 000000000000..6afe371e3fc1 --- /dev/null +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/NestedGeckoView.java @@ -0,0 +1,174 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.geckoview_example; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import androidx.core.view.NestedScrollingChild; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.ViewCompat; +import org.mozilla.geckoview.GeckoResult; +import org.mozilla.geckoview.GeckoView; +import org.mozilla.geckoview.PanZoomController; + + +/** + * GeckoView that supports nested scrolls (for using in a CoordinatorLayout). + * + * This code is a simplified version of the NestedScrollView implementation + * which can be found in the support library: + * [android.support.v4.widget.NestedScrollView] + * + * Based on: + * https://github.com/takahirom/webview-in-coordinatorlayout + */ + +public class NestedGeckoView extends GeckoView implements NestedScrollingChild { + + private int mLastY; + private final int[] mScrollOffset = new int[2]; + private final int[] mScrollConsumed = new int[2]; + private int mNestedOffsetY; + private NestedScrollingChildHelper mChildHelper; + + /** + * Integer indicating how user's MotionEvent was handled. + * + * There must be a 1-1 relation between this values and [EngineView.InputResult]'s. + */ + private int mInputResult = PanZoomController.INPUT_RESULT_UNHANDLED; + + public NestedGeckoView(final Context context) { + this(context, null); + } + + public NestedGeckoView(final Context context, final AttributeSet attrs) { + super(context, attrs); + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + MotionEvent event = MotionEvent.obtain(ev); + final int action = event.getActionMasked(); + int eventY = (int) event.getY(); + + switch (action) { + case MotionEvent.ACTION_MOVE: + final boolean allowScroll = !shouldPinOnScreen() && + mInputResult == PanZoomController.INPUT_RESULT_HANDLED; + int deltaY = mLastY - eventY; + + if (allowScroll && dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { + deltaY -= mScrollConsumed[1]; + event.offsetLocation(0f, -mScrollOffset[1]); + mNestedOffsetY += mScrollOffset[1]; + } + + mLastY = eventY - mScrollOffset[1]; + + if (allowScroll && dispatchNestedScroll(0, mScrollOffset[1], 0, deltaY, mScrollOffset)) { + mLastY -= mScrollOffset[1]; + event.offsetLocation(0f, mScrollOffset[1]); + mNestedOffsetY += mScrollOffset[1]; + } + break; + + case MotionEvent.ACTION_DOWN: + // A new gesture started. Reset handled status and ask GV if it can handle this. + mInputResult = PanZoomController.INPUT_RESULT_UNHANDLED; + updateInputResult(event); + + mNestedOffsetY = 0; + mLastY = eventY; + + // The event should be handled either by onTouchEvent, + // either by onTouchEventForResult, never by both. + // Early return if we sent it to updateInputResult(..) which calls onTouchEventForResult. + event.recycle(); + return true; + + // We don't care about other touch events + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + stopNestedScroll(); + break; + } + + // Execute event handler from parent class in all cases + final boolean eventHandled = callSuperOnTouchEvent(event); + + // Recycle previously obtained event + event.recycle(); + + return eventHandled; + } + + private boolean callSuperOnTouchEvent(MotionEvent event) { + return super.onTouchEvent(event); + } + + private void updateInputResult(MotionEvent event) { + super.onTouchEventForResult(event).accept(inputResult -> { + mInputResult = inputResult; + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + }); + } + + public int getInputResult() { + return mInputResult; + } + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override + public boolean startNestedScroll(int axes) { + return mChildHelper.startNestedScroll(axes); + } + + @Override + public void stopNestedScroll() { + mChildHelper.stopNestedScroll(); + } + + @Override + public boolean hasNestedScrollingParent() { + return mChildHelper.hasNestedScrollingParent(); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, + int dyConsumed, + int dxUnconsumed, + int dyUnconsumed, + int[] offsetInWindow) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } +} diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/ToolbarBottomBehavior.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/ToolbarBottomBehavior.java new file mode 100644 index 000000000000..93fd4e1eafad --- /dev/null +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/ToolbarBottomBehavior.java @@ -0,0 +1,64 @@ +package org.mozilla.geckoview_example; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.MotionEvent; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; +import org.mozilla.geckoview.GeckoView; +import org.mozilla.geckoview.PanZoomController; + +public class ToolbarBottomBehavior extends CoordinatorLayout.Behavior { + public ToolbarBottomBehavior(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + @Override + public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, + View child, + View directTargetChild, + View target, + int axes, + int type) { + NestedGeckoView geckoView = (NestedGeckoView)target; + if (axes == ViewCompat.SCROLL_AXIS_VERTICAL && + geckoView.getInputResult() == PanZoomController.INPUT_RESULT_HANDLED) { + return true; + } + + if (geckoView.getInputResult() == PanZoomController.INPUT_RESULT_UNHANDLED) { + // Restore the toolbar to the original (visible) state, this is what A-C does. + child.setTranslationY(0f); + } + + return false; + } + + @Override + public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, + View child, + View target, + int type) { + // Snap up or down the user stops scrolling. + if (child.getTranslationY() >= (child.getHeight() / 2f)) { + child.setTranslationY(child.getHeight()); + } else { + child.setTranslationY(0f); + } + } + + @Override + public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, + View child, + View target, + int dx, + int dy, + int[] consumed, + int type) { + super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type); + child.setTranslationY(Math.max(0f, Math.min(child.getHeight(), child.getTranslationY() + dy))); + } + +} diff --git a/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml b/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml index b72dc3259eaf..8ee4a615a92b 100644 --- a/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml +++ b/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml @@ -1,22 +1,26 @@ - + android:layout_height="match_parent"> - + android:layout_gravity="bottom" + android:background="#eeeeee" + app:layout_behavior="org.mozilla.geckoview_example.ToolbarBottomBehavior" + app:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed" /> - + +