美文网首页
三、Behavior中,仿一个Scrolling Activit

三、Behavior中,仿一个Scrolling Activit

作者: ec1d4f49eddd | 来源:发表于2017-08-24 13:43 被阅读0次

这篇文章,我将仿写第一篇文章中提到的ScrollingActivity,并且说明Behavior的其他回调方法。

1)使用Behavior布局

第一篇文章中,我提到了CoordinatorLayout的三种布局方式,当时留了一个坑,现在来说明如何使用Behavior布局,即如何使ScrollingActivity的NestedScrollView放置在Header之下。
在Behavior的代码中,我们会看到两个非常熟悉的回调

/**
         * Called when the parent CoordinatorLayout is about to measure the given child view.
         *
         * <p>This method can be used to perform custom or modified measurement of a child view
         * in place of the default child measurement behavior. The Behavior's implementation
         * can delegate to the standard CoordinatorLayout measurement behavior by calling
         * {@link CoordinatorLayout#onMeasureChild(android.view.View, int, int, int, int)
         * parent.onMeasureChild}.</p>
         *
         * @param parent the parent CoordinatorLayout
         * @param child the child to measure
         * @param parentWidthMeasureSpec the width requirements for this view
         * @param widthUsed extra space that has been used up by the parent
         *        horizontally (possibly by other children of the parent)
         * @param parentHeightMeasureSpec the height requirements for this view
         * @param heightUsed extra space that has been used up by the parent
         *        vertically (possibly by other children of the parent)
         * @return true if the Behavior measured the child view, false if the CoordinatorLayout
         *         should perform its default measurement
         */
        public boolean onMeasureChild(CoordinatorLayout parent, V child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            return false;
        }

还有

/**
         * Called when the parent CoordinatorLayout is about the lay out the given child view.
         *
         * <p>This method can be used to perform custom or modified layout of a child view
         * in place of the default child layout behavior. The Behavior's implementation can
         * delegate to the standard CoordinatorLayout measurement behavior by calling
         * {@link CoordinatorLayout#onLayoutChild(android.view.View, int)
         * parent.onLayoutChild}.</p>
         *
         * <p>If a Behavior implements
         * {@link #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)}
         * to change the position of a view in response to a dependent view changing, it
         * should also implement <code>onLayoutChild</code> in such a way that respects those
         * dependent views. <code>onLayoutChild</code> will always be called for a dependent view
         * <em>after</em> its dependency has been laid out.</p>
         *
         * @param parent the parent CoordinatorLayout
         * @param child child view to lay out
         * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
         *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
         *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
         * @return true if the Behavior performed layout of the child view, false to request
         *         default layout behavior
         */
        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
            return false;
        }

这两个方法会在渲染被动View时被依次调用,那么要使ScrollingView的NestedScrollView置于Header之下,只需覆写onLayoutChild方法即可。

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, TextView child, int layoutDirection) {
        NestedScrollView dependency = (NestedScrollView) parent.getDependencies(child).get(0);
        dependency.layout(0, child.getMeasuredHeight(), parent.getWidth(), child.getMeasuredHeight() + dependency.getMeasuredHeight());
        return false;
    }

这里,让child置于dependency之下,并且让child的大小为一个屏幕。

我们可以使用CoordinatorLayout #getDependencies来获得child对应的被动View列表
,同样也可以使用CoordinatorLayout #getDependents来获得child对应的主动View列表,由于在这里NestedScrollView只有一个被动View,所以简单粗暴的get(0)即可。

对child进行布局,需要调用child#layout方法对他进行布局。

注意,如果在onLayoutChild方法中我们对child做了布局时,则这个函数应该返回true,否则,当默认使用CoordinatorLayout对其进行布局时,应该返回false。一旦该函数返回true,就必须调用child.layout方法为child进行布局,否则child将不会显示。这个方法只针对child,其他View则不受函数返回值的影响

2)Behavior的NestedScrolling 回调


上图中的七个回调,会在发生NestedScrolling的不同时机分别被调用,这点看名字就可以了。

1. onStartNestedScroll
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
                V child, View directTargetChild, View target, int nestedScrollAxes)

为了使其他的回调生效必须覆写onStartNestedScroll,并在合适情景的时候返回true。

当CoordinatorLayout下有多个Behavior时,每一个onStartNestedScroll方法返回true的Behavior都能接收到NestedScrolling事件。

onStartNestedScroll方法有一个新的参数directTargetChild,它表示产生这次NestedScroll事件的View,*能产生NestedScroll事件的View有NestedScrollView、RecyclerView、以及实现了NestedScrollingChild接口的View。(NestedScrollView、RecyclerView都实现了这个接口)

2. onNestedPreScroll

现在,让onStartNestedScroll直接返回true

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

