NestedScrolling机制现在在App的作用越来越重要,许多很漂亮的交互都是基于NestedScrolling机制进行完成的。

NestedScrolling机制主要是能够让父View和子View在滚动时互相协调配合。其中有两个重要的类,分别是:

接口类NestedScrollingParent(最新:NestedScrollingParent2)NestedScrollingChild(最新:NestedScrollingChild2)帮助类NestedScrollingChildHelperNestedScrollingParentHelper

父类继承NestedScrollingParent接口,而子类继承NestedScrollingChild接口,同时让父类包含子类,而不是自接父子关系,就搭起了NestedScrollingParent机制的基本骨架。

其主要流程是:

  1. 子类滑动,把滑动产生的事件和参数传给父类
  2. 父类根据子类传过来的参数进行各种交互操作,如变大缩小之类的

而NestedScrollingChildHelper和NestedScrollingParentHelper是两个帮助类,在实现NestedScrollingChild和NestedScrollingParent接口时,使用这两个帮助类可以简化我们的工作。

NestedScrollingChild 接口类

public interface NestedScrollingChild {    /**     * 设置嵌套滑动是否能用     */    @Override    public void setNestedScrollingEnabled(boolean enabled);    /**     * 判断嵌套滑动是否可用     */    @Override    public boolean isNestedScrollingEnabled();    /**     * 开始嵌套滑动     *     * @param axes 表示方向轴,有横向和竖向     */    @Override    public boolean startNestedScroll(int axes);    /**     * 停止嵌套滑动     */    @Override    public void stopNestedScroll();    /**     * 判断是否有父View 支持嵌套滑动     */    @Override    public boolean hasNestedScrollingParent() ;    /**     * 滑行时调用     * @param velocityX x 轴上的滑动速率     * @param velocityY y 轴上的滑动速率     * @param consumed 是否被消费     * @return  true if the nested scrolling parent consumed or otherwise reacted to the fling     */    @Override    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) ;    /**     * 进行滑行前调用     * @param velocityX x 轴上的滑动速率     * @param velocityY y 轴上的滑动速率     * @return true if a nested scrolling parent consumed the fling     */    @Override    public boolean dispatchNestedPreFling(float velocityX, float velocityY) ;    /**     * 子view处理scroll后调用     * @param dxConsumed x轴上被消费的距离(横向)     * @param dyConsumed y轴上被消费的距离(竖向)     * @param dxUnconsumed x轴上未被消费的距离     * @param dyUnconsumed y轴上未被消费的距离     * @param offsetInWindow 子View的窗体偏移量     * @return  true if the event was dispatched, false if it could not be dispatched.     */    @Override    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) ;    /**     * 在子View的onInterceptTouchEvent或者onTouch中,调用该方法通知父View滑动的距离     * @param dx  x轴上滑动的距离     * @param dy  y轴上滑动的距离     * @param consumed 父view消费掉的scroll长度     * @param offsetInWindow   子View的窗体偏移量     * @return 支持的嵌套的父View 是否处理了 滑动事件     */    @Override    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow);}

上面这个方法方法和代表的意思我都已经贴出来, 然后是只是一个接口类上面的方法要怎么实现呢,这时候就要用到上面的帮助类NestedScrollingChildHelper,一个完整的实现模板如下:

public class MyNestedScrollingChild extends LinearLayout implements NestedScrollingChild {    private NestedScrollingChildHelper mNestedScrollingChildHelper;    public MyNestedScrollingChild(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);        mNestedScrollingChildHelper.setNestedScrollingEnabled(true);    }    @Override    public void setNestedScrollingEnabled(boolean enabled) {        mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);    }    @Override    public boolean isNestedScrollingEnabled() {        return mNestedScrollingChildHelper.isNestedScrollingEnabled();    }    @Override    public boolean startNestedScroll(int axes) {        return mNestedScrollingChildHelper.startNestedScroll(axes);    }    @Override    public void stopNestedScroll() {        mNestedScrollingChildHelper.stopNestedScroll();    }    @Override    public boolean hasNestedScrollingParent() {        return mNestedScrollingChildHelper.hasNestedScrollingParent();    }    @Override    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {        return mNestedScrollingChildHelper.dispatchNestedFling(velocityX,velocityY,consumed);    }    @Override    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {        return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX,velocityY);    }    @Override    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {        return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);    }    @Override    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {        return dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);    }}

NestedScrollingParent 接口

public interface NestedScrollingParent {    @Override    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);    @Override    public void onStopNestedScroll(View child);    @Override    public void onNestedScrollAccepted(View child, View target, int axes);    @Override    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);    @Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);    @Override    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);    @Override    public boolean onNestedPreFling(View target, float velocityX, float velocityY);    @Override    public int getNestedScrollAxes();}

