• Android -- NestedScrolling滑动机制


    1,如今NestedScrolling运用到很多地方了,要想好看一点的滑动变换,基本上就是使用这个来完成的,让我们来简单的了解一下。

    2,NestedScrolling机制能够让父View和子View在滚动式进行配合,其基本流程如下:

    • 当子view开始滚动之前,可以通知父View,让其先于自己进行滚动;
    • 子View自己进行滚动;
    • 子view滚动之后,还可以通知父view继续滚动。
      而要实现这样的交互机制,首先父view要实现NestedScrollingParent接口,而子View需要实现N恩斯特大S从rollingChild接口,在这套机制中子View是发起者,父view是接受回调并做出响应的。
       一下是几个关键的类和接口
    //主要接口
    NestedScrollingChild
    NestedScrollingParent
    //帮助类
    NestedScrollingChildHelper
    NestedScrollingParentHelper
    

      

      一些新的系统View已经帮我们实现了以上两个接口,也就是说他们是支持NestedScrolling,例如:
      NestedScrollView已经实现了NestedScrollingChild和NestedScrollingParent两个接口
      RecycleView已经实现了NestedScrollingChild
      CoordinatorLayout实现了NestedScrollingParent
      ....等等。
     
      NestedScrollingChild接口
          
    //开始、停止嵌套滚动
    public boolean startNestedScroll(int axes); public void stopNestedScroll();
    //触摸滚动相关
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
    //惯性滚动相关 public boolean dispatchNestedPreFling(float velocityX, float velocityY);
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
     
      public boolean startNestedScroll(int axes);
     
      开启嵌套滚动流程(实际上是进行了一些嵌套滚动前准备工作)。
      当找到了能够配合当前子view进行嵌套滚动的父view时,返回值为true(Returns:true if a cooperative parent was found and nested scrolling has been enabled for the current gesture)。
     
      public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
     
      在子view自己进行滚动之前调用此方法,询问父view是否要在子view之前进行滚动。
      此方法的前两个参数用于告诉父View此次要滚动的距离;而第三第四个参数用于子view获取父view消费掉的距离和父view位置的偏移量。
      第一第二个参数为输入参数,即常规的函数参数,调用函数的时候我们需要为其传递确切的值。而第三第四个参数为输出参数,调用函数时我们只需要传递容器(在这里就是两个数组),在调用结束后,我们就可以从容器中获取函数输出的值。
      如果parent消费了一部分或全部距离,则此方法返回true。
     
      public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
     
      在子view自己进行滚动之后调用此方法,询问父view是否还要进行余下(unconsumed)的滚动。
      前四个参数为输入参数,用于告诉父view已经消费和尚未消费的距离,最后一个参数为输出参数,用于子view获取父view位置的偏移量。
      返回值:(翻译出来可能有歧义,直接放原文)true if the event was dispatched, false if it could not be dispatched.
     
      public void stopNestedScroll();
      最后,stopNestedScroll()方法与startNestedScroll(int axes)对应,用于结束嵌套滚动流程;而惯性滚动相关的两个方法与触摸滚动相关的两个方法类似,这里不再赘述。
     
      NestedScrollingParent
      接口概述
      
    //当开启、停止嵌套滚动时被调用
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
    public void onStopNestedScroll(View target);
    //当触摸嵌套滚动时被调用
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
    //当惯性嵌套滚动时被调用
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
    

      

      从命名可以看出,这几个都是回调方法。当调用NestedScrollingChild中的方法时,NestedScrollingParent中与之相对应的方法就会被回调。方法之间的具体对应关系如下:
       
      从上面的接口还有方法我们可以得出一些简单的流程
    • 调用child的startNestedScroll()来发起嵌套滑动流程(实质上是寻找能够配合child进行嵌套滚动的parent)。parent的onStartNestedScroll()会被调用,若此方法返回true,则OnNestScrollAccepted()也会被调用。
    • chuld每次滚动前,可以先询问parent是否要滚动,即调用dispatchNestedScroll(),这时可以回调到parent的OnNestedPreScroll(),parent可以在这个回调中先于child滚动。
    • dispatchNestedPreScroll()之后,child可以进行自己的滚动操作。

    3,自定义NestedScrolling控件

      先看一下效果

      

      先看一下布局文件

      activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    xmlns:tools="http://schemas.android.com/tools"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
        >
    
        <com.qianmo.mynestedscrolling.view.MyNestedScrollParent
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:orientation="vertical">
    
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:src="@mipmap/ic_launcher"/>
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#f0f"
                android:text="上面的图片会被隐藏,而这个文字不会被隐藏"/>
    
            <com.qianmo.mynestedscrolling.view.MyNestedScrollChild
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="123
    456
    789
    111
    222
    333
    444
    555
    666
    777
    888
    999
    14
    12
    13
    44
    55
    66
    77
    88
    99
    11
    22
    33
    44
    55
    66
    77
    88
    99
    77
    88
    88
    8
    88
    88
    "
                    android:textColor="#f0f"
                    android:textSize="20sp"/>
            </com.qianmo.mynestedscrolling.view.MyNestedScrollChild>
        </com.qianmo.mynestedscrolling.view.MyNestedScrollParent>
    </RelativeLayout>
    

      布局文件只是简单的嵌套,MyNestedScrollParent继承Linearlayout,并实现NestedScrollingParent接口,MyNestedScrollChild同理,先来看看MyNestedScrollChild这个类吧。

      MyNestedScrollChild.java

    package com.qianmo.mynestedscrolling.view;
    
    import android.content.Context;
    import android.os.Build;
    import android.support.annotation.RequiresApi;
    import android.support.v4.view.NestedScrollingChild;
    import android.support.v4.view.NestedScrollingChildHelper;
    import android.support.v4.view.ViewCompat;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.widget.LinearLayout;
    
    
    /**
     * Created by Administrator on 2017/2/14 0014.
     * E-Mil:543441727@qq.com
     */
    
    public class MyNestedScrollChild extends LinearLayout implements NestedScrollingChild {
        private NestedScrollingChildHelper mNestedScrollingChildHelper;
        private final int[] offset = new int[2]; //偏移量
        private final int[] consumed = new int[2]; //消费
        private int lastY;
        private int showHeight;
    
    
        public MyNestedScrollChild(Context context) {
            super(context);
        }
    
        public MyNestedScrollChild(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //第一次测量,因为布局文件中高度是wrap_content,因此测量模式为atmost,即高度不超过父控件的剩余空间
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            showHeight = getMeasuredHeight();
    
            //第二次测量,对稿哦度没有任何限制,那么测量出来的就是完全展示内容所需要的高度
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @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_HORIZONTAL)
                            && 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;
            }
            if (y < 0) {
                y = 0;
            }
            super.scrollTo(x, y);
        }
    
        //初始化helper对象
        private NestedScrollingChildHelper getScrollingChildHelper() {
            if (mNestedScrollingChildHelper == null) {
                mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
                mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
            }
            return mNestedScrollingChildHelper;
        }
    
        //实现一下接口
        @Override
        public void setNestedScrollingEnabled(boolean enabled) {
            getScrollingChildHelper().setNestedScrollingEnabled(enabled);
        }
    
        @Override
        public boolean isNestedScrollingEnabled() {
            return getScrollingChildHelper().isNestedScrollingEnabled();
        }
    
        @Override
        public boolean startNestedScroll(int axes) {
            return getScrollingChildHelper().startNestedScroll(axes);
        }
    
        @Override
        public void stopNestedScroll() {
            getScrollingChildHelper().stopNestedScroll();
        }
    
        @Override
        public boolean hasNestedScrollingParent() {
            return getScrollingChildHelper().hasNestedScrollingParent();
        }
    
        @Override
        public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
            return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
        }
    
        @Override
        public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
            return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        }
    
        @Override
        public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
            return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
        }
    
        @Override
        public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
            return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
        }
    }
    

     主要是在OnTouchEvent中先后调用了startNestedScroll()dispatchNestedPreScroll()方法,在借助helper来完成NestedScrollingParent接口方法

      MyNestedScrollParent.java

    package com.qianmo.mynestedscrolling.view;
    
    import android.content.Context;
    import android.support.v4.view.NestedScrollingParent;
    import android.support.v4.view.NestedScrollingParentHelper;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    
    /**
     * Created by wangjitao on 2017/2/14 0014.
     * E-Mail:543441727@qq.com
     * 嵌套滑动机制父View
     */
    
    public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent {
        private ImageView img;
        private TextView tv;
        private MyNestedScrollChild myNestedScrollChild;
        private NestedScrollingParentHelper mNestedScrollingParentHelper;
        private int imgHeight;
        private int tvHeight;
    
        public MyNestedScrollParent(Context context) {
            super(context);
        }
    
        public MyNestedScrollParent(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
        }
    
        //获取子view
        @Override
        protected void onFinishInflate() {
            img = (ImageView) getChildAt(0);
            tv = (TextView) getChildAt(1);
            myNestedScrollChild = (MyNestedScrollChild) getChildAt(2);
            img.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (imgHeight <= 0) {
                        imgHeight = img.getMeasuredHeight();
                    }
                }
            });
            tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (tvHeight <= 0) {
                        tvHeight = tv.getMeasuredHeight();
                    }
                }
            });
        }
    
        //在此可以判断参数target是哪一个子view以及滚动的方向,然后决定是否要配合其进行嵌套滚动
        @Override
        public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
            if (target instanceof MyNestedScrollChild) {
                return true;
            }
            return false;
        }
    
    
        @Override
        public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
            mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
        }
    
        @Override
        public void onStopNestedScroll(View target) {
            mNestedScrollingParentHelper.onStopNestedScroll(target);
        }
    
        //先于child滚动
        //前3个为输入参数,最后一个是输出参数
        @Override
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
            if (showImg(dy) || hideImg(dy)) {//如果需要显示或隐藏图片,即需要自己(parent)滚动
                scrollBy(0, -dy);//滚动
                consumed[1] = dy;//告诉child我消费了多少
            }
        }
    
        //后于child滚动
        @Override
        public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
    
        }
    
        //返回值:是否消费了fling
        @Override
        public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
            return false;
        }
    
        //返回值:是否消费了fling
        @Override
        public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
            return false;
        }
    
        @Override
        public int getNestedScrollAxes() {
            return mNestedScrollingParentHelper.getNestedScrollAxes();
        }
    
        //下拉的时候是否要向下滚动以显示图片
        public boolean showImg(int dy) {
            if (dy > 0) {
                if (getScrollY() > 0 && myNestedScrollChild.getScrollY() == 0) {
                    return true;
                }
            }
    
            return false;
        }
    
        //上拉的时候,是否要向上滚动,隐藏图片
        public boolean hideImg(int dy) {
            if (dy < 0) {
                if (getScrollY() < imgHeight) {
                    return true;
                }
            }
            return false;
        }
    
        //scrollBy内部会调用scrollTo
        //限制滚动范围
        @Override
        public void scrollTo(int x, int y) {
            if (y < 0) {
                y = 0;
            }
            if (y > imgHeight) {
                y = imgHeight;
            }
    
            super.scrollTo(x, y);
        }
    }
    

      MyNestedScrollParent主要是实现一下功能

      ①、在onStartNestedScroll()中判断参数target是哪一个子view以及滚动的方向,然后决定是否要配合其进行嵌套滚动

      ②、在onNestedPreScroll()中获取需要滚动的距离,根据情况决定自己是否要进行滚动,最后还要将自己滚动消费掉的距离存储在consumed数组中回传给child

      就这样基本实现了,很简单有没有,再看看我们接下来要实现的效果,如图:

      

      也很简单,就不在废话了,直接和上面的项目一起,直接上源码  https://github.com/543441727/MyNestedScrolling

      See You Next Time ! 

  • 相关阅读:
    Mongoose Schemas中定义日期以及timestamps选项的妙用
    如何用Linux的命令正确识别cpu的个数和核数【转】
    缓存算法
    使用pm2常见问题
    JavaScript 循环:如何处理 async/await
    常用的Linux操作
    Mysql数据库If语句的使用
    java解析邮箱中的邮件信息
    淘宝分布式数据层TDDL
    maven正式版本和快照版本的区别
  • 原文地址:https://www.cnblogs.com/wjtaigwh/p/6398562.html
Copyright © 2020-2023  润新知