Android(安卓)NestedScrolling机制
NestedScrolling机制现在在App的作用越来越重要,许多很漂亮的交互都是基于NestedScrolling机制进行完成的。
NestedScrolling机制主要是能够让父View和子View在滚动时互相协调配合。其中有两个重要的类,分别是:
接口类NestedScrollingParent(最新:NestedScrollingParent2)NestedScrollingChild(最新:NestedScrollingChild2)帮助类NestedScrollingChildHelperNestedScrollingParentHelper
父类继承NestedScrollingParent接口,而子类继承NestedScrollingChild接口,同时让父类包含子类,而不是自接父子关系,就搭起了NestedScrollingParent机制的基本骨架。
其主要流程是:
- 子类滑动,把滑动产生的事件和参数传给父类
- 父类根据子类传过来的参数进行各种交互操作,如变大缩小之类的
而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
更多相关文章
- android RecyclerView局部刷新
- Android(安卓)性能优化的一些方法
- Android(安卓)Studio插件Gsonformat使用
- Android笔记汇总
- Android(安卓)studio Xposed框架Hook小试
- android View与ViewGroup研究
- Android(安卓)中View.onDraw(Canvas canvas)的使用方法
- Android(安卓)binder机制(native服务篇)
- (20120731)android面试总结(002)