• Webview+AppbarLayout上下滑动冲突


    问题描述

    AppBarLayout是material包里面提供的容器组件,可用于实现MD风格的页面滑动动效。

    在开发场景中,我们经常使用CoordinatorLayout+AppBarLayout来实现页面头部的折叠和吸顶效果。在滑动过程中,View的行为随offset变化,View之间存在关联变化。

    关于AppBarLayout的使用和嵌套滑动原理,可以见参考文档前三篇。

    当AppBarLayout和Webview共同使用时,会出现一个问题,AppBarLayout无法折叠了,Webview只能在较小的固定区域内上下滑动,可以理解为两者上下滑动冲突了。

    原因分析

    使用AppBarLayout,我们一般是通过addOffsetChangedListener来监听嵌套滑动的位移的,从而修改AppBarLayout的appbarState,或者做一些透明度等视觉上的修改。但在这个场景下,offset始终为0,说明滑动事件并没有那我们预期处理。

    滑动冲突问题的本质,是滑动事件的分发和消费不对。

    在AppBarLayout中,是通过Behavior实现嵌套滑动的:若Behavior所附属的View存在可以滑动的子View,而且正在滑动的View足够大去滑动的话,就接受嵌套滑动事件.
    那么问题就变成了,我们的WebView没有实现嵌套滑动机制。

    解决方案

    实现嵌套滑动机制,有两种方式:

    1. Android官方的例子中用NestedScrollView可以实现嵌套滑动,那么直接将WebView作为NestedScrollView直接子View即可实现WebView的嵌套滑动。
    2. 重写Webview,自己实现嵌套滑动逻辑。
      - 实现NestedScrollingChild嵌套滑动接口
      - 重写onTouchEvent方法

    方式2在实现过程中,发现在WebView的惯性滑动下,滑动到顶也无法将AppBarLayout拉下来,触顶后必须再下滑一次才能将AppBarLayout拉下来,因此处理这个问题,需要额外增加一些处理逻辑.

    添加一个速度追踪器(VelocityTracker)记录滑动速度,然后在ACTION_UP触摸事件出现时,获得这个速度,在ViewConfiguration中获得最大的滑动速度,然后再调用OverScroller来计算惯性滑动的距离,并且调用View的刷新从而实现调用computeScroll方法,然后在computeScroll方法中根据OverScroller计算的应该滑动的距离滑动到指定位置.

    核心代码

    private final int[] mScrollConsumed = new int[2];
    private final int[] mScrollOffset = new int[2];
    private int mLastMotionY;
    private VelocityTracker mVelocityTracker;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private OverScroller mScroller;
    private int mLastScrollerY;
    private final NestedScrollingChildHelper mChildHelper;
    
    // 初始化
    public NestedScrollWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mChildHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
    
        mScroller = new OverScroller(getContext());
    
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    private void initVelocityTrackerIfNotExists() {
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
        }
    
        private void recycleVelocityTracker() {
            if (mVelocityTracker != null) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
        }
    
        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);
        }
    
        @Override
        public void computeScroll() {
            super.computeScroll();
            if (mScroller.computeScrollOffset()) {
                final int x = mScroller.getCurrX();
                final int y = mScroller.getCurrY();
                Log.d(TAG, "computeScroll: y : " + y);
                int dy = y - mLastScrollerY;
                if (dy != 0) {
                    int scrollY = getScrollY();
                    int dyUnConsumed = 0;
                    int consumedY = dy;
                    if (scrollY == 0) {
                        dyUnConsumed = dy;
                        consumedY = 0;
                    } else if (scrollY + dy < 0) {
                        dyUnConsumed = dy + scrollY;
                        consumedY = -scrollY;
                    }
    
                    if (!dispatchNestedScroll(0, consumedY, 0, dyUnConsumed, null,
                            ViewCompat.TYPE_NON_TOUCH)) {
    
                    }
                }
    
                // 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 onTouchEvent(MotionEvent event) {
            initVelocityTrackerIfNotExists();
    
            MotionEvent vtev = MotionEvent.obtain(event);
    
            final int actionMasked = event.getAction();
    
            switch (actionMasked) {
                case MotionEvent.ACTION_DOWN:
                    mLastMotionY = (int) event.getRawY();
                    startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                    mVelocityTracker.addMovement(vtev);
                    mScroller.computeScrollOffset();
                    if (!mScroller.isFinished()) {
                        mScroller.abortAnimation();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) velocityTracker.getYVelocity();
                    if (Math.abs(initialVelocity) > mMinimumVelocity) {
                        fling(-initialVelocity);
                    }
                case MotionEvent.ACTION_CANCEL:
                    stopNestedScroll();
                    recycleVelocityTracker();
                    break;
                case MotionEvent.ACTION_MOVE:
                    final int y = (int) event.getRawY();
                    int deltaY = mLastMotionY - y;
                    if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
                        vtev.offsetLocation(0, mScrollConsumed[1]);
                    }
                    mLastMotionY = y;
    
                    int scrollY = getScrollY();
    
                    int dyUnconsumed = 0;
                    if (scrollY == 0) {
                        dyUnconsumed = deltaY;
                    } else if (scrollY + deltaY < 0) {
                        dyUnconsumed = deltaY + scrollY;
                        vtev.offsetLocation(0, -dyUnconsumed);
                    }
                    mVelocityTracker.addMovement(vtev);
                    boolean result = super.onTouchEvent(vtev);
                    if (dispatchNestedScroll(0, deltaY - dyUnconsumed, 0, dyUnconsumed, mScrollOffset)) {
    
                    }
                    return result;
                default:
    
                    break;
            }
            return super.onTouchEvent(vtev);
        }
    
        // NestedScrollingChild 嵌套滑动接口
        @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 boolean startNestedScroll(int axes, int type) {
            return mChildHelper.startNestedScroll(axes, type);
        }
    
        @Override
        public void stopNestedScroll() {
            mChildHelper.stopNestedScroll();
        }
    
        @Override
        public void stopNestedScroll(int type) {
            mChildHelper.stopNestedScroll(type);
        }
    
        @Override
        public boolean hasNestedScrollingParent() {
            return mChildHelper.hasNestedScrollingParent();
        }
    
        @Override
        public boolean hasNestedScrollingParent(int type) {
            return mChildHelper.hasNestedScrollingParent(type);
        }
    
        @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 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) {
            return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        }
    
        @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, consumed);
        }
    
        @Override
        public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
            return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197

    参考文档

    基于AppBarLayout实现二级吸顶&踩坑记录
    Android NestedScroll嵌套滑动机制解析
    Android CoordinatorLayout和Behavior解析
    WebView和AppBarLayout嵌套滑动联动无效分析及解决办法

  • 相关阅读:
    C#开发的OpenRA游戏之雷达地图
    【Python 基础 2023 最新】第七课 Pandas
    DBConvert Studio 3.0.6 -2022-08-13 Crack
    【论文阅读笔记】-针对RSA的短解密指数的密码学分析(Cryptanalysis of Short RSA Secret Exponents)
    从零开始手写mmo游戏从框架到爆炸(二十四)— 装备系统二
    Docker配置镜像加速器
    django数据库mysql迁移问题
    用DIV+CSS技术设计的水果介绍网站(web前端网页制作课作业)
    Linux常用命令
    群晖NAS使用Docker安装WPS Office并结合内网穿透实现公网远程办公
  • 原文地址:https://blog.csdn.net/yaojie5519/article/details/126678453