NestedScrolling的使用及ScrollView的惯性滑动

[size=xx-large]NestedScrolling的使用及ScrollView的惯性滑动[/size]

转载请注明出处:[url]http://renyuan-1991.iteye.com/blogs/2262643[/url]

[size=x-large]NestedScrolling介绍[/size]
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()表示本次处理结束。就这么简单,下面总结一下流程:
[size=large]第一步:[/size]
在Childe中创建NestedScrollingChildHelper一般实现一下几个方法即可完成需求:
setNestedScrollingEnabled
isNestedScrollingEnabled
startNestedScroll
stopNestedScroll
hasNestedScrollingParent
dispatchNestedScroll
dispatchNestedPreScroll
dispatchNestedFling
dispatchNestedPreFling


[size=large] 第二步:[/size]
在构造函数里面实例化helper并设置setnestedScrollingEnabled(true)
public MyChildScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
childHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}


[size=large]第三步:[/size]
在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) //超过边界
if((sumScroll+dy)<0){
MyScrollView.this.scrollBy(dx, -sumScroll);//
consumed[1] = sumScroll+dy;
sumScroll = 0;
}else{//未超过边界
MyScrollView.this.scrollBy(dx, dy);
sumScroll += dy;//记录这个view滑动的真实距离
consumed[1] = 0;
}
}else {

}
}

@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}

最后提醒一下,在父类的onNestedPreScroll中如果返回0,在子类中的dispatchNestedPreScroll就会返回false。官网上说“true if the parent consumed some or all of the scroll delta”。可以理解成当我们给consumed赋值的时候如果指定它的值是0,就会认为父类没有消耗。所以在这里强调一下consumed一定要统一返回父类消耗的距离。

[color=green][size=large]子view:[/size][/color]
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);
}
}

[color=green][size=large]中间view:[/size][/color]
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);
}

}

[color=green][size=large]父view:[/size][/color]
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) //超过边界
if((sumScroll+dy)<0){
MyScrollView.this.scrollBy(dx, -sumScroll);//
consumed[1] = sumScroll+dy;
sumScroll = 0;
}else{//未超过边界
MyScrollView.this.scrollBy(dx, dy);
sumScroll += dy;//记录这个view滑动的真实距离
consumed[1] = 0;
}
}else {

}
}

@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
}


对了,好像忘了说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地址:[url]https://github.com/renyuan1991/MyFistNesteD/tree/renyuan_ns[/url]

转载请注明出处:[url]http://renyuan-1991.iteye.com/blogs/2262643[/url]
希望爱好编程的小伙伴能加这个群,互相帮助,共同学习。群号: 141877583

你可能感兴趣的:(Android移动开发)