Behavior 研究

2019-12-19  本文已影响0人  最美下雨天

研究的Demo地址:https://www.jianshu.com/p/f7989a2a3ec2
这个文章中涉及到了4个Behavior的使用。
关于文章中一点修改的地方:
1、
删除HeaderScrollingViewBehavior#onMeasureChild方法中的逻辑,测试是没有问题的,这段代码中的逻辑主要是重新测量viewpager的高度,防止上滑后底部留白的问题,但实际上是不会存在这个问题的(因为这个viewpager是CoordinatorLayout的子类,默认是与CoordinatorLayout左上角对齐的,只要设置其宽高为match_parent即可)
2、
滑动不灵敏,有时候滑不动,得试两三次
修改viewpager的Behavior中onInterceptTouchEvent方法的逻辑

@Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        //初始状态下,不让viewpager左右滑动,所以水平滑动要拦截,竖直不拦截
        //不用拦截竖直滑动事件,因为viewpager并不会消费竖直滑动事件,事件会传递到Recyclerview
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            lastX = ev.getX();
            lastY = ev.getY();
        }

        if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
            float currX = ev.getX();
            float currY = ev.getY();
            float delta=Math.abs(currX - lastX);
            boolean horizontalScroll =delta>touchslop;
            if (horizontalScroll && child.getTranslationY() == 0)
            {
                return true;
            }
        }

        return super.onInterceptTouchEvent(parent, child, ev);
    }

3、
此处容易误导,没必要再新建对象
UcNewsHeaderPagerBehavior类中

 private void start() {
            if (mOverScroller.computeScrollOffset()) {
                ViewCompat.postOnAnimation(mLayout, this);
            } else {
                onFlingFinished(mParent, mLayout);
            }
        }

效果拆分

image.png

HeaderPager向上滑动(NewsTitle伴随着HeaderPager的滑动向下滑动,NewsTabs伴随着HeaderPager的滑动向上滑动,NewsContent伴随着HeaderPager的滑动向上滑动)
需要解决的问题:
1、
初始状态隐藏NewsTitle和NewsTabs(这个其实不是隐藏了,只是看不见了,被NewsContent给遮住了),NewsContent固定在HeaderPager下面
隐藏NewsTitle:设置NewsTitle的margin_Top值为height的负数即可。
隐藏NewsTabs、重新设置NewsContent的位置:
Behavior中

@Override
    protected void layoutChild(final CoordinatorLayout parent, final View child, final int layoutDirection) {
        final List<View> dependencies = parent.getDependencies(child);
        final View header = findFirstDependency(dependencies);

        if (header != null) {
            final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            final Rect available = mTempRect1;
            available.set(parent.getPaddingLeft() + lp.leftMargin, header.getBottom() + lp.topMargin,
                    parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,
                    parent.getHeight() + header.getBottom() - parent.getPaddingBottom() - lp.bottomMargin);

            final Rect out = mTempRect2;
            GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), child.getMeasuredHeight(), available, out, layoutDirection);

            final int overlap = getOverlapPixelsForOffset(header);

            child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap);
            mVerticalLayoutGap = out.top - header.getBottom();
        } else {
            // If we don't have a dependency, let super handle it
            super.layoutChild(parent, child, layoutDirection);
            mVerticalLayoutGap = 0;
        }
    }

2、
初始状态下,禁止viewpager消费事件(其实就是禁止它消费水平滑动事件,竖直方向的事件它本身就不会去消费),这样的话事件会传递给起子view(在这里是Recyclerview),在NestedScroll机制中,Recyclerview会先将事件传递给起父容器(CoordinatorLayout)
第一步:重写ViewPager的Behavior的onInterceptTouchEvent事件
UcNewsContentBehavior#onInterceptTouchEvent

@Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        //初始状态下,不让viewpager左右滑动,所以水平滑动要拦截,竖直不拦截
        //不用拦截竖直滑动事件,因为viewpager并不会消费竖直滑动事件,事件会传递到Recyclerview
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            lastX = ev.getX();
            lastY = ev.getY();
        }

        if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
            float currX = ev.getX();
            float currY = ev.getY();
            float delta=Math.abs(currX - lastX);
            boolean horizontalScroll =delta>touchslop;
            if (horizontalScroll && child.getTranslationY() == 0)
            {
                return true;
            }
        }

        return super.onInterceptTouchEvent(parent, child, ev);
    }

事件拦截后,接下来该关注HeaderPager的Behavior了,事件由Recyclerview传递给CoordinatorLayout后,CoordinatorLayout会遍历所有直接值view,存在Behavior的就会调用Behavior中的方法,在这个例子中是由HeaderPager的Behavior来消费事件的:
我们按照回调方法的先后顺序来介绍
UcNewsHeaderPagerBehavior#onStartNestedScroll
表示要消费竖直方向的事件

@Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "onStartNestedScroll: ");
        }
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

UcNewsHeaderPagerBehavior#onNestedPreScroll

@Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        //dy>0 scroll up;dy<0,scroll down
        //we don't consume at the first time when header is not closed and scroll down,maybe NestedScrollingChild need it now,
        // we consume the rest dy in onNestedScroll method
        Log.e("abc","getTranlationY###"+child.getTranslationY());
        if (child.getTranslationY() >= getHeaderOffsetRange() && dy < 0)
            return;

      //滑动太灵敏了,这里只将滑动距离的四分之一作用到child上
        float halfOfDis = dy / 4.0f;
        if (!canScroll(child, halfOfDis)) {
            child.setTranslationY(halfOfDis > 0 ? getHeaderOffsetRange() : 0);
        } else {
            child.setTranslationY(child.getTranslationY() - halfOfDis);
      //表示事件全部都消费掉了,不给Recyclerview了
            consumed[1] = dy;
        }

    }

UcNewsHeaderPagerBehavior#onNestedScroll

@Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        //(如果不重写这个方法,滑动到顶部(tab置顶),再往下滑动时无法滑动)
        float halfOfDis = dyUnconsumed / 4.0f;
        if (!canScroll(child, halfOfDis)) {
            child.setTranslationY(halfOfDis > 0 ? getHeaderOffsetRange() : 0);
        } else {
            child.setTranslationY(child.getTranslationY() - halfOfDis);
        }
    }

UcNewsHeaderPagerBehavior#onNestedPreFling

@Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
        // consumed the flinging behavior until Closed
        //当tab置顶的时候不要拦截fling事件了,要不然Recyclerview的fling事件都不起作用了,
        //但是没有置顶的时候是需要拦截的,否则tab还没有置顶,Recyclerview就会惯性滑动
        return !isClosed(child);
    }

UcNewsHeaderPagerBehavior#onStopNestedScroll

/**
     * settle here
     */
    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        settle(coordinatorLayout, child);
    }
private void settle(CoordinatorLayout parent, final View child) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "settle: ");
        }
        if (mFlingRunnable != null) {
            child.removeCallbacks(mFlingRunnable);
            mFlingRunnable = null;
        }
        mFlingRunnable = new FlingRunnable(parent, child);
        if (child.getTranslationY() < getHeaderOffsetRange() / 3.0f) {
            mFlingRunnable.scrollToClosed(DURATION_SHORT);
        } else {
            mFlingRunnable.scrollToOpen(DURATION_SHORT);
        }

    }
private class FlingRunnable implements Runnable {
        private final CoordinatorLayout mParent;
        private final View mLayout;

        FlingRunnable(CoordinatorLayout parent, View layout) {
            mParent = parent;
            mLayout = layout;
        }

        public void scrollToClosed(int duration) {
            float curTranslationY = ViewCompat.getTranslationY(mLayout);
            float dy = getHeaderOffsetRange() - curTranslationY;
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "scrollToClosed:offest:" + getHeaderOffsetRange());
                Log.d(TAG, "scrollToClosed: cur0:" + curTranslationY + ",end0:" + dy);
                Log.d(TAG, "scrollToClosed: cur:" + Math.round(curTranslationY) + ",end:" + Math.round(dy));
                Log.d(TAG, "scrollToClosed: cur1:" + (int) (curTranslationY) + ",end:" + (int) dy);
            }
            mOverScroller.startScroll(0, Math.round(curTranslationY - 0.1f), 0, Math.round(dy + 0.1f), duration);
            start();
        }

        public void scrollToOpen(int duration) {
            float curTranslationY = ViewCompat.getTranslationY(mLayout);
            mOverScroller.startScroll(0, (int) curTranslationY, 0, (int) -curTranslationY, duration);
            start();
        }

        private void start() {
            if (mOverScroller.computeScrollOffset()) {
                ViewCompat.postOnAnimation(mLayout, this);
            } else {
                onFlingFinished(mParent, mLayout);
            }
        }


        @Override
        public void run() {
            if (mLayout != null && mOverScroller != null) {
                if (mOverScroller.computeScrollOffset()) {
                    if (BuildConfig.DEBUG) {
                        Log.d(TAG, "run: " + mOverScroller.getCurrY());
                    }
                    ViewCompat.setTranslationY(mLayout, mOverScroller.getCurrY());
                    ViewCompat.postOnAnimation(mLayout, this);
                } else {
                    onFlingFinished(mParent, mLayout);
                }
            }
        }
    }

HeaderPager的Behavior分析完了,其他view都是依赖HeaderPager这个View的,所以他们的Behavior也相对比较简单

分析NewsContent这个view的Behavior

核心逻辑:

首先:

@Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return isDependOn(dependency);
    }
private boolean isDependOn(View dependency) {
        return dependency != null && dependency.getId() == R.id.id_uc_news_header_pager;
    }

接着

 @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "onDependentViewChanged");
        }
        offsetChildAsNeeded(parent, child, dependency);
        return true;
    }
private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
        child.setTranslationY((int) (-dependency.getTranslationY() / (getHeaderOffsetRange() * 1.0f) * getScrollRange(dependency)));

    }
上一篇下一篇

猜你喜欢

热点阅读