这样代码就能走到onNestedPreScroll这个回调了(默认onStartNestedScroll返回false,则onNestedPreScroll不会被调用)。回想ScrollingActivity的Demo,在Toolbar缩小到最小前,NestedScrollView整体向上平移,但NestedScrollView的内容没有发生滑动;在Toolbar缩小到最小时,NestedScrollView不发生平移,且NestedScrollView的内容开始滚动。这个逻辑就是在onNestedPreScroll这个回调中做的。

        /**
         * Called when a nested scroll in progress is about to update, before the target has
         * consumed any of the scrolled distance.
         *
         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
         * that returned true will receive subsequent nested scroll events for that nested scroll.
         * </p>
         *
         * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated
         * by the nested scrolling child, before the nested scrolling child has consumed the scroll
         * distance itself. <em>Each Behavior responding to the nested scroll will receive the
         * same values.</em> The CoordinatorLayout will report as consumed the maximum number
         * of pixels in either direction that any Behavior responding to the nested scroll reported
         * as consumed.</p>
         *
         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
         *                          associated with
         * @param child the child view of the CoordinatorLayout this Behavior is associated with
         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
         * @param dx the raw horizontal number of pixels that the user attempted to scroll
         * @param dy the raw vertical number of pixels that the user attempted to scroll
         * @param consumed out parameter. consumed[0] should be set to the distance of dx that
         *                 was consumed, consumed[1] should be set to the distance of dy that
         *                 was consumed
         *
         * @see NestedScrollingParent#onNestedPreScroll(View, int, int, int[])
         */
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
                int dx, int dy, int[] consumed) {
            // Do nothing
        }

对于ScrollingActivity,当NestedScrollView开始滑动时,就产生了NestedScrolling事件(即一个dy,也可以理解为一组dy)。在嵌套滑动的体系下,NestedScrollView在开始让自身滑动dy前,如果其他的View需要从中消耗dy',那么NestedScrollView最终只能让自身的内容滑动dy - dy'

参数中,dy和dx表示的是NestedScrollingChild所产生的原始事件的距离 。target是产生NestedScrolling事件的View,这个例子中指的是NestedScrollView。consumed是一个二维数组,consumed[0]表示child在x方向要消耗的距离,consumed[1]则表示y方向。如果需要消耗一段距离则赋值 consumed[1] = d; 即可。

在我们的ScrollingActivity中的onNestedPreScroll方法,直接让NestedScrollView向上平移

   @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, TextView child, View target, int dx, int dy, int[] consumed) {
        NestedScrollView dependency = (NestedScrollView) parent.getDependencies(child).get(0);
        ViewCompat.offsetTopAndBottom(dependency , -dy);
    }

效果如下:


可以看到,在不设置consumed时,不仅NestedScrollView向上平移了,并且它的内容也发生了滚动。

现在让这个Behavior消耗掉所有的dy:

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, NestedScrollView child, View target, int dx, int dy, int[] consumed) {
        NestedScrollView dependency = (NestedScrollView) parent.getDependencies(child).get(0);
        ViewCompat.offsetTopAndBottom(dependency , -dy);
        consumed[1] = dy;
    }

运行的效果符合预期:


继续完善逻辑:

   @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, TextView child, View target, int dx, int dy, int[] consumed) {

        int topViewMinHeight = DisplayUtilsLite.dp2px(mContext, StaticConfig.TOP_VIEW_MIN_HEIGHT_DP);
        int topViewMaxHeight = child.getMeasuredHeight();

        boolean notOnTop;//NestedScrollView没滑动到顶部时
        boolean overScrollDownAtTop;//NestedScrollView在顶部,向下滑到底,继续向下滑
        boolean overScrollUpAtBottom;//NestedScrollView在底部,向上滑到顶,继续向上滑

        NestedScrollView dependency = (NestedScrollView) coordinatorLayout.getDependencies(child).get(0);

        notOnTop = (dependency.getTop() > topViewMinHeight);
        overScrollDownAtTop = (dependency.getTop() == topViewMinHeight) && (dy < 0) && (dependency.getScrollY() == 0);
        overScrollUpAtBottom = (dependency.getTop() == topViewMaxHeight) && (dy > 0) && (dependency.getScrollY() == 0);

        if (notOnTop || overScrollDownAtTop || overScrollUpAtBottom){//平移NestedScrollView
            if ((dependency.getTop() - dy) < topViewMinHeight){
                ViewCompat.offsetTopAndBottom(dependency, -(dependency.getTop() - topViewMinHeight));
            }else if((dependency.getTop() - dy) > topViewMaxHeight){
                ViewCompat.offsetTopAndBottom(dependency, (topViewMaxHeight - dependency.getTop()));
            }else{
                ViewCompat.offsetTopAndBottom(dependency, -dy);
            }
            consumed[1] = dy;
        }
    }

现在的效果是这样的:


3)onDependentViewChanged,让TextView跟随滑动发生改变

@Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
        child.setTranslationY(dependency.getTop() - topViewMaxHeight);
        return super.onDependentViewChanged(parent, child, dependency);
 }

这里就简单的让TextView平移了一下,最终的效果就是这样的了

4)onTouchEvent

在ScrollingActivity Demo中,滑动上面的AppBar和滑动下面的NestedScrollView效果是一样的。这部分的逻辑就是在onTouchEvent里面做的。这个回调就很熟悉了,需要注意的一点是,只有child View上产生的事件才会进入这个方法。
简单写一下相关的代码:

    float y;

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, TextView child, MotionEvent ev) {

        NestedScrollView dependency = (NestedScrollView) parent.getDependencies(child).get(0);

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                y = ev.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                ViewCompat.offsetTopAndBottom(dependency, (int) (ev.getRawY() - y));
                y = ev.getRawY();
                break;
        }
        return true;
    }

大概就是这样了,还有一些逻辑就和自定义View时没有任何区别,就没有必要再赘述了。

5)

至此ScrollingActivity就仿完了,倒是不难,就是有点繁琐。

相关文章

网友评论

      本文标题:三、Behavior中,仿一个Scrolling Activit

      本文链接:https://www.haomeiwen.com/subject/gcchdxtx.html