从上面的代码可以看出NestedScrollingChild的方法执行之后就会回调父View的各个方法,从方法名也知道作用和NestedScrollingChild的用作大同小异。当子View执行startNestedScroll时,就会回调父View的onStartNestedScroll、onNestedScrollAccepted方法,当子View执行dispatchNestedPreScroll方法时,就会回调父View的onNestedPreScroll,当子View执行dispatchNestedScroll方法时,就会回调父View的onNestedScroll方法,由此类推,dispatchNestedPreFling回调父View的onNestedPreFling方法,dispatchNestedFling回调父View的onNestedFling方法,等。

同时也有几个接口是需要帮助类进行实现的,模板代码如下:

public class MyNestedScrollingParent extends LinearLayout implements NestedScrollingParent {    private NestedScrollingParentHelper mNestedScrollingParentHelper;    public MyNestedScrollingParent(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);    }    @Override    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {        return super.onStartNestedScroll(child, target, nestedScrollAxes);    }    @Override    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);    }    @Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {        super.onNestedPreScroll(target, dx, dy, consumed);    }    @Override    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {        return super.onNestedFling(target, velocityX, velocityY, consumed);    }    @Override    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {        return super.onNestedPreFling(target, velocityX, velocityY);    }    @Override    public void onStopNestedScroll(View child) {        mNestedScrollingParentHelper.onStopNestedScroll(child);    }    @Override    public void onNestedScrollAccepted(View child, View target, int axes) {        mNestedScrollingParentHelper.onNestedScrollAccepted(child,target,axes);    }    @Override    public int getNestedScrollAxes() {        return mNestedScrollingParentHelper.getNestedScrollAxes();    }}

最后总结,子View通过startNestedScroll()发起嵌套滑动,同时父View也会回调自己的onStartNestedScroll()方法,接着子View每次在滚动前都会调用dispatchNestedPreScroll()方法,父View的onNestedPreScroll()也会操作,父View决定是否熬滑动,然后才是子View自己滑动,之后子View也可以调用上面的其它方法做相应的处理,最后调用stopNestedScroll()结束。

最后举一个实例吧

