美文网首页
NestedScrolling机制学习(一)

NestedScrolling机制学习(一)

作者: boboyuwu | 来源:发表于2017-06-07 15:22 被阅读11次

    自从5.0推出NestedScrolling这个机制一直没有去了解,之前项目中要求做这种效果后觉得还是有必要学习一下,掌握这个机制是可以做出现在很多App流行的联动滚动效果的。
    比如饿了么这种效果:

    20161226202655700.gif

    原谅我随便盗个效果图~~

    这个机制也是挺复杂的,需要对于view知识有一定的了解,自己刚开始学习这个,也算对于自己学习的一个总结.

    贴下布局

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.boboyuwu.nestedscrolling.MainActivity">
        <com.example.boboyuwu.nestedscrolling.LinearNestedScrollParentLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="@android:color/holo_blue_light"
                android:gravity="center"
                android:text="这是我的ActionBar"
                android:textColor="@android:color/white"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="30dp"
                android:background="@android:color/holo_red_light"
                android:gravity="center"
                android:text="这是中间隔的一个view哦"/>
    
            <com.example.boboyuwu.nestedscrolling.LinearNestedScrollChildLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:text="@string/content"/>
                
            </com.example.boboyuwu.nestedscrolling.LinearNestedScrollChildLayout>
        </com.example.boboyuwu.nestedscrolling.LinearNestedScrollParentLayout>
    
    </FrameLayout>
    

    首先我们定义一个

     LinearNestedScrollParentLayout extends LinearLayout implements NestedScrollingParent
    

    实现NestedScrollingParent接口

        public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
        public void onStopNestedScroll(View target);
        public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed);
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
        public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
        public boolean onNestedPreFling(View target, float velocityX, float velocityY);
    
        public int getNestedScrollAxes();
    

    做为一个接口特么居然这么多实现方法,额滴天啊是不是一眼就想放弃的感觉,我也是这样想的,不过还好系统给我们提供了一个helper类自动帮我们处理这些逻辑.
    我们在init初始化的时候new一个helper对象

    mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
    

    然后在每个实现方法里我们这样调用

      mNestedScrollingParentHelper.onNestedScrollAccepted(child,target,nestedScrollAxes);
    

    把方法中参数传递到helper类相同方法的参数中,copy copy就行了~

    ok我们复制粘贴一通终于"实现了"所有的方法.

    接下来以同样的方式定义一个LinearNestedScrollChildLayout类

    public class LinearNestedScrollChildLayout extends LinearLayout implements NestedScrollingChild
    

    它实现NestedScrollingChild接口,同样我们要实现一堆的方法,这个类也给我们提供了一个helper帮助类简化我们的工作
    在init的时候我们创建一个helper对象

        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
    

    跟parent同样的方式我们使用这个helper类去实现接口中的所有方法,这里就不贴代码了跟上面是一样的.

    好了,基本工作做完接下来我们就去实现一个类似的效果。
    我们重写child触摸事件

       @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mStartY = event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float y = event.getY();
                    int diffY = (int) (mStartY - y);
                    if (startNestedScroll(View.SCROLL_AXIS_VERTICAL) && dispatchNestedPreScroll(0, diffY, consume, offset)) {
                        mOffsetY += offset[1];
                        Log.e("wwwconsume", " offset:" + mOffsetY);
                    } else {
                        scrollBy(0, diffY);
                    }
                    Log.e("wwww", diffY + "     startY:" + mStartY + "   y:" + y + "  top:" + getTop());
                    mStartY = y;
                    break;
                case MotionEvent.ACTION_UP:
    
                    break;
            }
            return true;
        }
    

    当我们移动的时候调用startNestedScroll(View.SCROLL_AXIS_VERTICAL)方法通知我们parent我要开始滑动的,里面传入一个参数,这里我传入一个垂直标记,parent可以根据这个判断返回boolean值,如果返回true就优先于child滑动,如果返回false,那么我们的if判断就不成立走else,自然parent也就不接受这次滚动事件,同时我们还要调用dispatchNestedPreScroll(0, diffY, consume, offset)这个方法里面参数分别是
    params:
    int dx -> 自己当前移动位置的x坐标
    int dy ->自己当前移动位置的y坐标
    int[] consumed -> 一个int[]数组 用于接收parent优先滑动消费的x,y距离,这个由parent滑动消费后指定
    int[] offsetInWindow -> 一个int[]数组 用于记录parent每次事件的偏移量,如果每次child滑动距离parent全部消费了,那么consumed 里面的值是等同于
    offsetInWindow 偏移量的我们在if()判断里打印下看看是不是这样

    06-07 14:36:50.619 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-3  consume:3
    06-07 14:36:50.632 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-18  consume:18
    06-07 14:36:50.649 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-1  consume:1
    06-07 14:36:50.666 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-15  consume:15
    06-07 14:36:50.682 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:2  consume:-2
    06-07 14:36:50.699 25570-25570/com.example.boboyuwu.nestedscrolling E/wwwconsume:  offset:-9  consume:9
    ``
    忽略helper帮我处理后的符号问题可以看到值都是一模一样的因为我在parent消费完了child的滚动距离
    
    ok现在在parent中我们去实现一些细节
    
    
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        Log.e("wwww","onStartNestedScroll");
        return child instanceof NestedScrollingChild && nestedScrollAxes==View.SCROLL_AXIS_VERTICAL;
    }
    

    在onStartNestedScroll中我简单的判断了一下直接返回true代表parent接受这次事件在child之前滚动,这个方法调用完毕后会接着调用onNestedScrollAccepted方法我们可以在开始滚动之前进行一些设置操作,
    接着child的dispatchNestedPreScroll会调用parent的
    onNestedPreScroll(View target, int dx, int dy, int[] consumed) 方法,在这里
    设置了2个方法

    
        @Override
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
            //滚动之前消费
            boolean b = hasHide(dy);
            boolean b1 = hasShow(dy);
            Log.e("aaaaaaaaaaa","b:"+b+"  b1:"+b1);
            if(hasHide(dy) || hasShow(dy)){
                scrollBy(0,dy);
                consumed[1]=dy;
            }
        }
    
        private boolean hasShow(int dy) {
    
            if(dy<0){
                    if(getScrollY()>0 && mChildAt2.getScrollY()==0){
                        Log.e("wwwhasShow", getScrollY()+ "   "  +mChildAt2.getScrollY()+"  "+mChildAt2.getScrollY());
                        return true;
                    }
    
            }
            return false;
        }
    
        public boolean hasHide(int dy){
    
            if(dy>0){
                    if(getScrollY()<mChildAt0MeasuredHeight && mChildAt2.getScrollY()==0){
                        Log.e("wwwhasHide", getScrollY()+"   "  +mChildAt2.getScrollY());
                        return true;
                    }
            }
            return false;
        }
    
    

    当接收到child传过来的dy时,根据scrollTo特性如果是-的认为是向下滚动,如果是+的则向上滚动,所以这里dy<0的时候我们判断向下滚动,这里还需要判断一下mChildAt2.getScrollY()==0因为少了这个判断的话,我们向上滚动一段距离,后再向下滚动这时应该child优先滚动,但是由于 getScrollY()始终>0那么这个方法永远返回true, 如果我们增加判断,由于我们之前child向上滚动过一段距离那么getScrollY()!=0的所以这个时候判断不成立滚动就交给我们child了。

    同样当dy>0则向上滚动,同样逻辑增加一个判断.

    我们滚动自己内容的时候是要限制一下滚动区域的,本来我期望向上滚动的时候滚动actionbar title高度parent就停止滚动,如果不限制它会一直向上滚,所以我们重写一下scrollTo方法因为scrollBy内部也是调用scorllTo的,

      @Override
        public void scrollTo(@Px int x, @Px int y) {
            Log.e("wwwwscrollTo","x:"+x+"   y:"+y+" mChildAt0MeasuredHeight"+mChildAt0MeasuredHeight);
            if(y<0){
                y=0;
            }
            if(y>mChildAt0MeasuredHeight && mChildAt0MeasuredHeight!=0){
                y=mChildAt0MeasuredHeight;
            }
            super.scrollTo(x, y);
        }
    

    这里限制一下如果如果y<0说明向下已经滚动到actionbar title高度,我们限制y=0
    如果向上滚动dy>actionbar title 的height高度那么则限制它就为height

    同样child里面也应该限制下滚动范围原理跟parent一样就不贴代码了,好了,大概思路已经实现了,这里实现方式比较简单主要是熟悉一下NestedScrolling滚动机制,我们看一下代码运行效果,模拟器有点卡真机流畅很多

    FN6BxE4Rwt.gif

    相关文章

      网友评论

          本文标题:NestedScrolling机制学习(一)

          本文链接:https://www.haomeiwen.com/subject/zycnfxtx.html