【问题标题】:NestedWebView working properly with ScrollingViewBehaviorNestedWebView 与 ScrollingViewBehavior 一起正常工作
【发布时间】:2019-12-30 10:36:24
【问题描述】:

我有一个包含两个活动的应用程序扩展相同的abstract - 一个与WebView,另一个与ViewPager 与TabLayout 和RecyclerViews - 在这个级别上我已经实现了“共享”显示/隐藏带有滚动/翻转功能的工具栏:

app:layout_scrollFlags="scroll|snap|enterAlways"

WebView 是“推出”Toolbar,就像在ViewPager 中的任何选项卡下的RecyclerView(保持TabLayout 可见),在这两种情况下,任何滚动到顶部都会返回Toolbar。就像这个例子一样:

现在我要改变这种行为 - 当用户不在页面顶部时,Toolbar 应该被隐藏,所以:任何滚动到底部都应该隐藏 Toolbar(如果存在且仅)(滚动到顶部 && getScrollY()<=0 ) 应该让Toolbar 输入:

我从THIS 指南中借用了 GIF,这表明(所有其他)我想要的行为是默认的,只有scroll 标志(可能还有snap)。所以我删除了enterAlways,我的“本机”Activity 开始按预期开箱即用。 WebView - 完全没有变化...

我怀疑这个错误是由我的NestedWebView 引起的,我目前正在使用THIS 之一。因此,我尝试将此类替换为以下内容:

marshi - 不可滚动的 webview 内容(工具栏显示/隐藏)

takahirom - 工具栏进入,即使向上滚动 1px 几乎立即(一种捕捉行为,但在触摸期间)

hanks - 没有嵌套滚动(固定工具栏),还有未注释的setNestedScrollingEnabled(true); 行

如何实现与WebView配合的第二个GIFToolbar行为?

