NestScroll嵌套滑动

NestScroll嵌套滑动

标签(空格分隔): 未分类


引用:Android 嵌套滑动——NestedScrolling完全解析

效果图:

主要代码:
xml:

<?xml version="1.0" encoding="utf-8"?>
<xiey94.com.nestedscrolling.nest1.NestParent 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <View
        android:layout_width="50dip"
        android:layout_height="50dip"
        android:layout_gravity="center"
        android:layout_marginTop="100dip"
        android:background="#f0f" />

    <xiey94.com.nestedscrolling.nest1.NestChild
        android:layout_width="50dip"
        android:layout_height="50dip"
        android:layout_gravity="center"
        android:layout_marginTop="200dip"
        android:background="#88ff7f3c" />
</xiey94.com.nestedscrolling.nest1.NestParent>

NestChild:

package xiey94.com.nestedscrolling.nest1;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class NestChild extends View implements NestedScrollingChild {

    public static final String TAG = "cctw";
    private NestedScrollingChildHelper childHelper;

    public NestChild(Context context) {
        super(context);
    }

    public NestChild(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //生成辅助类,并传入当前控件
        childHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return childHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return childHelper.isNestedScrollingEnabled();
    }

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        childHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return childHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        childHelper.stopNestedScroll();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
        //滚动之后将剩余滑动传给父类
        return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {
        //子View滚动之前将滑动距离传给父类
        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);
    }

    private int mOldY;
    private int[] mConsumed = new int[2];
    private int[] mOffset = new int[2];

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //启动滑动,传入方向
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                //记录y值
                mOldY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int y = (int) event.getRawY();
                //计算y值得偏移量
                int offsetY = y - mOldY;
                //通知父类,如果返回true,表示父类消耗了触摸
                if (dispatchNestedPreScroll(0, offsetY, mConsumed, mOffset)) {
                    offsetY -= mConsumed[1];
                }
                int unConsumed = 0;
                float targetY = getTranslationY() + offsetY;
                if (targetY > -40 && targetY < 40) {
                    setTranslationY(targetY);
                } else {
                    unConsumed = offsetY;
                    offsetY = 0;
                }
                //滚动完成之后,通知当前滑动的状态
                dispatchNestedScroll(0, offsetY, 0, unConsumed, mOffset);
                mOldY = y;
                break;
            case MotionEvent.ACTION_UP:
                //滑动结束
                stopNestedScroll();
                break;
            default:
                break;
        }

        return true;
    }
}

NestParent:

package xiey94.com.nestedscrolling.nest1;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class NestParent extends LinearLayout implements NestedScrollingParent {
    public static final String TAG = "ccer";
    NestedScrollingParentHelper parentHelper;

    public NestParent(Context context) {
        this(context, null);
    }

    public NestParent(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NestParent(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        parentHelper = new NestedScrollingParentHelper(this);
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        //child 嵌套滑动的子控件(当前控件的子控件),target,手指触摸的控件
        return true;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        parentHelper.onNestedScrollAccepted(child, target, axes);
    }

    @Override
    public void onStopNestedScroll(View child) {
        parentHelper.onStopNestedScroll(child);
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        getChildAt(0).setTranslationY(getChildAt(0).getTranslationY() + dyUnconsumed);
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        //开始滑动之前
        super.onNestedPreScroll(target, dx, dy, consumed);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return false;
    }

    @Override
    public int getNestedScrollAxes() {
        return parentHelper.getNestedScrollAxes();
    }
}

说一下过程:
一开始在构造函数里面初始化Helper,然后设置可以嵌套滑动;

public NestChild(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    //生成辅助类,并传入当前控件
    childHelper = new NestedScrollingChildHelper(this);
    setNestedScrollingEnabled(true);
}

@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    //child 嵌套滑动的子控件(当前控件的子控件),target,手指触摸的控件
    return true;
}

然后开始嵌套滑动,并指定滑动的方向为竖直方向;并记录当前下手的y值

