diff --git a/app/src/main/java/com/gh/common/view/NestedScrollWebView2.java b/app/src/main/java/com/gh/common/view/NestedScrollWebView2.java new file mode 100644 index 0000000000..767afa52c9 --- /dev/null +++ b/app/src/main/java/com/gh/common/view/NestedScrollWebView2.java @@ -0,0 +1,784 @@ +package com.gh.common.view; + +import android.content.Context; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AnimationUtils; +import android.webkit.WebView; +import android.widget.OverScroller; +import android.widget.ScrollView; + +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.InputDeviceCompat; +import androidx.core.view.NestedScrollingChild2; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.NestedScrollingParent; +import androidx.core.view.NestedScrollingParentHelper; +import androidx.core.view.ViewCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.core.view.accessibility.AccessibilityRecordCompat; +import wendu.dsbridge.DWebView; + +/** + * Copyright (c) Tuenti Technologies. All rights reserved. + *
+ * WebView compatible with CoordinatorLayout.
+ * The implementation based on NestedScrollView of design library
+ */
+public class NestedScrollWebView2 extends DWebView implements NestedScrollingChild2, NestedScrollingParent {
+
+ private static final int INVALID_POINTER = -1;
+ private static final String TAG = "NestedWebView";
+
+ private final int[] mScrollOffset = new int[2];
+ private final int[] mScrollConsumed = new int[2];
+
+ private int mLastMotionY;
+ private NestedScrollingParentHelper mParentHelper;
+ private NestedScrollingChildHelper mChildHelper;
+ private boolean mIsBeingDragged = false;
+ private VelocityTracker mVelocityTracker;
+ private int mTouchSlop;
+ private int mActivePointerId = INVALID_POINTER;
+ private int mNestedYOffset;
+ private OverScroller mScroller;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+ private static final AccessibilityDelegate ACCESSIBILITY_DELEGATE = new AccessibilityDelegate();
+
+ public NestedScrollWebView2(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public NestedScrollWebView2(Context context) {
+ super(context);
+ init();
+ }
+ private void init(){
+ setOverScrollMode(WebView.OVER_SCROLL_NEVER);
+ initScrollView();
+ mChildHelper = new NestedScrollingChildHelper(this);
+ mParentHelper = new NestedScrollingParentHelper(this);
+ setNestedScrollingEnabled(true);
+ ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE);
+ }
+
+
+ private void initScrollView() {
+ mScroller = new OverScroller(getContext());
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ }
+
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (disallowIntercept) {
+ recycleVelocityTracker();
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
+ return true;
+ }
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ final int activePointerId = mActivePointerId;
+ if (activePointerId == INVALID_POINTER) {
+ break;
+ }
+
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ if (pointerIndex == -1) {
+ Log.e(TAG, "Invalid pointerId=" + activePointerId
+ + " in onInterceptTouchEvent");
+ break;
+ }
+
+ final int y = (int) ev.getY(pointerIndex);
+ final int yDiff = Math.abs(y - mLastMotionY);
+ if (yDiff > mTouchSlop
+ && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) {
+ mIsBeingDragged = true;
+ mLastMotionY = y;
+ initVelocityTrackerIfNotExists();
+ mVelocityTracker.addMovement(ev);
+ mNestedYOffset = 0;
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ final int y = (int) ev.getY();
+
+ mLastMotionY = y;
+ mActivePointerId = ev.getPointerId(0);
+
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+
+ mScroller.computeScrollOffset();
+ mIsBeingDragged = !mScroller.isFinished();
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mIsBeingDragged = false;
+ mActivePointerId = INVALID_POINTER;
+ recycleVelocityTracker();
+ if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ stopNestedScroll();
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ return mIsBeingDragged;
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean returnValue = false;
+ initVelocityTrackerIfNotExists();
+
+ MotionEvent vtev = MotionEvent.obtain(ev);
+
+ final int actionMasked = ev.getActionMasked();
+
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ mNestedYOffset = 0;
+ }
+ vtev.offsetLocation(0, mNestedYOffset);
+
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN: {
+ returnValue = super.onTouchEvent(ev);
+ if (mIsBeingDragged = !mScroller.isFinished()) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ mLastMotionY = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+ break;
+ }
+ case MotionEvent.ACTION_MOVE:
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (activePointerIndex == -1) {
+ Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
+ break;
+ }
+
+ final int y = (int) ev.getY(activePointerIndex);
+ int deltaY = mLastMotionY - y;
+ if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
+ ViewCompat.TYPE_TOUCH)) {
+ deltaY -= mScrollConsumed[1];
+ ev.offsetLocation(0, -mScrollOffset[1]);
+ vtev.offsetLocation(0, -mScrollOffset[1]);
+ mNestedYOffset += mScrollOffset[1];
+ }
+
+ boolean notMove = mScrollOffset[1] == 0;
+ if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ mIsBeingDragged = true;
+ if (deltaY > 0) {
+ deltaY -= mTouchSlop;
+ } else {
+ deltaY += mTouchSlop;
+ }
+ }
+ if (mIsBeingDragged) {
+ mLastMotionY = y - mScrollOffset[1];
+ final int oldY = getScrollY();
+ final int range = getScrollRange();
+ int unconsumedY = 0;
+ int scrolledDeltaY = deltaY;
+ int expectScroll = oldY + deltaY;
+ if (expectScroll < 0) {
+ unconsumedY = expectScroll;
+ scrolledDeltaY = oldY;
+ } else if (expectScroll > range) {
+ unconsumedY = range - expectScroll;
+ scrolledDeltaY = expectScroll - range;
+ }
+ if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
+ ViewCompat.TYPE_TOUCH)) {
+ vtev.offsetLocation(0, mScrollOffset[1]);
+ mNestedYOffset += mScrollOffset[1];
+ mLastMotionY -= mScrollOffset[1];
+ }
+ }
+ notMove &= (mScrollOffset[1] == 0);
+ if (notMove) {
+ returnValue = super.onTouchEvent(ev);
+ } else {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (Math.abs(mNestedYOffset) < mTouchSlop) {
+ returnValue = super.onTouchEvent(ev);
+ } else {
+ ev.setAction(MotionEvent.ACTION_CANCEL);
+ returnValue = super.onTouchEvent(ev);
+ }
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ flingWithNestedDispatch(-initialVelocity);
+ } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
+ getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ returnValue = true;
+ if (mIsBeingDragged && getChildCount() > 0) {
+ if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
+ getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ }
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int index = ev.getActionIndex();
+ mLastMotionY = (int) ev.getY(index);
+ mActivePointerId = ev.getPointerId(index);
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
+ break;
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(vtev);
+ }
+ vtev.recycle();
+ return returnValue;
+ }
+
+
+ int getScrollRange() {
+ //Using scroll range of webview instead of childs as NestedScrollView does.
+ return computeVerticalScrollRange();
+ }
+
+ @Override
+ protected void onOverScrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ super.scrollTo(scrollX, scrollY);
+ }
+
+ boolean overScrollByCompat(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverScrollX, int maxOverScrollY,
+ boolean isTouchEvent) {
+ final int overScrollMode = getOverScrollMode();
+ final boolean canScrollHorizontal =
+ computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+ final boolean canScrollVertical =
+ computeVerticalScrollRange() > computeVerticalScrollExtent();
+ final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS
+ || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+ final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
+ || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+ int newScrollX = scrollX + deltaX;
+ if (!overScrollHorizontal) {
+ maxOverScrollX = 0;
+ }
+
+ int newScrollY = scrollY + deltaY;
+ if (!overScrollVertical) {
+ maxOverScrollY = 0;
+ }
+
+ // Clamp values if at the limits and record
+ final int left = -maxOverScrollX;
+ final int right = maxOverScrollX + scrollRangeX;
+ final int top = -maxOverScrollY;
+ final int bottom = maxOverScrollY + scrollRangeY;
+
+ boolean clampedX = false;
+ if (newScrollX > right) {
+ newScrollX = right;
+ clampedX = true;
+ } else if (newScrollX < left) {
+ newScrollX = left;
+ clampedX = true;
+ }
+
+ boolean clampedY = false;
+ if (newScrollY > bottom) {
+ newScrollY = bottom;
+ clampedY = true;
+ } else if (newScrollY < top) {
+ newScrollY = top;
+ clampedY = true;
+ }
+
+ if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
+ mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
+ }
+
+ onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+ return clampedX || clampedY;
+ }
+
+ private float mVerticalScrollFactor;
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if ((event.getSource() & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_SCROLL: {
+ if (!mIsBeingDragged) {
+ final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ if (vscroll != 0) {
+ final int delta = (int) (vscroll * getVerticalScrollFactorCompat());
+ final int range = getScrollRange();
+ int oldScrollY = getScrollY();
+ int newScrollY = oldScrollY - delta;
+ if (newScrollY < 0) {
+ newScrollY = 0;
+ } else if (newScrollY > range) {
+ newScrollY = range;
+ }
+ if (newScrollY != oldScrollY) {
+ super.scrollTo(getScrollX(), newScrollY);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private float getVerticalScrollFactorCompat() {
+ if (mVerticalScrollFactor == 0) {
+ TypedValue outValue = new TypedValue();
+ final Context context = getContext();
+ if (!context.getTheme().resolveAttribute(
+ android.R.attr.listPreferredItemHeight, outValue, true)) {
+ throw new IllegalStateException(
+ "Expected theme to define listPreferredItemHeight.");
+ }
+ mVerticalScrollFactor = outValue.getDimension(
+ context.getResources().getDisplayMetrics());
+ }
+ return mVerticalScrollFactor;
+ }
+
+
+ private void endDrag() {
+ mIsBeingDragged = false;
+ recycleVelocityTracker();
+ stopNestedScroll();
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = ev.getActionIndex();
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionY = (int) ev.getY(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private void initOrResetVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+
+ private void initVelocityTrackerIfNotExists() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ }
+
+ private void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void flingWithNestedDispatch(int velocityY) {
+ final int scrollY = getScrollY();
+ final boolean canFling = (scrollY > 0 || velocityY > 0)
+ && (scrollY < getScrollRange() || velocityY < 0);
+ if (!dispatchNestedPreFling(0, velocityY)) {
+ dispatchNestedFling(0, velocityY, canFling);
+ fling(velocityY);
+ }
+ }
+
+ public void fling(int velocityY) {
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+ mScroller.fling(getScrollX(), getScrollY(), // start
+ 0, velocityY, // velocities
+ 0, 0, // x
+ Integer.MIN_VALUE, Integer.MAX_VALUE, // y
+ 0, 0); // overscroll
+ mLastScrollerY = getScrollY();
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+
+ private int mLastScrollerY;
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ final int x = mScroller.getCurrX();
+ final int y = mScroller.getCurrY();
+
+ int dy = y - mLastScrollerY;
+
+ // Dispatch up to parent
+ if (dispatchNestedPreScroll(0, dy, mScrollConsumed, null, ViewCompat.TYPE_NON_TOUCH)) {
+ dy -= mScrollConsumed[1];
+ }
+
+ if (dy != 0) {
+ final int range = getScrollRange();
+ final int oldScrollY = getScrollY();
+
+ overScrollByCompat(0, dy, getScrollX(), oldScrollY, 0, range, 0, 0, false);
+
+ final int scrolledDeltaY = getScrollY() - oldScrollY;
+ final int unconsumedY = dy - scrolledDeltaY;
+
+ if (!dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, null,
+ ViewCompat.TYPE_NON_TOUCH)) {
+ final int mode = getOverScrollMode();
+ final boolean canOverscroll = mode == OVER_SCROLL_ALWAYS
+ || (mode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
+ if (canOverscroll) {
+// ensureGlows();
+// if (y <= 0 && oldScrollY > 0) {
+// mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
+// } else if (y >= range && oldScrollY < range) {
+// mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
+// }
+ }
+ }
+ }
+
+ // Finally update the scroll positions and post an invalidation
+ mLastScrollerY = y;
+ ViewCompat.postInvalidateOnAnimation(this);
+ } else {
+ // We can't scroll any more, so stop any indirect scrolling
+ if (hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
+ stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
+ }
+ // and reset the scroller y
+ mLastScrollerY = 0;
+ }
+ }
+
+ @Override
+ public boolean isNestedScrollingEnabled() {
+ return mChildHelper.isNestedScrollingEnabled();
+ }
+
+ @Override
+ public void setNestedScrollingEnabled(boolean enabled) {
+ mChildHelper.setNestedScrollingEnabled(enabled);
+ }
+
+ @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);
+ }
+
+ @Override
+ public int getNestedScrollAxes() {
+ return mParentHelper.getNestedScrollAxes();
+ }
+
+ @Override
+ public boolean startNestedScroll(int axes, int type) {
+ return mChildHelper.startNestedScroll(axes, type);
+ }
+
+ @Override
+ public void stopNestedScroll(int type) {
+ mChildHelper.stopNestedScroll(type);
+ }
+
+ @Override
+ public boolean hasNestedScrollingParent(int type) {
+ return mChildHelper.hasNestedScrollingParent(type);
+ }
+
+ @Override
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
+ int dyUnconsumed, int[] offsetInWindow, int type) {
+ return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+ offsetInWindow, type);
+ }
+
+ @Override
+ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
+ int type) {
+ return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
+ int dyUnconsumed) {
+ final int oldScrollY = getScrollY();
+ scrollBy(0, dyUnconsumed);
+ final int myConsumed = getScrollY() - oldScrollY;
+ final int myUnconsumed = dyUnconsumed - myConsumed;
+ dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
+ }
+
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ dispatchNestedPreScroll(dx, dy, consumed, null);
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (!consumed) {
+ flingWithNestedDispatch((int) velocityY);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+ return dispatchNestedPreFling(velocityX, velocityY);
+ }
+
+ // nested scroll parent
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+ mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ mParentHelper.onStopNestedScroll(target);
+ stopNestedScroll();
+ }
+
+ /**
+ * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+ *
+ * @param x the position where to scroll on the X axis
+ * @param y the position where to scroll on the Y axis
+ */
+ public final void smoothScrollTo(int x, int y) {
+ smoothScrollBy(x - getScrollX(), y - getScrollY());
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param dx the number of pixels to scroll by on the X axis
+ * @param dy the number of pixels to scroll by on the Y axis
+ */
+ private long mLastScroll;
+ static final int ANIMATED_SCROLL_GAP = 250;
+
+ public final void smoothScrollBy(int dx, int dy) {
+ long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+ if (duration > ANIMATED_SCROLL_GAP) {
+ final int height = getHeight() - getPaddingBottom() - getPaddingTop();
+ final int bottom = getHeight();
+ final int maxY = Math.max(0, bottom - height);
+ final int scrollY = getScrollY();
+ dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
+
+ mScroller.startScroll(getScrollX(), scrollY, 0, dy);
+ ViewCompat.postInvalidateOnAnimation(this);
+ } else {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ scrollBy(dx, dy);
+ }
+ mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+ }
+
+ static class AccessibilityDelegate extends AccessibilityDelegateCompat {
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
+ if (super.performAccessibilityAction(host, action, arguments)) {
+ return true;
+ }
+ final NestedScrollWebView2 nsvHost = (NestedScrollWebView2) host;
+ if (!nsvHost.isEnabled()) {
+ return false;
+ }
+ switch (action) {
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
+ final int viewportHeight = nsvHost.getHeight() - nsvHost.getPaddingBottom()
+ - nsvHost.getPaddingTop();
+ final int targetScrollY = Math.min(nsvHost.getScrollY() + viewportHeight,
+ nsvHost.getScrollRange());
+ if (targetScrollY != nsvHost.getScrollY()) {
+ nsvHost.smoothScrollTo(0, targetScrollY);
+ return true;
+ }
+ }
+ return false;
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
+ final int viewportHeight = nsvHost.getHeight() - nsvHost.getPaddingBottom()
+ - nsvHost.getPaddingTop();
+ final int targetScrollY = Math.max(nsvHost.getScrollY() - viewportHeight, 0);
+ if (targetScrollY != nsvHost.getScrollY()) {
+ nsvHost.smoothScrollTo(0, targetScrollY);
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ final NestedScrollWebView2 nsvHost = (NestedScrollWebView2) host;
+ info.setClassName(ScrollView.class.getName());
+ if (nsvHost.isEnabled()) {
+ final int scrollRange = nsvHost.getScrollRange();
+ if (scrollRange > 0) {
+ info.setScrollable(true);
+ if (nsvHost.getScrollY() > 0) {
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+ }
+ if (nsvHost.getScrollY() < scrollRange) {
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+ final NestedScrollWebView2 nsvHost = (NestedScrollWebView2) host;
+ event.setClassName(ScrollView.class.getName());
+ final boolean scrollable = nsvHost.getScrollRange() > 0;
+ event.setScrollable(scrollable);
+ event.setScrollX(nsvHost.getScrollX());
+ event.setScrollY(nsvHost.getScrollY());
+ AccessibilityRecordCompat.setMaxScrollX(event, nsvHost.getScrollX());
+ AccessibilityRecordCompat.setMaxScrollY(event, nsvHost.getScrollRange());
+ }
+ }
+
+ /**
+ * Stop any current scroll
+ * #fling(int, int)} or a touch-initiated fling.
+ */
+ public void stopScroll() {
+ if (mScroller != null) {
+ mScroller.forceFinished(true);
+ }
+ }
+}
diff --git a/app/src/main/res/layout/fragment_web.xml b/app/src/main/res/layout/fragment_web.xml
index 8d4a99ec6b..5818300ac3 100644
--- a/app/src/main/res/layout/fragment_web.xml
+++ b/app/src/main/res/layout/fragment_web.xml
@@ -6,7 +6,7 @@
android:fitsSystemWindows="true"
app:consumeWindowInsets="true">
-