【问题讨论】:

    标签: android android-webview android-toolbar android-appbarlayout android-scroll


    【解决方案1】:

    阅读底部和其他答案/cmets 的编辑!不要使用这个类,如果可以的话,也避免以任何形式使用WebView。你迟早会遇到麻烦,维护WebView是地狱......

    我发现NestedWebView 的几个不同的实现,并注意到它们都是旧的并且实现NestedScrollingChild,而目前我们有这个接口的第三个版本:NestedScrollingChild3 (androidx)。所以我已经深入了解了NestedSrcollView 的当前版本,并添加了一些新的处理方法,现在它可以工作了。确切地说 - 我试图在整个屏幕上实现平滑滚动,就像 WebView 会填满整个屏幕并且 Toolbar 是 Web 内容的一部分(总是在顶部)。 snap 也在工作。明智地使用

    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.VelocityTracker;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.view.ViewParent;
    import android.webkit.WebView;
    import android.widget.OverScroller;
    
    import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;
    import androidx.core.view.NestedScrollingChild3;
    import androidx.core.view.NestedScrollingChildHelper;
    import androidx.core.view.ViewCompat;
    
    /**
     * WebView compatible with CoordinatorLayout by snachmsm
     * The implementation based on NestedScrollView of design library androidx v1.0.1
     */
    public class NestedWebView extends WebView implements NestedScrollingChild3 {
    
        private static final String TAG = "NestedWebView";
        private static final int INVALID_POINTER = -1;
    
        private final int[] mScrollOffset = new int[2];
        private final int[] mScrollConsumed = new int[2];
    
        private int mLastMotionY;
        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 int mLastScrollerY;
    
        public NestedWebView(Context context) {
            this(context, null);
        }
    
        public NestedWebView(Context context, AttributeSet attrs) {
            this(context, attrs, android.R.attr.webViewStyle);
        }
    
        public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            setOverScrollMode(WebView.OVER_SCROLL_NEVER);
            initScrollView();
            mChildHelper = new NestedScrollingChildHelper(this);
            setNestedScrollingEnabled(true);
        }
    
        private void initScrollView() {
            mScroller = new OverScroller(getContext());
            final ViewConfiguration configuration = ViewConfiguration.get(getContext());
            mTouchSlop = configuration.getScaledTouchSlop();
            mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
            mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            final int action = ev.getAction();
            if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { // most common
                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:
                    mLastMotionY = (int) ev.getY();
                    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;
        }
    
        @SuppressLint("ClickableViewAccessibility")
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            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:
                    if ((mIsBeingDragged = !mScroller.isFinished())) {
                        final ViewParent parent = getParent();
                        if (parent != null) {
                            parent.requestDisallowInterceptTouchEvent(true);
                        }
                    }
    
                    if (!mScroller.isFinished()) {
                        abortAnimatedScroll();
                    }
    
                    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];
                        mNestedYOffset += mScrollOffset[1];
                    }
                    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();
    
                        // Calling overScrollByCompat will call onOverScrolled, which
                        // calls onScrollChanged if applicable.
                        if (overScrollByCompat(0, deltaY, 0, oldY, 0, range, 0,
                                0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {
                            mVelocityTracker.clear();
                        }
    
                        final int scrolledDeltaY = getScrollY() - oldY;
                        final int unconsumedY = deltaY - scrolledDeltaY;
    
                        mScrollConsumed[1] = 0;
    
                        dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
                                ViewCompat.TYPE_TOUCH, mScrollConsumed);
    
                        mLastMotionY -= mScrollOffset[1];
                        mNestedYOffset += mScrollOffset[1];
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                        if (!dispatchNestedPreFling(0, -initialVelocity)) {
                            dispatchNestedFling(0, -initialVelocity, true);
                            fling(-initialVelocity);
                        }
                    } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                            getScrollRange())) {
                        ViewCompat.postInvalidateOnAnimation(this);
                    }
                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                    break;
                case MotionEvent.ACTION_CANCEL:
                    if (mIsBeingDragged) {
                        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 super.onTouchEvent(ev);
        }
    
        private void abortAnimatedScroll() {
            mScroller.abortAnimation();
            stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
        }
    
        private void endDrag() {
            mIsBeingDragged = false;
    
            recycleVelocityTracker();
            stopNestedScroll();
        }
    
        private void onSecondaryPointerUp(MotionEvent ev) {
            final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
            final int pointerId = ev.getPointerId(pointerIndex);
            if (pointerId == mActivePointerId) {
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                mLastMotionY = (int) ev.getY(newPointerIndex);
                mActivePointerId = ev.getPointerId(newPointerIndex);
                if (mVelocityTracker != null) {
                    mVelocityTracker.clear();
                }
            }
        }
    
        private void fling(int velocityY) {
            int height = getHeight();
            mScroller.fling(getScrollX(), getScrollY(), // start
                    0, velocityY, // velocities
                    0, 0, // x
                    Integer.MIN_VALUE, Integer.MAX_VALUE, // y
                    0, height / 2);
            runAnimatedScroll(true);
        }
    
        private void runAnimatedScroll(boolean participateInNestedScrolling) {
            if (participateInNestedScrolling) {
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
            } else {
                stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
            }
            mLastScrollerY = getScrollY();
            ViewCompat.postInvalidateOnAnimation(this);
        }
    
        @Override
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            if (disallowIntercept) {
                recycleVelocityTracker();
            }
            super.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    
        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;
            }
        }
    
        @Override
        protected boolean overScrollBy(int deltaX, int deltaY,
                                       int scrollX, int scrollY,
                                       int scrollRangeX, int scrollRangeY,
                                       int maxOverScrollX, int maxOverScrollY,
                                       boolean isTouchEvent) {
            // this is causing double scroll call (doubled speed), but this WebView isn't overscrollable
            // all overscrolls are passed to appbar, so commenting this out during drag
            if (!mIsBeingDragged)
                overScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
                        maxOverScrollX, maxOverScrollY, isTouchEvent);
            // without this call webview won't scroll to top when url change or when user pick input
            // (webview should move a bit making input still in viewport when "adjustResize")
            return true;
        }
    
        int getScrollRange() {
            //Using scroll range of webview instead of childs as NestedScrollView does.
            return computeVerticalScrollRange();
        }
    
        @Override
        public boolean isNestedScrollingEnabled() {
            return mChildHelper.isNestedScrollingEnabled();
        }
    
        @Override
        public void setNestedScrollingEnabled(boolean enabled) {
            mChildHelper.setNestedScrollingEnabled(enabled);
        }
    
        @Override
        public boolean startNestedScroll(int axes, int type) {
            return mChildHelper.startNestedScroll(axes, type);
        }
    
        @Override
        public boolean startNestedScroll(int axes) {
            return startNestedScroll(axes, ViewCompat.TYPE_TOUCH);
        }
    
        @Override
        public void stopNestedScroll(int type) {
            mChildHelper.stopNestedScroll(type);
        }
    
        @Override
        public void stopNestedScroll() {
            stopNestedScroll(ViewCompat.TYPE_TOUCH);
        }
    
        @Override
        public boolean hasNestedScrollingParent(int type) {
            return mChildHelper.hasNestedScrollingParent(type);
        }
    
        @Override
        public boolean hasNestedScrollingParent() {
            return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
        }
    
        @Override
        public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                                            int[] offsetInWindow) {
            return dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                    offsetInWindow, ViewCompat.TYPE_TOUCH);
        }
    
        @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 void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                                         @Nullable int[] offsetInWindow, int type, @NonNull int[] consumed) {
            mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                    offsetInWindow, type, consumed);
        }
    
        @Override
        public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
            return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH);
        }
    
    
        @Override
        public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
            return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
        }
    
        @Override
        public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
            return mChildHelper.dispatchNestedFling(velocityX, velocityY, false);
        }
    
        @Override
        public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
            return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
        }
    
        @Override
        public int getNestedScrollAxes() {
            return ViewCompat.SCROLL_AXIS_VERTICAL;
        }
    
        @Override
        public void computeScroll() {
            if (mScroller.isFinished()) {
                return;
            }
    
            mScroller.computeScrollOffset();
            final int y = mScroller.getCurrY();
            int unconsumed = y - mLastScrollerY;
            mLastScrollerY = y;
    
            // Nested Scrolling Pre Pass
            mScrollConsumed[1] = 0;
            dispatchNestedPreScroll(0, unconsumed, mScrollConsumed, null,
                    ViewCompat.TYPE_NON_TOUCH);
            unconsumed -= mScrollConsumed[1];
    
    
            if (unconsumed != 0) {
                // Internal Scroll
                final int oldScrollY = getScrollY();
                overScrollByCompat(0, unconsumed, getScrollX(), oldScrollY, 0, getScrollRange(),
                        0, 0, false);
                final int scrolledByMe = getScrollY() - oldScrollY;
                unconsumed -= scrolledByMe;
    
                // Nested Scrolling Post Pass
                mScrollConsumed[1] = 0;
                dispatchNestedScroll(0, 0, 0, unconsumed, mScrollOffset,
                        ViewCompat.TYPE_NON_TOUCH, mScrollConsumed);
                unconsumed -= mScrollConsumed[1];
            }
    
            if (unconsumed != 0) {
                abortAnimatedScroll();
            }
    
            if (!mScroller.isFinished()) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    
        // copied from NestedScrollView exacly as it looks, leaving overscroll related code, maybe future use
        private 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;
        }
    }
    

    edit:这个类有一个巨大的缺陷!这个View 消耗所有滚动事件,将它们分派到Toolbar、Coordinator 或它自己的但永远不会用于HTML 内容中的嵌套滚动div/view。例如如果您有自己的下拉列表(或任何自滚动视图)的实现,它将不会被手指滚动,所有事件都被本机视图消耗......我最终把这个类和所有与相关的嵌套滚动功能都扔掉了他妈的WebView

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-25
      • 2017-12-14
      • 2018-11-10
      • 2020-08-19
      • 2013-02-06
      • 2019-02-02
      • 2014-07-01
      相关资源
      最近更新 更多