case MotionEvent.ACTION_DOWN:
    //启动滑动,传入方向
    startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
    //记录y值
    mOldY = (int) event.getRawY();
    break;

移动的时候记录y轴的偏移量;

int y = (int) event.getRawY();
//计算y值得偏移量
int offsetY = y - mOldY;

然后将偏移量传给dispatchNestedPreScroll方法中,回调父类的onNestedPreScroll;

//通知父类,如果返回true,表示父类消耗了触摸
if (dispatchNestedPreScroll(0, offsetY, mConsumed, mOffset)) {
    offsetY -= mConsumed[1];
}

在我们这个demo中,父类在这个回调并没有做 任何操作,所以offsetY这个偏移量并没有消耗;

累积偏移量,

float targetY = getTranslationY() + offsetY;

如果偏移量在-40到40之间则直接平移,否则则统计未消耗的距离,并将偏移量置为0

int unConsumed = 0;
if (targetY > -40 && targetY < 40) {
    setTranslationY(targetY);
} else {
    unConsumed = offsetY;
    offsetY = 0;
}

调用dispatchNestedScroll将未消耗的距离传递给父View的onNestedScroll

//滚动完成之后,通知当前滑动的状态
dispatchNestedScroll(0, offsetY, 0, unConsumed, mOffset);
mOldY = y;
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
    getChildAt(0).setTranslationY(getChildAt(0).getTranslationY() + dyUnconsumed);
}

在这里消耗子View未消耗的距离;

最后结束嵌套滑动

case MotionEvent.ACTION_UP:
    //滑动结束
    stopNestedScroll();
    break;

这就是 全部的过程

再来看看另一个例子:

引用:android NestedScroll嵌套滑动机制完全解析-原来如此简单(修正自己的一个错误说法)

先贴一张图看看效果:
在这里插入图片描述

这里先贴一下代码:
MyNestedScrollingParent

public class MyNestedScrollingParent extends LinearLayout implements NestedScrollingParent {
    private NestedScrollingParentHelper mParentHelper;
    private ImageView iv;
    private TextView tv;
    private MyNestedScrollingChild nsv;
    private int ivHeight, tvHeight;

    public MyNestedScrollingParent(Context context) {
        super(context);
        init();
    }

