NestedScrolling(嵌套滚动)
关于自定义Behavior,其中还有一种是实现NestedScrolling效果的,这种效果很常见,比如像支付宝的首页,有道词典的首页等,有了这种嵌套滚动的机制可以实现很多复杂的界面效果。CoordinatorLayout
就使用了这套机制,一般我们使用它直接做父View,因为它实现了NestedScrollingParent
接口,那么下面我们说一说NestedScrolling。
要想了解NestedScrolling
我们就得说一说NestedScrollingParent
与NestedScrollingChild
NestedScrollingParent
public interface NestedScrollingParent {
/**
*响应一个子View视图,是否执行可嵌套的滚动操作。
*当子View视图调用了startNestedScroll(),Parent会收到onStartNestedScroll()回调,
*决定是否需要配合Child来一起进行处理滑动,如果需要配合,还会回调onNestedScrollAccepted()。
*当嵌套的滚动完成后,这个ViewParent将会回调onStopNestedScroll(View)。
* @param 这个ViewParent所包含的直接子对象
* @param 嵌套滚动的View
* @param 嵌套滚动的方向SCROLL_AXIS_HORIZONTAL,SCROLL_AXIS_VERTICAL或者两者
* @return 如果此ViewParent接受嵌套滚动操作,则返回true
*/
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
/**
*此方法将在onStartNestedScroll返回true之后调用
*这个方法中可以通知需要嵌套滚动的子View
* @param 这个ViewParent包含的直接子对象
* @param 嵌套滚动的View
* @param 嵌套滚动的方向SCROLL_AXIS_HORIZONTAL,SCROLL_AXIS_VERTICAL或者两者
*
*/
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
/**
*
*在嵌套滚动操作后执行资源回收等操作
*当一个嵌套的滚动停止时,这个方法将被调用
* @param 嵌套滚动的View
*/
public void onStopNestedScroll(View target);
/**
* 当ViewParent正在进行嵌套滚动的时候,调用此方法
* 滑动的距离消耗部分和未消耗部分都会通知给ViewParent
* @param target 嵌套滚动的子View
* @param dxConsumed 表示target已经消费的x方向的距离
* @param dyConsumed 表示target已经消费的x方向的距离
* @param dxUnconsumed 表示x方向剩下的滑动距离
* @param dyUnconsumed 表示y方向剩下的滑动距离
*/
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
/**
* 如果父视图会在嵌套滚动子View之前先滚动一段距离,那就用这个方法,也
* 就是发生嵌套滚动之前回调
* @param 初始化嵌套滚动的目标视图
* @param 本次滚动产生的x方向的滚动总距离
* @param dy 本次滚动产生的y方向的滚动总距离
* @param 表示viewParent要消费的滚动距离,consumed[0]和consumed[1]分别表示viewParent在x和y方向上消费的距离.
*/
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
/**
*如果一个嵌套滚动子视图fling,但这是它位于它自己的内容的边缘,
*那么它就可以使用这个方法将它委托给它嵌套的滚动的ViewParent。
* ViewParent可以消费或者观察子View的fling动作
* @param target View that initiated the nested scroll
* @param 水平方向的速度
* @param 垂直方向速度
* @param 如果子View消费了fling操作,返回true,反之亦然
* @return 如果ViewParent消费了fling或者以其他方式响应fling都返回true
*/
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
/**
*
* 嵌套滚动之前,响应fling
* @param target View that initiated the nested scroll
* @param 水平方向的速度
* @param 垂直方向速度
* @return 如果viewParent在target之前消费了fling返回true
*/
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
/**
*
*
* @return 返回嵌套滚动的方向(标识)
* @see ViewCompat#SCROLL_AXIS_HORIZONTAL
* @see ViewCompat#SCROLL_AXIS_VERTICAL
* @see ViewCompat#SCROLL_AXIS_NONE
*/
public int getNestedScrollAxes();
}
NestedScrollingChild
public interface NestedScrollingChild {
/**
* 启用或禁用此视图的嵌套滚动
* 如果此属性设置为true,则视图将会启动兼容ViewParent嵌套滚动的操作
*
* @param true为启用嵌套滚动,false为禁用
*/
public void setNestedScrollingEnabled(boolean enabled);
/**
* 判断嵌套滑动是否可用
*
* @return 如果该视图启用嵌套滚动,则为true
*/
public boolean isNestedScrollingEnabled();
/**
* 沿着设定好的坐标轴方向开始嵌套滚动
* 该视图将在启动滚动操作时调用startNestedScroll,
* 如果startNestedScroll返回true,那么就会寻找ViewParent
* @param axes 表示方向轴,有横向和竖向
*/
public boolean startNestedScroll(int axes);
/**
* 停止正在进行的嵌套滚动
* 当嵌套滚动不在进行中时调用此方法是没有什么卵用的。
*/
public void stopNestedScroll();
/**
* 如果此视图具有嵌套的滚动的ViewParent,则返回true。
* @return whether this view has a nested scrolling parent
*/
public boolean hasNestedScrollingParent();
/**
* 在子View的onInterceptTouchEvent或者onTouch中,调用该方法通知父View滑动的距离
* dispatchNestedPreScroll可以为,想在嵌套滚动中的父视图,提供了消耗部分或全部滚动操作机会。
*(在子视图嵌套滚动消耗距离之前)
* @param dx x轴上滑动的距离
* @param dy y轴上滑动的距离
* @param consumed 父view消费掉的scroll长度
* @param offsetInWindow 子View的窗体偏移量
* @return 支持的嵌套的父View 是否处理了 滑动事件
*/
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
/**
* 支持嵌套滚动的视图的应该调用这个方法来报告当前滚动的信息到当前嵌套的滚动父对象
*
* @param dxConsumed x轴上被消费的距离(横向)
* @param dyConsumed y轴上被消费的距离(竖向)
* @param dxUnconsumed x轴上未被消费的距离
* @param dyUnconsumed y轴上未被消费的距离
* @param offsetInWindow 子View的窗体偏移量
* @return 如果事件被发送,则为true,如果没能发送,则为false。
*/
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
/**
* 滑行时调用
*
* @param velocityX x 轴上的滑动速率
* @param velocityY y 轴上的滑动速率
* @param consumed 如果孩子消费了这个Fling,则为true,否则为false
* @return 如果嵌套的滚动父级消耗或以以其他方式对Fling做出反应,则返回true
*/
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
/**
* 在这个视图处理之前,将一个fling分发给一个嵌套滚动的父级。
*
* @param velocityX x 轴上的滑动速率
* @param velocityY y 轴上的滑动速率
* @return 如果嵌套的滚动父级消费了该Fling,则为true
*/
public boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
这么多的方法和参数,看这真是晕~但实际上,我们不会直接使用这两个接口的。而是使用 NestedScrollingChildHelper或NestedScrollingParentHelper这个辅助类,Android已经实现好了 Child 和 Parent 交互的逻辑。
实现 NestedScrollingChild
如果你有一个滑动的View(自定义或原生控件)需要被用来作为嵌入滑动的子 View,就必须实现本接口。例如:ListView
大体的流程如下:
1.子View需要调用setNestedScrollingEnabled启动嵌套滑动,然后调用startNestedScroll方法,让他去寻找viewParent,
通知ViewParent.
2.然后在子ViewonInterceptTouchEvent或者onTouch中,调用dispatchNestedPreScroll来通知ViewParent需不需要滑动,如果ViewParent要滑动,那么根据方法的3,4参数,返回父view消费掉的scroll长度和子View的窗体偏移量,如果ViewParent没有把所有的距离消耗完,那么子View就可以处理 剩下的距离了。
3.子View调用dispatchNestedScroll方法向ViewParent通知滚动情况,包括子view消费的部分和子view没有消费的部分。
如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。
4.调用stopNestedScroll
CoordinatorLayout就实现了NestedScrollingParent
那么说到嵌套滚动,Behavior在这里面是干啥的呢?下面看看在源码里面做了什么
//滑动开始的调用startNestedScroll(),ViewParent收到onStartNestedScroll() 回调,
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == View.GONE) {
// If it's GONE, don't dispatch
continue;
//决定是否需要配合 Child 一起进行处理滑动,如果需要配合,还会回调 onNestedScrollAccepted()。
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
mNestedScrollingDirectChild = child;
mNestedScrollingTarget = target;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
}
}
}
@Override
public void onStopNestedScroll(View target) {
mNestedScrollingParentHelper.onStopNestedScroll(target);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
viewBehavior.onStopNestedScroll(this, view, target);
}
lp.resetNestedScroll();
lp.resetChangedAfterNestedScroll();
}
mNestedScrollingDirectChild = null;
mNestedScrollingTarget = null;
}
//Child 滑动以后,会调用 onNestedScroll(),回调到 ViewParent 的 onNestedScroll(),
//这里就是 Child 滑动后,剩下的给 ViewParent 处理,也就是后于 Child 滑动。
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
final int childCount = getChildCount();
boolean accepted = false;
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
accepted = true;
}
}
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
//ViewParent 可以在这个回调中消费滚动的距离
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
int xConsumed = 0;
int yConsumed = 0;
boolean accepted = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
mTempIntPair[0] = mTempIntPair[1] = 0;
viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
: Math.min(xConsumed, mTempIntPair[0]);
yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
: Math.min(yConsumed, mTempIntPair[1]);
accepted = true;
}
}
consumed[0] = xConsumed;
consumed[1] = yConsumed;
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
consumed);
}
}
if (handled) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
return handled;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
}
}
return handled;
}
@Override
public int getNestedScrollAxes() {
return mNestedScrollingParentHelper.getNestedScrollAxes();
}
当CoordiantorLayout接收到了NestedScrollingChild的回调后,把事件交给了Behavior来处理的,
用户来继承CoordiantorLayout.Behavior的时候,这个Behavior其实是个NestedScrollingParent ,
而且还是个加强版的。
以前练习的demo找不到了,等下次重新写一个吧。
隔壁HMI的人,天天在吵吵,头痛( ⊙ o ⊙ )啊!如果这些HMI团队的人是那种妹子团的,我也就忍了。可是这种老男人+大姐的这种团队,就搞的你是真不爽了。
上一篇: 界面小知识
推荐阅读
-
NestedScrolling(嵌套滚动)
-
Quartz定时任务嵌套,创建不同的Scheduler实例问题
-
避免session失效导致页面框架嵌套展示登陆界面
-
避免session失效导致页面框架嵌套展示登陆界面
-
Mysql_嵌套表查询_查询结果作为子表(临时表) 博客分类: DB_Mysql_Oracle_Informix_SqlServer MySQLOracle
-
用 ES6 写全屏滚动插件
-
如何将一个HTML页面嵌套在多个动态页面(asp) 博客分类: Javascript asp
-
ASP嵌套VB代码读取ini配置文件 博客分类: VB ASP .NET VBASPini
-
【2019-2020春学期】数据库作业8:SQL练习5 - SELECT(嵌套查询EXISTS、集合查询、基于派生表的查询)
-
Android 自定义LinearLayout和Behavior实现嵌套滑动