public class MyNestedScrollChild extends LinearLayout implements NestedScrollingChild2 {    private NestedScrollingChildHelper mNestedScrollingChildHelper;    private int[] offset=new int[2];    private int[] consumed=new int[2];    private TextView scrollText;    private int showHeight;    private int lastY;    private  boolean srcollTop=false;    public MyNestedScrollChild(Context context) {        super(context);    }    public MyNestedScrollChild(Context context, AttributeSet attrs) {        super(context, attrs);        setBackgroundColor(context.getResources().getColor(R.color.colorffffff));    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        scrollText=(TextView)getChildAt(0);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        showHeight = getMeasuredHeight();        heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    public boolean canChildScrollUp() {        return srcollTop;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch(event.getAction()){            case MotionEvent.ACTION_DOWN:                lastY=(int)event.getRawY();                break;            case MotionEvent.ACTION_MOVE:                int y=(int)(event.getRawY());                int dy=y-lastY;                lastY=y;                if(startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)                        &&dispatchNestedPreScroll(0,dy,consumed,offset)){                    int remain = dy - consumed[1];                    if (remain != 0) {                        scrollBy(0, -remain);                    }                }else{                    scrollBy(0, -dy);                }                break;        }        return true;    }    //限制滚动范围    @Override    public void scrollTo(int x, int y) {        int maxY = getMeasuredHeight()- showHeight;        if (y > maxY) {            y = maxY;            srcollTop=false;        }else if (y < 0) {            y = 0;            srcollTop=true;        }else{            srcollTop=false;        }        super.scrollTo(x, y);    }    public NestedScrollingChildHelper getNestedScrollingChildHelper(){        if(mNestedScrollingChildHelper==null){            mNestedScrollingChildHelper=new NestedScrollingChildHelper(this);            mNestedScrollingChildHelper.setNestedScrollingEnabled(true);        }        return mNestedScrollingChildHelper;    }    @Override    public boolean startNestedScroll(int axes, int type) {        return getNestedScrollingChildHelper().startNestedScroll(axes,type);    }    @Override    public void stopNestedScroll(int type) {        getNestedScrollingChildHelper().stopNestedScroll(type);    }    @Override    public boolean hasNestedScrollingParent(int type) {        return getNestedScrollingChildHelper().hasNestedScrollingParent(type);    }    @Override    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,                                        int dyUnconsumed, @Nullable int[] offsetInWindow, int type) {        return getNestedScrollingChildHelper().dispatchNestedScroll(dxConsumed,dyConsumed,                dxUnconsumed,dyUnconsumed,offsetInWindow,type);    }    @Override    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) {        return getNestedScrollingChildHelper().dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow,type);    }    @Override    public void setNestedScrollingEnabled(boolean enabled) {        getNestedScrollingChildHelper().setNestedScrollingEnabled(enabled);    }    @Override    public boolean isNestedScrollingEnabled() {        return getNestedScrollingChildHelper().isNestedScrollingEnabled();    }    @Override    public boolean startNestedScroll(int axes) {        return getNestedScrollingChildHelper().startNestedScroll(axes);    }    @Override    public void stopNestedScroll() {        getNestedScrollingChildHelper().stopNestedScroll();    }    @Override    public boolean hasNestedScrollingParent() {        return getNestedScrollingChildHelper().hasNestedScrollingParent();    }    @Override    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,                                        @Nullable int[] offsetInWindow) {        return getNestedScrollingChildHelper().dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);    }    @Override    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {        return getNestedScrollingChildHelper().dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);    }    @Override    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {        return getNestedScrollingChildHelper().dispatchNestedFling(velocityX,velocityY,consumed);    }    @Override    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {        return getNestedScrollingChildHelper().dispatchNestedPreFling(velocityX,velocityY);    }}

首先给出NestedScrollingChild子View,重点看一下onTouchEvent()方法,当MotionEvent.ACTION_MOVE时,不断的调用startNestedScroll()和dispatchNestedPreScroll()向父View发送直接,然后滚动通过scrollBy()滚动触发事件的View,这就是最核心的代码了,接着看父View代码如下:

public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent2 {    private NestedScrollingParentHelper mNestedScrollingParentHelper;    private MyNestedScrollChild scrollChildView;    private ImageView foodIV;    private TextView titleTV;    private int imageHeight;    private int titleHeight;    private int imageMargin;    private int scrollY;    public MyNestedScrollParent(Context context) {        this(context,null);    }    public MyNestedScrollParent(Context context, AttributeSet attrs) {        super(context, attrs);        mNestedScrollingParentHelper=new NestedScrollingParentHelper(this);    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        FrameLayout frameLayout=(FrameLayout) getChildAt(0);        scrollChildView=(MyNestedScrollChild) getChildAt(1);        foodIV=frameLayout.findViewById(R.id.foodIV);        titleTV=frameLayout.findViewById(R.id.titleTV);        foodIV.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                imageHeight=foodIV.getHeight();            }        });        titleTV.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                titleHeight=titleTV.getHeight();            }        });    }    @Override    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) {        if(target instanceof MyNestedScrollChild) {            return true;        }        return false;    }    @Override    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {        mNestedScrollingParentHelper.onNestedScrollAccepted(child,target,axes,type);    }    @Override    public void onStopNestedScroll(@NonNull View target, int type) {        mNestedScrollingParentHelper.onStopNestedScroll(target,type);    }    @Override    public int getNestedScrollAxes() {        return mNestedScrollingParentHelper.getNestedScrollAxes();    }    @Override    public void onStopNestedScroll(@NonNull View target) {        mNestedScrollingParentHelper.onStopNestedScroll(target);    }    @Override    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,                               int dxUnconsumed, int dyUnconsumed, int type) {    }    @Override    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {        imageMargin=titleHeight-imageHeight;        scrollY+=dy;        if(scrollY<=imageMargin){            scrollY=imageMargin;            scrollChildView.setTranslationY(scrollY);        }else{            if(dy<0){                //上滑                consumed[1]=dy;                scrollChildView.setTranslationY(scrollY);            }else{                //下滑                if(!scrollChildView.canChildScrollUp()){                    scrollY-=dy;                }                if(scrollY>=0){                    scrollY=0;                }                scrollChildView.setTranslationY(scrollY);            }        }    }    @Override    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) {        return false;    }}

前面的子View的dispatchNestedPreScroll()对应这个父View的onNestedScroll()如上面的代码,通过View.setTranslationY()来滑动整个子View,consumed[1]=dy;表示子View和滑动的View一起滑。最后看一下布局文件:

<?xml version="1.0" encoding="utf-8"?>                                        

父View包裹子View,以达成依赖关系。

讲了NestedScrolling,就有必要讲解CoordinatorLayout.Behavior,下回讲吧,最后奉上源码吧https://github.com/jack921/MeiTuanGoodsDetails

更多相关文章

  1. android RecyclerView局部刷新
  2. Android(安卓)性能优化的一些方法
  3. Android(安卓)Studio插件Gsonformat使用
  4. Android笔记汇总
  5. Android(安卓)studio Xposed框架Hook小试
  6. android View与ViewGroup研究
  7. Android(安卓)中View.onDraw(Canvas canvas)的使用方法
  8. Android(安卓)binder机制(native服务篇)
  9. (20120731)android面试总结(002)

随机推荐

  1. JS调用Android里面的方法,Android调用JS里
  2. Android系统架构概况
  3. 【Android(安卓)Training - 00】Google A
  4. 关于android中的gif实现
  5. Android之玩转View
  6. Android程序结构
  7. Android(安卓)4.4 新特性分析-15项大改进
  8. 在android 下支持ntfs-3g
  9. Android(安卓)DEPPLINK、APPLink原理简析
  10. 为什么说 Android(安卓)很了不起!