table of Contents
- Why do we need to scroll
- Scroll function description
- Call flow diagram
- Simple Case
Why do we need to scroll nesting
Event handling, processing an event is done by a View, the View can not handle events to other View. NestedScrollxxx interface, providing a solution that can deal with a co-event tree View a scroll event. Thus a more elegant implementation of a variety of gorgeous rolling effect.
Nested rolling, not nested slide. Scroll rolling layout of content (Scroll), and change the layout of a rolling position (Move). The core interface NestedScrollChild and NestedScrollParent
NestedScrollxxx Functional Description
NestedScrollChild
public interface NestedScrollingChild {
/**
*使当前View实例能嵌套滚动
*/
void setNestedScrollingEnabled(boolean enabled);
/**
* 当前View是否开起了嵌套滚动
*/
boolean isNestedScrollingEnabled();
/**
*通常在onTouch事件的MotionEvent#ACTION_DOWN中调用该方法开始滚动。如果调用了ViewParent#requestDisallowInterceptTouchEvent(boolean)
*方法,嵌套滚动自动结束。通常结束滚动的方法是调用{@link #stopNestedScroll()}
* 返回值为true时,表示有可以嵌套滚动的父类被找到,否则将忽略掉本次滚动。
*(如果当前正处于滚动当中,该方法直接返回true)
*/
boolean startNestedScroll(@ScrollAxis int axes);
/**
* 停止嵌套滚动,如果当然没有处于滚动,并调用该方法,将是不友好的
*/
void stopNestedScroll();
/**
*
*当前view是否有一个可嵌套滚动的父亲
*/
boolean hasNestedScrollingParent();
/**
* 嵌套滚动中的滚动信息的分发(先自己,后父亲)
*
* @param dxConsumed 当前View水平消耗的距离(单位px)
* @param dyConsumed 当前View垂直消耗的距离(单位px)
* @param dxUnconsumed 当前View水平未消耗的距离(单位px)
* @param dyUnconsumed 当前View垂直未消耗的距离(单位px)
* @param offsetInWindow 可选参数.
*/
boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
/**
* 嵌套滚动中的滚动信息的分发(先父亲,后自己)
*
* @param dx 水平需要滚动的距离(单位px)
* @param dy 垂直需要滚动的距离(单位px)
* @param consumed Output. consumed[0] 表示父亲(祖上)消耗的水平距离,
consumed[1] 表示父亲(祖上)消耗的垂直距离,
* @param offsetInWindow 可选参数
*/
boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
@Nullable int[] offsetInWindow);
/**
* 将Fling分发给父亲
* Fling的意思是:触摸滚动结束时有一个速度,而且这个速度的值大于
*{@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
* 场景:1 如果当前View滚动到了边界,可以调用该方法让其父亲消耗该Fling
* 2 父亲有监听当前View滚动的需求
* @param velocityX 每秒水平方向滚动的距离
* @param velocityY 每秒垂直方向滚动的距离
* @param consumed 当前View是否消耗该fling了
* @return 父亲是否消耗处理了该FLing
*/
boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
/**
* 在自己处理fling之前,先传递给父亲去处理
* @param velocityX 每秒水平方向滚动的距离
* @param velocityY 每秒垂直方向滚动的距离
* @param consumed 当前View是否消耗该fling了
* @return 父亲是否消耗处理了该FLing
*/
boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
NestedScrollParent
public interface NestedScrollingParent {
/**
*对子view的startNestedScroll()方法的响应
* @param child 直接子Viewe
* @param target 初始化嵌套滚动的Viewe
* @param axes 嵌套滚动的方向 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
* @return true 如果接受嵌套滚动,则返回true
*/
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
/**
* 在onStartNestedScroll()或者onStartNestedScroll()方法调用后,被调用。目的就是在初始化view的嵌套配置
* @param child 当前ViewGroup的直接子类
* @param target 当前ViewGroup中初始化嵌套滚动的子孙
* @param axes 滚动的方向 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
*/
void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
/**
* 停止嵌套滚动后的 善后处理操作
*/
void onStopNestedScroll(@NonNull View target);
/**
* 处理嵌套滚动
* 前提是onStartNestedScroll()返回值为True
* @param target 嵌套滚动发起的子孙View
* @param dxConsumed 子孙View已经消费的水平距离
* @param dyConsumed 子孙View已经消费的垂直距离
* @param dxUnconsumed 子孙View未消费的水平距离
* @param dyUnconsumed 子孙View未消费的垂直距离
*/
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
/**
* 在子View之前处理嵌套滚动
*
* @param target 嵌套滚动发起的子孙View
* @param dx 事件触发时,水平需要滚动的距离
* @param dy 事件触发时,垂直需要滚动的距离
* @param consumed Output. 水平和垂直方向被父类消费的距离
*/
void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
/**
* 当前ViewGroup实现嵌套Fling效果
*
* @param target 当前ViewGroup的中初始化嵌套Fling的子孙
* @param velocityX 水平方向的速率
* @param velocityY 垂直方向的速率
* @param consumed targetView是否消耗过Fling了
* @return true if this parent consumed or otherwise reacted to the fling
*/
boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);
/**
* 当前ViewGroup优先消费嵌套Fling
*
* @param target 当前ViewGroup的中初始化嵌套Fling的子孙
* @param velocityX 水平方向的速率
* @param velocityY 垂直方向的速率
* @return true 当前是否消费了嵌套fling
*/
boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);
/**
* 返回嵌套滚动的方向
*
*/
@ScrollAxis
int getNestedScrollAxes();
}
Timing diagram
Simple Case
/**
* ChildView
*
* @author zfc
* @date 2020-01-07
*/
public class ChildView extends LinearLayout implements NestedScrollingChild {
//速度跟踪器
private VelocityTracker mVelocityTracker;
//动画
private Animation mAnimation;
public ChildView(Context context) {
super(context);
init();
}
public ChildView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ChildView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
mVelocityTracker = VelocityTracker.obtain();
//使能嵌套滚动
setNestedScrollingEnabled(true);
}
int left;
int top;
int right;
int bottom;
int startX;
int startY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//
startNestedScroll(SCROLL_AXIS_VERTICAL);
left = getLeft();
top = getTop();
right = getRight();
bottom = getBottom();
startX = Math.round(event.getRawX());
startY = Math.round(event.getRawY());
break;
case MotionEvent.ACTION_MOVE:
int moveX = Math.round(event.getRawX());
int moveY = Math.round(event.getRawY());
int dx = 0;
int dy = moveY-startY;
int[] consumed = new int[2];
int[] offsetInWindow = new int[2];
//判断父亲是否消耗了该滚动距离
if(dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow)){
//暂时只讨论Y方向
int parentConsumedY = consumed[1];
left = left+dx;
top = top +parentConsumedY;
right = right+dx;
bottom = bottom+parentConsumedY;
//平滑滚动
scrollBy(0,(int)dy);
}
startX = moveX;
startY = moveY;
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000);
float vx = mVelocityTracker.getXVelocity();
float vy = mVelocityTracker.getYVelocity();
moveX = Math.round(event.getRawX());
moveY = Math.round(event.getRawY());
fling(moveX,moveY,vx,vy);
break;
default:
break;
}
return true;
}
private void fling(int moveX, int moveY, float vx, final float vy) {
if (mAnimation == null) {
mAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float speed = mVelocityTracker.getXVelocity();
//interpolatedTime是当前的时间插值, 从0-1减速变化
//所以(1 - interpolatedTime)就是从1-0减速变化,
//而(1 - interpolatedTime) * speed就是将当前速度乘以插值,速度也会跟着从speed-0减速变化,
//将(1 - interpolatedTime) * speed)用于重绘,就可以实现平滑的滚动
scrollBy(0,(int)((1 - interpolatedTime) * vy));
Log.d("sliding", "cur speed = " + String.valueOf((1 - interpolatedTime) * speed));
}
};
mAnimation.setInterpolator(new DecelerateInterpolator());//设置一个减速插值器
}
stopScroll();
mAnimation.setDuration(2000);
startAnimation(mAnimation);
}
/**
* 停止滑动
*/
private void stopScroll() {
if (mAnimation != null && !mAnimation.hasEnded()) {
mAnimation.cancel();
clearAnimation();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mVelocityTracker != null) {//要记得回收
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
}
/**
* ParentView
*
* @author zfc
* @date 2020-01-07
*/
public class ParentView extends LinearLayout implements NestedScrollingParent {
public ParentView(Context context) {
super(context);
}
public ParentView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ParentView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ParentView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
//开启嵌套滚动
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return true;
}
//做一些嵌套前的滚动配置
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
super.onNestedScrollAccepted(child, target, axes);
}
//是否消耗滚动距离
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
consumed[0] = 0;
consumed[1] = dy/2;
scrollBy(0,dy/2);
}
}