美文网首页
Behavior 研究

Behavior 研究

作者: 最美下雨天 | 来源:发表于2019-12-19 11:08 被阅读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)));
    
        }
    

    相关文章

      网友评论

          本文标题:Behavior 研究

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