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 | 作用 |
---|---|---|
setNestedScrollingEnabled | onNestedScrollAccepted | 是否可嵌套滑动 |
isNestedScrollingEnabled | 是否可嵌套滑动 | |
startNestedScroll | onStartNestedScroll | 开始嵌套滑动,子View回调父View |
dispatchNestedPreScroll | onNestedPreScroll | 嵌套滑动前的准备工作,子View回调父View |
dispatchNestedScroll | onNestedScroll | 嵌套滑动,子View回调父View |
dispatchNestedPreFling | onNestedPreFling | Fling前的准备工作,子View回调父View |
dispatchNestedFling | onNestedFling | Fling,子View回调父View |
stopNestedScroll | onStopNestedScroll | 结束滑动,子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机制