    public MyNestedScrollingParent(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        iv = (ImageView) getChildAt(0);
        tv = (TextView) getChildAt(1);
        nsv = (MyNestedScrollingChild) getChildAt(2);
        iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                ivHeight = iv.getMeasuredHeight();
            }
        });
        tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                tvHeight = tv.getMeasuredHeight();
            }
        });

    }

    private void init() {
        mParentHelper = new NestedScrollingParentHelper(this);
    }

    /**
     * @param child
     * @param target
     * @param nestedScrollAxes 嵌套滑动的坐标系,也就是用来判断是X轴滑动还是Y轴滑动,返回true或者false,返回false就没得玩了
     * @return 如果要接受嵌套滑动操作,就返回true吧
     */
    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) {
        return true;
    }

    @Override
    public void onNestedScrollAccepted(@NonNull View var1, @NonNull View var2, int var3) {
        mParentHelper.onNestedScrollAccepted(var1, var2, var3);
    }

    @Override
    public void onStopNestedScroll(@NonNull View var1) {
        mParentHelper.onStopNestedScroll(var1);
    }

    @Override
    public void onNestedScroll(@NonNull View var1, int var2, int var3, int var4, int var5) {
    }

    /**
     * @param child
     * @param dx
     * @param dy
     * @param consumed
     */
    @Override
    public void onNestedPreScroll(@NonNull View child, int dx, int dy, @NonNull int[] consumed) {
        if (showImage(dy) || hideImage(dy)) {
            consumed[1] = dy;//完全消费有y轴的滑动
            scrollBy(0, dy);
        }
    }

    @Override
    public boolean onNestedFling(@NonNull View var1, float var2, float var3, boolean var4) {
        return super.onNestedFling(var1, var2, var3, var4);
    }

    @Override
    public boolean onNestedPreFling(@NonNull View var1, float var2, float var3) {
        return super.onNestedPreFling(var1, var2, var3);
    }

    @Override
    public int getNestedScrollAxes() {
        return super.getNestedScrollAxes();
    }

    /**
     * 滑动控制在图片的高度
     *
     * @param x
     * @param y
     */
    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > ivHeight) {
            y = ivHeight;
        }
        super.scrollTo(x, y);
    }

    /**
     * 往上往下滑,当Child滑动完了,只要没有滑动到起始位置(getScrollY==0),就一直往下滑,当滑动到起始位置时,滑动事件再传给Childs
     *
     * @param dy
     * @return
     */
    private boolean showImage(int dy) {
        if (dy < 0) {
            if (getScrollY() > 0 && nsv.getScrollY() == 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * 往上滑是隐藏图片
     *
     * @param dy >0是往上滑动,<0是往下滑动
     *           getScrollY()往上滑动值越大
     * @return 往上滑,当滑动的距离超过图片的高度时,则将滑动事件传给Child
     */
    private boolean hideImage(int dy) {
        if (dy > 0) {
            if (getScrollY() < ivHeight) {
                return true;
            }
        }
        return false;
    }
}

MyNestedScrollingChild

public class MyNestedScrollingChild extends LinearLayout implements NestedScrollingChild {
    private NestedScrollingChildHelper mChildHelper;
    private final int[] mScrollOffset = new int[2];
    private final int[] mScrollConsumed = new int[2];
    private int mLastTouchX, mLastTouchY;
    private int showHeight;

    public MyNestedScrollingChild(Context context) {
        super(context);
        init();
    }

    public MyNestedScrollingChild(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mChildHelper = new NestedScrollingChildHelper(this);
        mChildHelper.setNestedScrollingEnabled(true);
    }

    @Override
    public void setNestedScrollingEnabled(boolean var1) {
        mChildHelper.setNestedScrollingEnabled(var1);
    }

    /**
     * 允许嵌套滑动
     * @return
     */
    @Override
    public boolean isNestedScrollingEnabled() {
        return true;
    }

    @Override
    public boolean startNestedScroll(int var1) {
        return mChildHelper.startNestedScroll(var1);
    }

    @Override
    public void stopNestedScroll() {
        mChildHelper.stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return mChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int var1, int var2, int var3, int var4, @Nullable int[] var5) {
        return mChildHelper.dispatchNestedScroll(var1, var2, var3, var4, var5);
    }

    @Override
    public boolean dispatchNestedPreScroll(int var1, int var2, @Nullable int[] var3, @Nullable int[] var4) {
        return mChildHelper.dispatchNestedPreScroll(var1, var2, var3, var4);
    }

    @Override
    public boolean dispatchNestedFling(float var1, float var2, boolean var3) {
        return mChildHelper.dispatchNestedFling(var1, var2, var3);
    }

    @Override
    public boolean dispatchNestedPreFling(float var1, float var2) {
        return mChildHelper.dispatchNestedPreFling(var1, var2);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastTouchY = (int) (event.getRawY() + 0.5f);
                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                startNestedScroll(nestedScrollAxis);
                break;
            case MotionEvent.ACTION_MOVE:
                int x = (int) (event.getX() + 0.5f);
                int y = (int) (event.getRawY() + 0.5f);
                int dx = mLastTouchX - x;
                int dy = mLastTouchY - y;
                mLastTouchY = y;
                mLastTouchX = x;
                if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
                    dy -= mScrollConsumed[1];
                    if (dy == 0) {
                        return true;
                    }
                } else {
                    scrollBy(0, dy);
                }
                break;
        }
        return true;
    }

    @Override
    public void scrollTo(int x, int y) {
        int mh = getMeasuredHeight();
        int maxY = mh - showHeight;
        if (y < 0) {
            y = 0;
        }
        if (y > maxY) {
            y = maxY;
        }
        super.scrollTo(x, y);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (showHeight <= 0) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            showHeight = getMeasuredHeight();
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(1000000, MeasureSpec.UNSPECIFIED);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

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"
    tools:context=".MainActivity">

    <xiey94.com.nestedscrolling.MyNestedScrollingParent
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="100dip"
            android:src="@mipmap/ic_launcher" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dip"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text="@string/app_name"
            android:textColor="#fff"
            android:textSize="20sp" />

        <xiey94.com.nestedscrolling.MyNestedScrollingChild
            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="@string/test"
                android:textColor="@color/colorPrimaryDark"
                android:textSize="16sp" />
        </xiey94.com.nestedscrolling.MyNestedScrollingChild>
    </xiey94.com.nestedscrolling.MyNestedScrollingParent>

</RelativeLayout>

布局上就是一个父布局,第一个Child是ImageView,第二个Child是TextView,第三个Child是我们写的子View
效果就是当网上滑动时,parent整体往上滑,直到整个ImageView区域全部隐藏,然后我们写的字View开始往上滑动;反之,往下滑,子View先滑动到顶部,然后parent整体下滑,直到整个ImageView完全显示;

流程:

也是指定可以嵌套滑动:

private void init() {
    mChildHelper = new NestedScrollingChildHelper(this);
    mChildHelper.setNestedScrollingEnabled(true);
}

@Override
public void setNestedScrollingEnabled(boolean var1) {
    mChildHelper.setNestedScrollingEnabled(var1);
}

@Override
public boolean isNestedScrollingEnabled() {
    return true;
}

指定方向开始滑动

case MotionEvent.ACTION_DOWN:
    mLastTouchY = (int) (event.getRawY() + 0.5f);
    int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
    startNestedScroll(nestedScrollAxis);
    break;

计算距离差

int x = (int) (event.getX() + 0.5f);
int y = (int) (event.getRawY() + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;

将产生的距离差传给dispatchNestedPreScroll,回调父View的onNestedPreScroll
看父类消耗的距离,初始往上滑的时候,如果滑动的距离小于图片的高度则父类消耗掉;

    /**
     * 往上滑是隐藏图片
     *
     * @param dy >0是往上滑动,<0是往下滑动
     *           getScrollY()往上滑动值越大
     * @return 往上滑,当滑动的距离超过图片的高度时,则将滑动事件传给Child
     */
    private boolean hideImage(int dy) {
        if (dy > 0) {
            if (getScrollY() < ivHeight) {
                return true;
            }
        }
        return false;
    }

或者往下滑,如果子View滑到了顶部并且父类滑动的距离要大于0并且小于图片的高度则显示图片,也是父View消耗;

    /**
     * 从上往下滑,当Child滑动完了,只要没有滑动到起始位置(getScrollY==0),就一直往下滑,当滑动到起始位置时,滑动事件再传给Childs
     *
     * @param dy
     * @return
     */
    private boolean showImage(int dy) {
        if (dy < 0) {
            if (getScrollY() > 0 && nsv.getScrollY() == 0) {
                return true;
            }
        }
        return false;
    }

将父类可以消耗的距离全部消耗

    @Override
    public void onNestedPreScroll(@NonNull View child, int dx, int dy, @NonNull int[] consumed) {
        if (showImage(dy) || hideImage(dy)) {
            consumed[1] = dy;//完全消费有y轴的滑动
            scrollBy(0, dy);
        }
    }

父View滑动的距离固定在图片的高度

    /**
     * 滑动控制在图片的高度
     *
     * @param x
     * @param y
     */
    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > ivHeight) {
            y = ivHeight;
        }
        super.scrollTo(x, y);
    }

否则就是子View消耗

scrollBy(0, dy);

子View滑动的距离也指定

    @Override
    public void scrollTo(int x, int y) {
        int mh = getMeasuredHeight();
        int maxY = mh - showHeight;
        if (y < 0) {
            y = 0;
        }
        if (y > maxY) {
            y = maxY;
        }
        super.scrollTo(x, y);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (showHeight <= 0) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            showHeight = getMeasuredHeight();
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(1000000, MeasureSpec.UNSPECIFIED);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

代码主要就这些;

嵌套滑动一共涉及到了四个类:
NestedScrollingChild、NestedScrollingChildHelper、
NestedScrollingParent、NestedScrollingParentHelper;

NestedScrollingChild是一个接口,主要里面是:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.support.v4.view;

import android.support.annotation.Nullable;

public interface NestedScrollingChild {
    void setNestedScrollingEnabled(boolean var1);

    boolean isNestedScrollingEnabled();

    boolean startNestedScroll(int var1);

    void stopNestedScroll();

    boolean hasNestedScrollingParent();

    boolean dispatchNestedScroll(int var1, int var2, int var3, int var4, @Nullable int[] var5);

    boolean dispatchNestedPreScroll(int var1, int var2, @Nullable int[] var3, @Nullable int[] var4);

    boolean dispatchNestedFling(float var1, float var2, boolean var3);

    boolean dispatchNestedPreFling(float var1, float var2);
}

目前这些方法我们基本上都见到过,就一个没见到

boolean hasNestedScrollingParent();

但是他在Helper中间接的使用了,所以目前这个接口方法我们都见过,而且

boolean dispatchNestedFling(float var1, float var2, boolean var3);

boolean dispatchNestedPreFling(float var1, float var2);

除了这俩,在父View中的回调也都用过;所以也就没有特别的地方了;

再来看看NestedScrollingParent

同理他也是接口

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.support.v4.view;

import android.support.annotation.NonNull;
import android.view.View;

public interface NestedScrollingParent {
    boolean onStartNestedScroll(@NonNull View var1, @NonNull View var2, int var3);

    void onNestedScrollAccepted(@NonNull View var1, @NonNull View var2, int var3);

    void onStopNestedScroll(@NonNull View var1);

    void onNestedScroll(@NonNull View var1, int var2, int var3, int var4, int var5);

    void onNestedPreScroll(@NonNull View var1, int var2, int var3, @NonNull int[] var4);

    boolean onNestedFling(@NonNull View var1, float var2, float var3, boolean var4);

    boolean onNestedPreFling(@NonNull View var1, float var2, float var3);

    int getNestedScrollAxes();
}

可以看出,这些方法也都用过,也没有特殊的地方

对比一下:

子View父View作用
setNestedScrollingEnabledonNestedScrollAccepted是否可嵌套滑动
isNestedScrollingEnabled是否可嵌套滑动
startNestedScrollonStartNestedScroll开始嵌套滑动,子View回调父View
dispatchNestedPreScrollonNestedPreScroll嵌套滑动前的准备工作,子View回调父View
dispatchNestedScrollonNestedScroll嵌套滑动,子View回调父View
dispatchNestedPreFlingonNestedPreFlingFling前的准备工作,子View回调父View
dispatchNestedFlingonNestedFlingFling,子View回调父View
stopNestedScrollonStopNestedScroll结束滑动,子View回调父View
getNestedScrollAxes滑动方向
hasNestedScrollingParent是否有可以嵌套滑动的父View

一般的触摸消息的分发都是从外向内的,由外层的ViewGroup的dispatchTouchEvent方法调用到内层的View的dispatchTouchEvent方法.
而NestedScroll提供了一个反向的机制,内层的view在接收到ACTION_MOVE的时候,将滚动消息先传回给外层的ViewGroup,看外层的ViewGroup是不是需要消耗一部分的移动,然后内层的View再去消耗剩下的移动.内层view可以消耗剩下的滚动的一部分,如果还没有消耗完,外层的view可以再选择把最后剩下的滚动消耗掉.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.support.v4.view;

import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;

public class NestedScrollingParentHelper {
    private final ViewGroup mViewGroup;
    private int mNestedScrollAxes;

    public NestedScrollingParentHelper(@NonNull ViewGroup viewGroup) {
        this.mViewGroup = viewGroup;
    }

    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) {
        this.onNestedScrollAccepted(child, target, axes, 0);
    }

    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
        this.mNestedScrollAxes = axes;
    }

    public int getNestedScrollAxes() {
        return this.mNestedScrollAxes;
    }

    public void onStopNestedScroll(@NonNull View target) {
        this.onStopNestedScroll(target, 0);
    }

    public void onStopNestedScroll(@NonNull View target, int type) {
        this.mNestedScrollAxes = 0;
    }
}

NestedScrollingParentHelper 里面的东西实在是少,本质上就是一个set/get的实体;

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.support.v4.view;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewParent;

public class NestedScrollingChildHelper {
    private ViewParent mNestedScrollingParentTouch;
    private ViewParent mNestedScrollingParentNonTouch;
    private final View mView;
    private boolean mIsNestedScrollingEnabled;
    private int[] mTempNestedScrollConsumed;

    //把当前子View传进去,赋值
    public NestedScrollingChildHelper(@NonNull View view) {
        this.mView = view;
    }

    //设置可以嵌套滑动
    public void setNestedScrollingEnabled(boolean enabled) {
        if (this.mIsNestedScrollingEnabled) {
            ViewCompat.stopNestedScroll(this.mView);
        }

        this.mIsNestedScrollingEnabled = enabled;
    }

    //get
    public boolean isNestedScrollingEnabled() {
        return this.mIsNestedScrollingEnabled;
    }

    //get
    public boolean hasNestedScrollingParent() {
        return this.hasNestedScrollingParent(0);
    }

    //get
    public boolean hasNestedScrollingParent(int type) {
        return this.getNestedScrollingParentForType(type) != null;
    }

    //get
    public boolean startNestedScroll(int axes) {
        return this.startNestedScroll(axes, 0);
    }

    //
    public boolean startNestedScroll(int axes, int type) {
        //验证一个滑动流程只可以startNestedScroll一次
        if (this.hasNestedScrollingParent(type)) {
            return true;
        } else {
            //是否已经启动嵌套滑动
            if (this.isNestedScrollingEnabled()) {
                ViewParent p = this.mView.getParent();

                for(View child = this.mView; p != null; p = p.getParent()) {
                    //判断父view 是否实现NestedScrollingParent 且接口的onStartNestedScroll方法返回true
                    if (ViewParentCompat.onStartNestedScroll(p, child, this.mView, axes, type)) {
                        this.setNestedScrollingParentForType(type, p);
                        //调用NestedScrollingParent 的onNestedScrollAccepted回调方法
                        ViewParentCompat.onNestedScrollAccepted(p, child, this.mView, axes, type);
                        return true;
                    }

                    if (p instanceof View) {
                        child = (View)p;
                    }
                }
            }

            return false;
        }
    }

    //get
    public void stopNestedScroll() {
        this.stopNestedScroll(0);
    }
    
    //停止
    public void stopNestedScroll(int type) {
        ViewParent parent = this.getNestedScrollingParentForType(type);
        if (parent != null) {
            ViewParentCompat.onStopNestedScroll(parent, this.mView, type);
            this.setNestedScrollingParentForType(type, (ViewParent)null);
        }

    }
    
    //get
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
        return this.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, 0);
    }
    
    分发滑动事件
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, int type) {
        //判断是否启动嵌套滑动
        if (this.isNestedScrollingEnabled()) {
            //判断是否拥有实现了嵌套滑动机制的parent  View
            ViewParent parent = this.getNestedScrollingParentForType(type);
            if (parent == null) {
                return false;
            }
            
            //如果消耗的和未消耗的具体有一个不为0,则计算处理消耗
            if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
                int startX = 0;
                int startY = 0;
                //计算出偏移量
                if (offsetInWindow != null) {
                    this.mView.getLocationInWindow(offsetInWindow);
                    startX = offsetInWindow[0];
                    startY = offsetInWindow[1];
                }
                
                //滑动消耗
                ViewParentCompat.onNestedScroll(parent, this.mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
                //计算出滑动后剩余的偏移量
                if (offsetInWindow != null) {
                    this.mView.getLocationInWindow(offsetInWindow);
                    offsetInWindow[0] -= startX;
                    offsetInWindow[1] -= startY;
                }

                return true;
            }
            
            //未消耗,则偏移量为0
            if (offsetInWindow != null) {
                offsetInWindow[0] = 0;
                offsetInWindow[1] = 0;
            }
        }

        return false;
    }
    
    //get
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {
        return this.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, 0);
    }
    
    //滑动前的消耗
    //offsetInWindow 记录屏幕坐标偏移量
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) {
        //是否可以嵌套滑动
        if (this.isNestedScrollingEnabled()) {
            //是否有实现了嵌套滑动的parent View
            ViewParent parent = this.getNestedScrollingParentForType(type);
            if (parent == null) {
                return false;
            }
            
            //如果移动的距离不为0,则计算消耗
            if (dx != 0 || dy != 0) {
                int startX = 0;
                int startY = 0;
                //计算偏移量
                if (offsetInWindow != null) {
                    this.mView.getLocationInWindow(offsetInWindow);
                    startX = offsetInWindow[0];
                    startY = offsetInWindow[1];
                }
                
                //如果consumed为null则临时创建一个新的
                if (consumed == null) {
                    if (this.mTempNestedScrollConsumed == null) {
                        this.mTempNestedScrollConsumed = new int[2];
                    }

                    consumed = this.mTempNestedScrollConsumed;
                }

                consumed[0] = 0;
                consumed[1] = 0;
                //滑动
                ViewParentCompat.onNestedPreScroll(parent, this.mView, dx, dy, consumed, type);
                //剩余偏移量
                if (offsetInWindow != null) {
                    this.mView.getLocationInWindow(offsetInWindow);
                    offsetInWindow[0] -= startX;
                    offsetInWindow[1] -= startY;
                }
                
                //判断是否有消耗,有消耗则返回true
                return consumed[0] != 0 || consumed[1] != 0;
            }

            if (offsetInWindow != null) {
                offsetInWindow[0] = 0;
                offsetInWindow[1] = 0;
            }
        }

        return false;
    }
    
    //分发fling
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        if (this.isNestedScrollingEnabled()) {
            ViewParent parent = this.getNestedScrollingParentForType(0);
            if (parent != null) {
                return ViewParentCompat.onNestedFling(parent, this.mView, velocityX, velocityY, consumed);
            }
        }

        return false;
    }
    
    //分发预fling
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        if (this.isNestedScrollingEnabled()) {
            ViewParent parent = this.getNestedScrollingParentForType(0);
            if (parent != null) {
                return ViewParentCompat.onNestedPreFling(parent, this.mView, velocityX, velocityY);
            }
        }

        return false;
    }

    public void onDetachedFromWindow() {
        ViewCompat.stopNestedScroll(this.mView);
    }

    public void onStopNestedScroll(@NonNull View child) {
        ViewCompat.stopNestedScroll(this.mView);
    }
    
    //get;   type :0是手动触发;1是非手动触发;
    private ViewParent getNestedScrollingParentForType(int type) {
        switch(type) {
        case 0:
            return this.mNestedScrollingParentTouch;
        case 1:
            return this.mNestedScrollingParentNonTouch;
        default:
            return null;
        }
    }

    //set
    private void setNestedScrollingParentForType(int type, ViewParent p) {
        switch(type) {
        case 0:
            this.mNestedScrollingParentTouch = p;
            break;
        case 1:
            this.mNestedScrollingParentNonTouch = p;
        }

    }
}

案例:
仿美团详情滑动界面,并兼容NestedScroll嵌套
Android NestedScroll嵌套滑动机制解析

2018-12-17 补充

果然是学功夫要易于造功夫

在参考了这么多案例之后,又简单的摸清了原理之后,我就想创造一个案例:

预览图,我想要的效果就是当列表下拉时,头出现,列表上拉时,头隐藏

这个效果应该大家都见过,在Toolbar和CoordinatorLayout等交互的时候就可以设置

这里主要体现了Parent

<?xml version="1.0" encoding="utf-8"?>
<xiey94.com.nestedscrolling.nest2.NestParent2 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <xiey94.com.nestedscrolling.nest2.NestTextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="#88ff7f3c"
        android:padding="5dip"
        android:text="@string/test_1"
        android:textColor="#fff"
        android:textSize="16sp" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

</xiey94.com.nestedscrolling.nest2.NestParent2>

这个NestTextView暂时可以忽略,就当他是一个普通的TextView

package xiey94.com.nestedscrolling.nest2;

import android.content.Context;
import android.support.annotation.Nullable;
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.LinearLayout;

public class NestParent2 extends LinearLayout implements NestedScrollingParent {

    public static final String TAG = "cctw";
    private NestedScrollingParentHelper parentHelper;

    private View child0, child1;
    //获取第一个View的高度
    private int child0H;
    private int bottom;

    public NestParent2(Context context) {
        this(context, null);
    }

    public NestParent2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NestParent2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        parentHelper = new NestedScrollingParentHelper(this);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        child0 = getChildAt(0);
        child1 = getChildAt(1);
        child0.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                child0H = child0.getMeasuredHeight();
                bottom = child1.getBottom();
            }
        });
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return true;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        parentHelper.onNestedScrollAccepted(child, target, axes);
    }

    @Override
    public void onStopNestedScroll(View child) {
        parentHelper.onStopNestedScroll(child);
    }

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

    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        if (dy > 0 && getScrollY() < child0H) {
            //往上滑;隐藏第一个View
//            consumed[1] = dy;
            scrollBy(0, dy);
            child1.layout(child1.getLeft(), child1.getTop(), child1.getRight(), bottom + getScrollY());
        } else if (dy < 0 && getScrollY() > -child0H) {
            //往下滑;显示第一个View
//            consumed[1] = dy;
            scrollBy(0, dy);
            child1.layout(child1.getLeft(), child1.getTop(), child1.getRight(), bottom + getScrollY());
        }
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return false;
    }

    @Override
    public int getNestedScrollAxes() {
        return parentHelper.getNestedScrollAxes();
    }

    //scrollBy调用的就是scrollTo;这里限制头的滑动范围
    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > child0H) {
            y = child0H;
        }
        super.scrollTo(x, y);
    }

    private int abs(int x) {
        return Math.abs(x);
    }

}

参考:

NestedScrollingChild
NestedScrollingChild2
NestedScrollingChild3
NestedScrollingParent
NestedScrollingParent2
NestedScrollingParent3
android NestedScroll嵌套滑动机制完全解析-原来如此简单(修正自己的一个错误说法)
Android 嵌套滑动——NestedScrolling完全解析
安卓嵌套滚动NestedScroll了解一下
仿美团详情滑动界面,并兼容NestedScroll嵌套
Android NestedScroll嵌套滑动机制解析
Android NestedScrolling全面解析 - 带你实现一个支持嵌套滑动的下拉刷新(上篇)
Android 嵌套滑动机制(NestedScrolling)
从源码角度分析嵌套滑动机制NestedScrolling
优雅的嵌套滑动解决方式-NestedScroll
Android NestedScrolling机制完全解析 带你玩转嵌套滑动
嵌套滚动利器–NestedScrolling机制


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值