`
renyuan_1991
  • 浏览: 69428 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

NestedScrolling的使用及ScrollView的惯性滑动

阅读更多
NestedScrolling的使用及ScrollView的惯性滑动

转载请注明出处: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)<sumScroll){
            //超过边界
            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一定要统一返回父类消耗的距离。

子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)<sumScroll){
            //超过边界
            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地址:https://github.com/renyuan1991/MyFistNesteD/tree/renyuan_ns
   
    转载请注明出处:http://renyuan-1991.iteye.com/blogs/2262643
    希望爱好编程的小伙伴能加这个群,互相帮助,共同学习。群号: 141877583
   
   
   
   
   
   
   
   
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics