/* 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.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.onTouchEventForDetailResult(event) .accept( inputResult -> { mInputResult = inputResult.handledResult(); 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); } }