Coordinatorlayout嵌套滑动,自定义Behavior,听我来讲讲?
文章目录
- 前言
- 分析
- 1、父容器子容器
- 2、如何形成关联,谁是发起者
- 3、NestedScrollingParent和NestedScrollingChild对应
- 4、响应者
- 示例
前言
嵌套滑动,顾名思义,嵌套嵌套就一定有父容器和子容器。如何能让子容器滑动能带动父容器(或父容器包含的其他子容器)滑动?什么样的子容器有这种能力?这种关联如何形成,以及被关联的容器如何响应,这种响应的逻辑在哪里定义?
相信你在对本文的阅读之后会有一定的了解
提示:以下是本篇文章正文内容
分析
1、父容器子容器
了解安卓开发的同学对这个概念再熟悉不过了,父容器是容器布局,子容器(控件)则是被这个父容器包含的容器布局(控件)。如:
<FrameLayout><RelativeLayout>...</RelativeLayout><View/> </FrameLayout>这里的FrameLayout就是父容器,这里的RelativeLayout和View就是子容器(控件)。
2、如何形成关联,谁是发起者
父容器实现NestedScrollingParent接口,
子容器实现NestedScrollingChild接口。
查看代码示例
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout><android.support.design.widget.AppBarLayout/><FrameLayoutapp:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior"><android.support.v7.widget.RecyclerView/><!--悬浮条--><RelativeLayout>...</RelativeLayout></FrameLayoutapp:layout_behavior=".ScaleBehavior"><android.support.design.widget.FloatingActionButton/> </android.support.design.widget.CoordinatorLayout>示例中的CoordinatorLayout默认实现了NestedScrollingParent接口,而RecyclerView控件默认实现了NestedScrollingChild接口。
所以在RecyclerView滑动的时候,CoordinatorLayout一直能收到相应的回调。比如说这时候这个控件不是RecyclerView而是ListView的话,那这个回调自然是没有的,理由就是ListView并没有默认实现NestedScrollingChild接口。
实现了NestedScrollingChild接口的控件,其实也就是整个事件的发起者,而父容器便是接受的一方。
3、NestedScrollingParent和NestedScrollingChild对应
这两个接口的回调方法api,如下:
SCROLL_STATE_IDLE 0, 最后是RecyclerView滚动停止状态。 SCROLL_STATE_DRAGGING 1, 先是手指拖拽的状态 SCROLL_STATE_SETTLING 2,再是手指松开但是RecyclerView还在滑动/** *父容器实现的接口 */ public interface NestedScrollingParent {/*** 开始滑动回调* @param child 该父View 的子View* @param target 支持嵌套滑动的 VIew* @param nestedScrollAxes 滑动方向* @return 是否支持 嵌套滑动*/boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes);void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int nestedScrollAxes);void onStopNestedScroll(@NonNull View target);/*** 这里 传来了 x y 方向上的滑动距离* 并且 先与 子VIew 处理滑动, 并且 consumed 中可以设置相应的 除了的距离* 然后 子View 需要更具这感觉, 来处理自己滑动** @param target* @param dx* @param dy* @param consumed*/void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);/*** 这里 主要处理 dyUnconsumed dxUnconsumed 这两个值对应的数据* @param target* @param dxConsumed* @param dyConsumed* @param dxUnconsumed* @param dyUnconsumed*/void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);int getNestedScrollAxes(); }/** *子容器(控件)实现的接口 */ public interface NestedScrollingChild {//设置允许嵌套滑动 true表示允许void setNestedScrollingEnabled(boolean enable);boolean isNestedScrollingEnabled();//开始嵌套滑动 这里需要返回true 否在后续事件不会再触发boolean startNestedScroll(int axes);//坐标轴//结束嵌套滑动void stopNestedScroll();//判断NestedParent的onStartNestedScroll是否返回true 只有为true后续的事件才能继续一系列的嵌套滑动boolean hasNestedScrollingParent();//子view消费了拖动事件之前通知父view,dx dy是将要消费的距离,如果父view要消费可通过//设置consumed[0]=x .consumed[1]=y来分别消费x,y。然后子view继续处理剩下的位移(即dx-x,dy-y)boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow);//子View消费滑动事件后通知父Viewboolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);//子view消费了滑动事件之前通知父viewboolean dispatchNestedPreFling(float var1, float var2);//子view消费了滑动事件之后通知父viewboolean dispatchNestedFling(float var1, float var2, boolean var3); }父接口的回调和子接口的回调,两者的方法有明显的对应关系。
这样实现了子接口就可以在需要的时候调用接口的方法,如stopNestedScroll,这样对应在父接口onStopNestedScroll也就会被回调。
4、响应者
上文说了父容器是接受的一方,但它并不是真正意义上的响应者,响应者是谁取决于Behavior的定义。
本例中的父容器是CoordinatorLayout,我们就可以自定义一个Behavior继承CoordinatorLayout.Behavior,然后在对应的父容器回调方法中加入自己想要的逻辑。
值得一提的是,你即可以指定设置了Behavior的控件本身响应,也可以指定该父容器下的其他子容器(控件)响应,无论这个Behavior设置给哪个子容器(控件)。
示例如下:
public class ScaleBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {@Overridepublic boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {//只有返回true 后续的动作才会触发return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//垂直滚动}@Overridepublic void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);...} }当然Android本身也有很多定义好的Behavior可以直接使用,这里就不赘述了。
最后将这个Behavior设置到布局中,就可以正常使用了。
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout><android.support.design.widget.AppBarLayout/><FrameLayoutapp:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior"><android.support.v7.widget.RecyclerView/><!--悬浮条--><RelativeLayout>...</RelativeLayout></FrameLayoutapp:layout_behavior=".ScaleBehavior"><android.support.design.widget.FloatingActionButton/> </android.support.design.widget.CoordinatorLayout>示例
本例主要
- 使用RecyclerView做示范。(将项目启动页改为MainActivity查看)
- 自定义了一个实现了NestedScrollingChild接口的ListView做示范。
- 自定义Behavior示范。
效果如下
总布局代码如下
NestedListView代码如下
public class NestedListView extends ListView implements NestedScrollingChild {//1初始化获取ChildHelperprivate NestedScrollingChildHelper mChildHelper;private int mLastY;private final int[] mScrollOffset = new int[2];//滑动偏移private final int[] mScrollConsumed = new int[2];//滑动消费private int mNestedOffsetY;//嵌套偏移public NestedListView(Context context) {super(context);init();}public NestedListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public NestedListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mChildHelper = new NestedScrollingChildHelper(this);setNestedScrollingEnabled(true);}@Overridepublic void setNestedScrollingEnabled(boolean enabled) {mChildHelper.setNestedScrollingEnabled(enabled);}@Overridepublic boolean isNestedScrollingEnabled() {return mChildHelper.isNestedScrollingEnabled();}@Overridepublic boolean startNestedScroll(int axes) {return mChildHelper.startNestedScroll(axes);}@Overridepublic void stopNestedScroll() {mChildHelper.stopNestedScroll();}@Overridepublic boolean hasNestedScrollingParent() {return mChildHelper.hasNestedScrollingParent();}@Overridepublic boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);}@Overridepublic boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);}@Overridepublic boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);}@Overridepublic boolean dispatchNestedPreFling(float velocityX, float velocityY) {return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);}@Overridepublic boolean onTouchEvent(MotionEvent ev) {int action = ev.getAction();int y = (int) ev.getY();ev.offsetLocation(0, mNestedOffsetY);switch (action) {case MotionEvent.ACTION_DOWN:mLastY = y;mNestedOffsetY = 0;this.startNestedScroll((ViewCompat.SCROLL_AXIS_VERTICAL));//开始嵌套滑动break;case MotionEvent.ACTION_MOVE:int dy = mLastY - y;//Y的拖动距离int oldY = getScrollY();//注意一般一直为0//在自己消费前先分发给父容器if (dispatchNestedPreScroll(0, dy, mScrollConsumed, mScrollOffset)) {dy -= mScrollConsumed[1];//剩余ev.offsetLocation(0, -mScrollOffset[1]);mNestedOffsetY += mScrollOffset[1];}mLastY = y - mScrollOffset[1];int newScrollY = oldY + dy;dy -= newScrollY - oldY;//全部消费完//自己消费if (dispatchNestedScroll(0, newScrollY - dy, 0, dy, mScrollOffset)) {ev.offsetLocation(0, mScrollOffset[1]);mNestedOffsetY += mScrollOffset[1];mLastY -= mScrollOffset[1];}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:stopNestedScroll();break;}return super.onTouchEvent(ev);} }ScaleBehavior代码如下
public class ScaleBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {private Interpolator interpolator;private boolean isRunning;public ScaleBehavior(Context context, AttributeSet attrs) {super(context, attrs);interpolator = new AccelerateDecelerateInterpolator();}@Overridepublic boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {//只有返回true 后续的动作才会触发return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//垂直滚动}@Overridepublic void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);Log.e("test", dyConsumed + " " + dyUnconsumed);if (dyConsumed > 0 && !isRunning && child.getVisibility() == View.VISIBLE) {//上滑 缩小隐藏 动画scaleHide(child);} else if (dyConsumed < 0 && !isRunning && child.getVisibility() == View.INVISIBLE) {//下滑 放大显示scaleShow(child);}}private void scaleShow(final V child) {child.setVisibility(View.VISIBLE);ViewCompat.animate(child).alpha(1).scaleX(1).scaleY(1).setInterpolator(interpolator).setListener(new ViewPropertyAnimatorListener() {@Overridepublic void onAnimationStart(View view) {isRunning = true;}@Overridepublic void onAnimationEnd(View view) {isRunning = false;}@Overridepublic void onAnimationCancel(View view) {isRunning = false;}}).setDuration(500).start();}private void scaleHide(final V child) {ViewCompat.animate(child).alpha(0).scaleX(0).scaleY(0).setInterpolator(interpolator).setListener(new ViewPropertyAnimatorListener() {@Overridepublic void onAnimationStart(View view) {isRunning = true;}@Overridepublic void onAnimationEnd(View view) {isRunning = false;child.setVisibility(View.INVISIBLE);}@Overridepublic void onAnimationCancel(View view) {isRunning = false;}}).setDuration(500).start();} }推荐阅读
Android 11新特性,Scoped Storage又有了新花样
About
本文Demo
UI系列文章一览
总结
以上是生活随笔为你收集整理的Coordinatorlayout嵌套滑动,自定义Behavior,听我来讲讲?的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: flask框架1基础安装
- 下一篇: [BZOJ3653][长链剖分]谈笑风生