Bug 1500644 - Make GeckoView example toolbar dynamic at bottom by using CoordinatorLayout. r=geckoview-reviewers,snorp

This way is basically how android-components provides the dynamic toolbar
feature but there are a couple of caveats on this change as of now.

1) There is no option to switch the toolbar position at top
2) This change forces the toolbar to be dynamic, there is no way to make
   it static

Each corresponding file in android-components is;
 NestedGeckoView.java       <- NestedGeckoView.kt [1]
 ToolbarBottomBehavior.java <- BrowserToolbarBottomBehavior.kt [2]
 GeckoViewBottomBehavior    <- EngineViewBottomBehavior.kt [3]

[1] b6a5c64441/components/browser/engine-gecko-nightly/src/main/java/mozilla/components/browser/engine/gecko/NestedGeckoView.kt
[2] b6a5c64441/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/behavior/BrowserToolbarBottomBehavior.kt
[3] b6a5c64441/components/feature/session/src/main/java/mozilla/components/feature/session/behavior/EngineViewBottomBehavior.kt

Differential Revision: https://phabricator.services.mozilla.com/D92357
This commit is contained in:
Hiroyuki Ikezoe
2020-10-27 14:57:47 +00:00
parent 8b4a752fdc
commit b5ce865020
5 changed files with 279 additions and 7 deletions

View File

@@ -841,6 +841,8 @@ public class GeckoViewActivity
loadFromIntent(getIntent());
}
mGeckoView.setDynamicToolbarMaxHeight(findViewById(R.id.toolbar).getLayoutParams().height);
mToolbarView.getLocationView().setCommitListener(mCommitListener);
mToolbarView.updateTabCount();
}

View File

@@ -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<GeckoView> {
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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<View> {
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)));
}
}

View File

@@ -1,22 +1,26 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="match_parent">
<org.mozilla.geckoview.GeckoView
<org.mozilla.geckoview_example.NestedGeckoView
android:id="@+id/gecko_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/toolbar"
android:scrollbars="none"
app:layout_behavior="org.mozilla.geckoview_example.GeckoViewBottomBehavior"
/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:actionBarSize"
android:layout_alignParentBottom="true"/>
android:layout_gravity="bottom"
android:background="#eeeeee"
app:layout_behavior="org.mozilla.geckoview_example.ToolbarBottomBehavior"
app:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed" />
<ProgressBar
android:id="@+id/page_progress"
@@ -24,4 +28,5 @@
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_alignTop="@id/gecko_view" />
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>