NestedScrolling 嵌套滑动机制

参考1
参考2

1. NestedScrolling是什么

NestedScrolling 是内部嵌套滑动机制。
由NestedScrollingParent,NestedScrollingParentHepler,NestedScrollingChild,NestedScrollingChildHepler组成

2. NestedScrolling解决了什么问题

解决了两个可以滑动的控件嵌套时出现的手势冲突的情况。
比如:ScrollView+ListView
ListView滑到顶部后,无法触发ScrollView的滑动

3. NestedScrolling的原理 (用了代理模式)

  1. NestedScrollingChild向外暴露方法,实际上具体的操作由NestedScrollingChildHepler完成。
  2. NestedScrollingChildHelper最终会调用父容器NestedScrollingParent接口暴露出的方法,通知父容器做操作。


    NestedScrolling 嵌套滑动机制_第1张图片
    image

4. 源码分析

NestedScrollingChildHelper.startNestedScroll
会调用NestedScrollingParentHelper.onStartNestedScroll,如果返回true,还会调用NestedScrollingParentHelper.onNestedScrollAccepted。(其它的方法类似)

public class MyNestedScrollingChild extends LinearLayout implement NestedScrollingChild{
 public boolean startNestedScroll(int axes) {
        return mNestedScrollingChildHelper.startNestedScroll(axes);
    }
}
   public boolean startNestedScroll(@ScrollAxis int axes) {
        return startNestedScroll(axes, TYPE_TOUCH);
    }
    
        public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    setNestedScrollingParentForType(type, p);
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }

4. 使用示例

  1. 实现这样一个功能:Parent A下有一个不可滑动Child B和一个可滑动的Child C,向上拖动C,A和C一起动,C到置顶后,继续向上拖动,C的内容向上滑动

  2. 主要代码

 public class MyNestedScrollingChild extends LinearLayout implements NestedScrollingChild {
  private static final String TAG = "MyNestedScrollingChild";
  private NestedScrollingChildHelper mNestedScrollingChildHelper;
  private float mLastTouchX;
  private float mLastTouchY;
  private int[] mConsumed=new int[2];
  private int[] mOffsetInWindow=new int[2];
  public MyNestedScrollingChild(Context context) {
      super(context, null);
  }

  public MyNestedScrollingChild(Context context, @Nullable AttributeSet attrs) {
      super(context, attrs);
      init();
  }

  private void init() {
      if (mNestedScrollingChildHelper == null) {
          mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
          mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
      }
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      int action = event.getAction();
      float x=event.getX();
      float y=event.getY();
      switch (action){
          case MotionEvent.ACTION_DOWN:
              mLastTouchX=x;
              mLastTouchY=y;
              //关键点 通知Parent开始滑动
              startNestedScroll(SCROLL_AXIS_HORIZONTAL);
              break;
          case MotionEvent.ACTION_MOVE:
              int dx= (int) (mLastTouchX-x);
              int dy= (int) (mLastTouchY-y);
              mLastTouchX=x;
              mLastTouchY=y;
              //自己消耗事件前,将事件分发给Parent
              //返回true表示Parent消耗了事件,其实是consume中是否有值大于1来决定的,消耗的偏移量在mConsumed中
              if(dispatchNestedPreScroll(dx,dy,mConsumed, mOffsetInWindow)){//根据Parent的消耗再决定自己的消耗
                  Log.i(TAG, "Parent 消耗了 "+mConsumed[1]);
                  int leaveY=dy-mConsumed[1];
                  scrollBy(0,leaveY);
                  return true;
              }
              scrollBy(0,dy);
              break;
      }
      return true;
  }
}

public class MyNestedScrollingParent extends LinearLayout implements NestedScrollingParent {
   private NestedScrollingParentHelper mNestedScrollingParentHelper;
   private View mFirstView;
   private View mSecondView;
   public MyNestedScrollingParent(Context context) {
       this(context,null);
   }

   public MyNestedScrollingParent(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
       init();
   }

   private void init() {
       if (mNestedScrollingParentHelper == null) {
           mNestedScrollingParentHelper=new NestedScrollingParentHelper(this);
       }
   }

   @Override
   protected void onFinishInflate() {
       super.onFinishInflate();
       mFirstView=getChildAt(0);
       mSecondView=getChildAt(1);
   }


   @Override
   public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
       return true;
   }



   @Override
   public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
              if(consume(dy)){
                  consumed[1]=dy/2;
                  scrollBy(0,consumed[1]);
              }
   }

   private boolean consume(int dy) {
       if(dy < 0){
           if(getScrollY() > 0 && mSecondView.getScrollY() <= 0){
               return true;
           }
       }
       if(dy > 0){
           if(getScrollY() < mFirstView.getHeight()){
               return true;
           }
       }
       return false;
   }
}
   
        
        
            
        
    

MyNestedScrollingChild.java

5.总结

  1. NestedScrollView的相关类 NestedScrollingParent,NestedScrollingParentHepler,NestedScrollingChild,NestedScrollingChildHepler
  2. 使用了代理模式,接口NestedScrollingParent,NestedScrollingChild,向外暴露接口,NestedScrollingParentHepler与NestedScrollingChildHepler负责具体实现
  3. 调用NestedScrollingChild的方法最终会触发NestedScrollingParent的相关方法(这体现了接口)
  4. NestedScrolling机制只负责滑动事件的通知,但是具体的滑动操作还是要自己实现
  5. 思考:为什么要使用 接口+代理模式的方式来实现NestedScrolling机制
  6. 所有视图都已经继承了View,所以只有采用接口的形式
  7. 接口中的大部分方法实现的逻辑都相似,所以采用了代理,让代理类完成具体的工作。

你可能感兴趣的:(NestedScrolling 嵌套滑动机制)