Android:嵌套滑动总结

  • 时间:2021-03-20 02:21 作者:九九叔 来源: 阅读:485
  • 扫一扫,手机访问
摘要:貌似这个比较复杂,记录一下解释的比较好的。https://blog.csdn.net/recall2012/article/details/79474172一.何为滑动嵌套?就是就有滑动功能的view,再嵌套另一个具备滑动功能的view,例如recyclerview嵌套recyclerview。二.

貌似这个比较复杂,记录一下

解释的比较好的。
https://blog.csdn.net/recall2012/article/details/79474172

一.何为滑动嵌套?

就是就有滑动功能的view,再嵌套另一个具备滑动功能的view,例如recyclerview嵌套recyclerview。

二.CoordinatorLayout是什么?

是协调布局,是一个超级framelayout,可以控制多个view协调运动。通过behavior来实现。主要原理是,子view获取到滑动事件后,在滑动前,先讯问一下父view,问下父view要不要先滑,父view假如要滑,那就滑,而后把滑完之后把剩下的距离告诉子view,而后子view再滑。
所以说,假如解决不好,可以滑起来卡卡的,例如父view滑了一点,而后子view又滑了一点,你就会感觉卡卡的,难受。

三.behavior是怎么的?

    public static abstract class Behavior<V extends View> {        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,                @NonNull View target, int dxConsumed, int dyConsumed,                int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {            if (type == ViewCompat.TYPE_TOUCH) {                onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,                        dxUnconsumed, dyUnconsumed);            }        }        @Deprecated        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {            // Do nothing        }        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,                @NestedScrollType int type) {            if (type == ViewCompat.TYPE_TOUCH) {                onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);            }        }        public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,                @NonNull V child, @NonNull View target, float velocityX, float velocityY,                boolean consumed) {            return false;        }        public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,                @NonNull V child, @NonNull View target, float velocityX, float velocityY) {            return false;        }.......

是个笼统类,看上去有点像NestedScrollingParent的方法。

例如,我们看看onDependentViewChanged是怎样调用的把


image.png

可以看到,CoordinatorLayout注册了PreDraw的监听器,就是view在绘制前,会有回调函数回调到CoordinatorLayout的onChildViewsChanged,

if (b != null && b.layoutDependsOn(this, checkChild, child)) { //首先检查是不是依赖的view发生了改变      .....  //依赖的view发生改变了,回调给需要协调运动的view      handled = b.onDependentViewChanged(this, checkChild, child);       .....}

我们再看一种场景,就是使用onNestedPreScroll来实现的协调运动。
例如 SheHuan/BehaviorDemo
的第二个test。

看看他的backtrace


image.png

可以看到事件是从recyclerview传过来的,所以说move事件到达recyclerview后,recyclerview作为NestedScrollingChild2,会先循环一下作为NestedScrollingParent2的CoordinatorLayout,你CoordinatorLayout有没有需要滑动的,有没有需要解决move事件的,等CoordinatorLayou解决完之后,recyclerview再继续跑。这样看,像是子view和父view都消费了move事件,挺有意思。

recyclerview调用dispatchNestedPreScroll后,实际用使用NestedScrollingChildHelper来解决,

    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,            @Nullable int[] offsetInWindow, @NestedScrollType int type) {        if (isNestedScrollingEnabled()) { //判断能否支持嵌套滑动,我们常常可以在应用中关闭嵌套滑动                ViewParentCompat.onNestedPreScroll(parent, mView, dx,     }

而后,最后就要回调到CoordinatorLayout,而后CoordinatorLayout会遍历每个子view,子view可以根据需要来确定能否解决。

四.分析一下BottomSheetBehavior的绘制流程

layout流程
    @Override    @SuppressWarnings("unchecked")    protected void onLayout(boolean changed, int l, int t, int r, int b) {            final Behavior behavior = lp.getBehavior();           //获取childview的behavior,而后调用behavior的onLayoutChild去布局。假如没有behavior就走CoordinatorLayout自身的onLayoutChild            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {                onLayoutChild(child, layoutDirection);            }        }    }

可以看到,有配置behavior的view,就走behavior的onLayoutChild方法。所以layout方法被behavior接管了。

  viewDragHelper = ViewDragHelper.create(parent, dragCallback);

最后其实就是用viewdragger来实现拖拉位置的

  @Override  public boolean onLayoutChild(      @NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {    if (state == STATE_EXPANDED) {      ViewCompat.offsetTopAndBottom(child, getExpandedOffset());    } else if (state == STATE_HALF_EXPANDED) {      ViewCompat.offsetTopAndBottom(child, halfExpandedOffset);    } else if (hideable && state == STATE_HIDDEN) {      ViewCompat.offsetTopAndBottom(child, parentHeight);    } else if (state == STATE_COLLAPSED) {      ViewCompat.offsetTopAndBottom(child, collapsedOffset);    } else if (state == STATE_DRAGGING || state == STATE_SETTLING) {      ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());    }  }

五.bottomsheet嵌套recyclerview后,recyclerview关闭嵌套滑动之后,滑recyclerview,bottomsheet会动吗?

会动,别以为关闭嵌套滑动,父view就不阻拦事件了,实际上,父view会会直接把在slopmove的时候,直接开始阻拦move事件了。


image.png

那阻拦之后,是怎样拖到整个bottomsheet的呢?


image.png

其实就是阻拦之后,事件去了CoordinatorLayout,而后再调用behavior,behavior使用ViewDragHelper去拖动配置了behavior的view网上挪。

关键是下面的函数,来确认能否阻拦,返回true就阻拦,由于touchingScrollingChild为false,所以最后返回了true。假如recyclerview设置了NestedScrollingEnabled,则touchingScrollingChild为true,最后这个函数返回false,父view就不阻拦,move事件直接到达recyclerview。

        @Override        public boolean tryCaptureView(@NonNull View child, int pointerId) {          Log.i("fengfeng", "tryCaptureView start:" + "child=" + child + " pointId=" + pointerId + " state=" + state);          if (state == STATE_DRAGGING) {            Log.i("fengfeng", "tryCaptureView false 1");            return false;          }          if (touchingScrollingChild) {            Log.i("fengfeng", "tryCaptureView false 2");            return false;          }          if (state == STATE_EXPANDED && activePointerId == pointerId) {            View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;            if (scroll != null && scroll.canScrollVertically(-1)) {              // Let the content scroll up              Log.i("fengfeng", "tryCaptureView false 3");              return false;            }          }          Log.i("fengfeng", "tryCaptureView true 4");          return viewRef != null && viewRef.get() == child;        }        @Override        public boolean tryCaptureView(@NonNull View child, int pointerId) {          Log.i("fengfeng", "tryCaptureView start:" + "child=" + child + " pointId=" + pointerId + " state=" + state);          if (state == STATE_DRAGGING) {            Log.i("fengfeng", "tryCaptureView false 1");            return false;          }          if (touchingScrollingChild) {            Log.i("fengfeng", "tryCaptureView false 2");            return false;          }          if (state == STATE_EXPANDED && activePointerId == pointerId) {            View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;            if (scroll != null && scroll.canScrollVertically(-1)) {              // Let the content scroll up              Log.i("fengfeng", "tryCaptureView false 3");              return false;            }          }          Log.i("fengfeng", "tryCaptureView true 4");          return viewRef != null && viewRef.get() == child;        }

touchingScrollingChild在down事件赋值,代表点中了一个可以scroll的view,例如recyclerview就是可以scroll的。

      case MotionEvent.ACTION_DOWN:        if (state != STATE_SETTLING) {          View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;          if (scroll != null && parent.isPointInChildBounds(scroll, initialX, initialY)) {            touchingScrollingChild = true;          }        }
  • 全部评论(0)
最新发布的资讯信息
【系统环境|】pymysql使用(2025-10-27 23:27)
【系统环境|】如何使用Python和pymysql库连接数据库(2025-10-27 23:26)
【系统环境|】Python模块--PyMySQL(八)(2025-10-27 23:25)
【系统环境|】属性、正则表达式、pymysql、多线程编程(2025-10-27 23:24)
【系统环境|】一文讲完pymysql:python操作Mysql数据库(2025-10-27 23:23)
【系统环境|】Django使用上下文语句调用pymysql(2025-10-27 23:22)
【系统环境|】Python3.8 SQLAlchemy 和 PyMySQL 区别(2025-10-27 23:21)
【系统环境|】探讨NewSQL数据库在高并发场景下的ACID特性保障机制与实现策略(2025-10-27 23:21)
【系统环境|】MySQL 事务管理: ACID 特性实现原理(2025-10-27 23:20)
【系统环境|】数据库事务控制: 实现ACID特性及隔离级别调优(2025-10-27 23:19)
手机二维码手机访问领取大礼包
返回顶部