转载请注明出处: http://renyuan-1991.iteye.com/blogs/2262643
NestedScrolling介绍
Lollipop之后增加了NesteScrolling,可以通过这个方法在滚动当前控件的时候改变其他控件的样式,嵌套滑动就是最好的例子。以前的思路是在滑动之前判断父控件剩余滑动空间,如果有滑动空间就把touch事件交个父控件,如果父控件不需要滑动就直接把touch事件交给子控件。这样的处理有一个弊端,每次事件只能被一个对象处理。
在使用的时候会用到以下四个接口:
NestedScrollingChild,
NestedScrollingChildHelper,
NestedScrollingParent,
NestedScrollingParentHelper
子view实现NestedScrollingChild接口并重写
setNestedScrollingEnabled,
isNestedScrollingEnabled,
startNestedScroll,
stopNestedScroll,
hasNestedScrollingParent,
dispatchNestedScroll,
dispatchNestedPreScroll,
dispatchNestedFling,
dispatchNestedPreFling这几个方法。
父view实现NestedScrollingParent借口并重写
onNestedScrollAccepted,
onStartNestedScroll,
onNestedPreScroll,
onNestedScroll方法即可。他们之间详细的关系如下:
在子view需要滑动的时候例如ACTION_DOWN的时候就要调用startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL);方法来告诉父view自己要开始滑动了,父view会收到onStartNestedScroll这个方法的回调从而决定是不是要配合子view做出响应。如果需要配合,还会回调onNestedScrollAccepted()。在滑动事件产生但是子view还没处理前可以调用dispatchNestedPreScroll(0,dy,consumed,offsetInWindow)这个方法把事件传给父view这样父view就能在onNestedPreScroll方法里面收到子view的滑动信息,然后做出相应的处理把处理完后的结果通过consumed传给子view。同样的道理,如果父view需要在子view滑动后处理相关事件的话可以在子view的事件处理完成之后调用dispatchNestedScroll然后父view会在onNestedScroll收到回调。最后,滑动结束,调用onStopNestedScroll()表示本次处理结束。就这么简单,下面总结一下流程:
第一步:
在Childe中创建NestedScrollingChildHelper一般实现一下几个方法即可完成需求:
setNestedScrollingEnabled isNestedScrollingEnabled startNestedScroll stopNestedScroll hasNestedScrollingParent dispatchNestedScroll dispatchNestedPreScroll dispatchNestedFling dispatchNestedPreFling
第二步:
在构造函数里面实例化helper并设置setnestedScrollingEnabled(true)
public MyChildScrollView(Context context, AttributeSet attrs) { super(context, attrs); childHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); }
第三步:
在onTouchEvent中处理事件
Activon_down中调用startNestedScroll方法,告诉父类我马上要开始滑动了 startNestedScroll方法接受的参数为ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL
Activon_move中处理自己的移动,在自己移动之前可以调用dispatchNestedPreScroll让父view通过回调onNestedPreScroll方法 先滑动。父view滑动之后会把结果回调到这个方法的里面,我们可以通过这个方法的参数得知父view滑动了多少距离如下代码
/** * @param dx 水平滑动距离 * @param dy 垂直滑动距离 * @param consumed 父类消耗掉的距离 * @return */ @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); }
另外,如果我们滑动之后仍然需要父view做行对应的处理就可以通过调用dispatchNestedScroll方法实现,这个方法会回调到父view的onNestedScroll
Action_up中 调用stopNestedScroll();结束滑动
第四步:
父view也需要实helper
public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); parentHelper = new NestedScrollingParentHelper(this); }
子view中调用了startNestedScroll 那么父类就需要做相对应的响应,如果父类需要配合滑动就会调用
父view在onNestedPreScroll先于子view处理滑动事件,这里也可以拦截子view的滑动事件
如果父view在子view滑动之后也要做相对应的处理那么在onNestedScroll中也可以中相对应的处理
一般情况下父类只需要实现下面几个方法即可:
//~~~~~~~ 嵌套滑动 ~~~~~~~ @Override public void onNestedScrollAccepted(View child, View target, int axes) { super.onNestedScrollAccepted(child, target, axes); } @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return super.onStartNestedScroll(child, target, nestedScrollAxes); } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { // super.onNestedPreScroll(target, dx, dy, consumed); //切记,把父类滑剩下的给子类 //~~~~~~~ 正向滑动 ~~~~~~~ if((sumScroll+dy)>sumScroll){ //超过边界 if((Math.abs(sumScroll+dy))>myScrollDis){ MyScrollView.this.scrollBy(dx, myScrollDis - sumScroll);//最多滑动myScrollDis,当超出边界的时候父类只需要滑动剩下的距离 consumed[1] = dy - (myScrollDis - sumScroll);//将没滑动的距离给子类,让子类滑动 sumScroll = myScrollDis; }else{//未超过边界 MyScrollView.this.scrollBy(dx, dy); sumScroll += dy;//记录这个view滑动的真实距离 consumed[1] = 0; } } //~~~~~~~ 反向互动 ~~~~~~~ else if((sumScroll+dy)
最后提醒一下,在父类的onNestedPreScroll中如果返回0,在子类中的dispatchNestedPreScroll就会返回false。官网上说“true if the parent consumed some or all of the scroll delta”。可以理解成当我们给consumed赋值的时候如果指定它的值是0,就会认为父类没有消耗。所以在这里强调一下consumed一定要统一返回父类消耗的距离。
子view:
public class MyChildScrollView extends ScrollView implements NestedScrollingChild{ private NestedScrollingChildHelper childHelper; private int[] consumed = new int[2]; private int[] offsetInWindow = new int[2]; private int downX; private int downY; private VelocityTracker mVelocityTracker = null; private boolean allowFly = false; public MyChildScrollView(Context context) { super(context); } public MyChildScrollView(Context context, AttributeSet attrs) { super(context, attrs); childHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); } public MyChildScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent ev) { if(mVelocityTracker == null){ mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: allowFly = false; downX = (int)ev.getRawX(); downY = (int)ev.getRawY(); startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL); break; case MotionEvent.ACTION_MOVE: int moveX = (int)ev.getRawX(); int moveY = (int)ev.getRawY(); int dx = moveX - downX; int dy = -(moveY - downY);//滚动方法的方向跟坐标是相反的,所以这里要加一个负号 downX = moveX; downY = moveY; //在consumed中就是父类滑动后剩下的距离, if(dispatchNestedPreScroll(0,dy,consumed,offsetInWindow)){ dy = consumed[1]; MyChildScrollView.this.scrollBy(0, dy); allowFly = true; }else { } break; case MotionEvent.ACTION_UP: stopNestedScroll(); if(allowFly){ mVelocityTracker.computeCurrentVelocity(1000);//该参数指定的是1S内滑动的像素。也可以指定最大速率。 int myScrollFly = (int)mVelocityTracker.getYVelocity(); fling(-myScrollFly);//惯性滑动的方法 } break; } return true; } //~~~~~~~ 嵌套滑动的处理方法 ~~~~~~~ @Override public void setNestedScrollingEnabled(boolean enabled) { childHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return childHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return childHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { childHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return childHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } /** * @param dx 水平滑动距离 * @param dy 垂直滑动距离 * @param consumed 父类消耗掉的距离 * @return */ @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return childHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return childHelper.dispatchNestedPreFling(velocityX, velocityY); } }
中间view:
public class MyViewPager extends ViewPager implements NestedScrollingChild,NestedScrollingParent{ private NestedScrollingChildHelper childHelper; private NestedScrollingParentHelper parentHelper; private int[] consumed = new int[2]; private int[] offsetInWindow = new int[2]; private float downX; private float downY; public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); childHelper = new NestedScrollingChildHelper(this); parentHelper = new NestedScrollingParentHelper(this); setNestedScrollingEnabled(true); } public MyViewPager(Context context) { super(context); } //~~~~~~~ 作为父类滑动的四种处理方法 ~~~~~~~ @Override public void onNestedScrollAccepted(View child, View target, int axes) { parentHelper.onNestedScrollAccepted(child,target,axes); } @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { startNestedScroll(nestedScrollAxes); return true; } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { dispatchNestedPreScroll(dx, dy, consumed, null); } @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); } //~~~~~~~ 作为子类滑动的处理方法 ~~~~~~~ @Override public void setNestedScrollingEnabled(boolean enabled) { childHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return childHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return childHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { childHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return childHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } /** * @param dx 水平滑动距离 * @param dy 垂直滑动距离 * @param consumed 父类消耗掉的距离 * @return */ @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return childHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return childHelper.dispatchNestedPreFling(velocityX, velocityY); } }
父view:
public class MyScrollView extends ScrollView implements NestedScrollingParent { private int myScrollDis = 0; private int sumScroll = 0; private NestedScrollingParentHelper parentHelper; public MyScrollView(Context context) { super(context); } public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); parentHelper = new NestedScrollingParentHelper(this); } public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent ev) { System.out.println("MyScrollView.onTouchEvent"); return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return false; // return super.onInterceptTouchEvent(ev); } //~~~~~~~ 添加一个方法,在代码中设置这个父类滚动的限度 ~~~~~~~ public void setMyScrollDis(int myScrollDis){ this.myScrollDis = myScrollDis; } //~~~~~~~ 嵌套滑动 ~~~~~~~ @Override public void onNestedScrollAccepted(View child, View target, int axes) { super.onNestedScrollAccepted(child, target, axes); } @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return super.onStartNestedScroll(child, target, nestedScrollAxes); } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { // super.onNestedPreScroll(target, dx, dy, consumed); //切记,把父类滑剩下的给子类 //~~~~~~~ 正向滑动 ~~~~~~~ if((sumScroll+dy)>sumScroll){ //超过边界 if((Math.abs(sumScroll+dy))>myScrollDis){ MyScrollView.this.scrollBy(dx, myScrollDis - sumScroll);//最多滑动myScrollDis,当超出边界的时候父类只需要滑动剩下的距离 consumed[1] = dy - (myScrollDis - sumScroll);//将没滑动的距离给子类,让子类滑动 sumScroll = myScrollDis; }else{//未超过边界 MyScrollView.this.scrollBy(dx, dy); sumScroll += dy;//记录这个view滑动的真实距离 consumed[1] = 0; } } //~~~~~~~ 反向互动 ~~~~~~~ else if((sumScroll+dy)
对了,好像忘了说ScrollView的惯性滑动了,其实代码已经在上面的子view中了,先用VelocityTracker得到滑动的速率首先要创建mVelocityTracker,并把event添加到VelocityTracker中
if(mVelocityTracker == null){ mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev);
然后获得滑动速率(这里设置的时间是一秒)
mVelocityTracker.computeCurrentVelocity(1000);//该参数指定的是1S内滑动的像素。也可以指定最大速率。 int myScrollFly = (int)mVelocityTracker.getYVelocity();//获得y轴上的速率
最后用fling滑动这个速率即可。
fling(-myScrollFly);//惯性滑动的方法
github地址: https://github.com/renyuan1991/MyFistNesteD/tree/renyuan_ns
转载请注明出处: http://renyuan-1991.iteye.com/blogs/2262643
希望爱好编程的小伙伴能加这个群,互相帮助,共同学习。群号: 141877583