android NestedScrolling嵌套滑动实战之联合滚动fling效果

在上一篇博客中,学习了一下嵌套滑动,其实原理很简单,demo也就是存粹为了学习,没有实际意义,而且得到了第一个博客留言,说做的效果好看一点就好了。那么今天就来把效果做好看一点点,并且看到很多app的联合滚动就像是一体的一样,fling效果做的非常好,描述一下(录屏技术太差):当顶部的view在fling完全隐藏之后,顶部下面的listview接着会fling一段距离,或者listview在fling到顶部之后,顶部也会fling往下一段距离,就像惯性被托下来一样。这样的用户体验真的不错,但是我不会写啊,今天自己尝试实现以下,如果有人更好的麻烦您给我留个博客地址,万分感激。

现在先来看看我做的效果吧,(录屏技术有限,源码运行效果更好)



为了简单一点,listvew选用RecyclerView,因为他实现了NestedScrollingChild,其实如果你想用listview也是不麻烦的,因为上一篇博客已经介绍过了,我们只是在开始滑动的时候问了下有没有父view要消费而已。

在进入思考之前,再来描述一下我们想要的效果,顶部有一个view(可以被隐藏),中间有一个tablayout(始终显示),下面是一个listview,当我们手指抬起的时候有fling效果,并且顶部view和listview的fling可以共享,也就是顶部fing到完全隐藏之后,listview还可以往上fling一段距离,同理其他情况,就是为了让人感觉顶部view,tablayout和listview看起来像一个控件,它们fling的惯性效果是一体的(好难说,但是你肯定在很多app已经看到过,比如360手机助手,在app应用列表滑动的时候,但是我刚看了他的效果,每次切换fragment的时候页面都回到了顶部,好像一个新的页面一样,但是看他的一个tab下的联合滑动就是我想描述的效果,好累。。。)


如果你知道了我描述的效果,下面就来分情况讨论吧:

一、总体思路,让parent来分发fling事件,fling每次移动一点都进行分发(即拦截所有子view的fling事件,让parent来统一控制他们的fling滑动)

A.速率为正,(往下滑动fling事件)
情况1:
    top完全隐藏,不处理,直接给子view 处理fling
情况2:
    top未完全显示,这个时候fling事件肯定先给parent处理,在fling期间,如果top已经完全显示,则接下来的fling事件
    全部交给子view,(下面这段话可以先不要看,其实这个时候子view 没有必要处理,因为top如果可见,listview肯定是已经滑动到了顶部,他肯定不能向下滑动 ,当然这里描述的是只有一个tab的时候,如果多个tab,第一个tab把第top完全隐藏并且listview也往上滑动一段距离,当切到第二个tab把top完全拉出来,再回到第一个tab的时候,就出现了top遮挡listview的情况 ,所以top显示的时候往下fling,我们还是要处理)


B.速率为负,(往上滑动)
情况1:
   top可见,这个时候fling事件肯定先给parent处理,在fling期间,如果top已经完全隐藏,则接下来的fling事件
    全部交给子view
情况2:
   top不可见,子view自己处理


到此为止,情况讨论完了,然后就是代码的实现,其实根据上面的分析,我们需要储备几个知识点。

1、RecyclerView滑动到顶部的判断条件

2、scroller、VelocityTracker

3、view事件分发机制(当然了,不是说用嵌套滑动就可以不用事件分发机制了吗?因为这里我们要在parent中处理所有子view的fling,所有只需要每次在dispatchTouchEvent判断处理)

4、RecyclerView控件高度的改变,RecyclerView的正确高度应该是整个parent减去tablaout的高度

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    ViewGroup.LayoutParams params = viewPager.getLayoutParams();//viewpager中包含RecyclerView,所有这里改变viewpager的高度
    params.height = getMeasuredHeight() - barHeight;//barHeight就是中间的tablayout的高度(原谅一下命名)
}

好了有了这些逻辑和这些知识点,基本问题不大了,下面是dispatchTouchEvent中最主要的逻辑

   @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("aaa","getY():getRawY:"+event.getRawY());
        initVelocity(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastTouchY = (int) (event.getRawY() + 0.5f);
                reset();
                break;
            case MotionEvent.ACTION_MOVE:
                int y = (int) (event.getRawY() + 0.5f);
                int dy = mLastTouchY - y;
                mLastTouchY = y;
                if(showImg(dy)||hideImg(dy)){//如果父亲自己要滑动
                    scrollBy(0,dy);
                }
                break;
            case MotionEvent.ACTION_UP:
                mVelocityTracker.computeCurrentVelocity(300,maxVelocity);
               yVelocity = (int) mVelocityTracker.getYVelocity();
                Log.i(Tag,"getYVelocity:"+yVelocity+",minVelocity:"+minVelocity);
                if(Math.abs(yVelocity)>minVelocity){
//                    mScroller.fling(0,getScrollY(), 0, -yVelocity, 0, 0, 0, Math.max(0, nsc.getMeasuredHeight()+imgHeight), 0, nsc.getMeasuredHeight()/2);
                    mScroller.fling(0,getScrollY(),0,-yVelocity,0,0,-50000,5000);
                    postInvalidate();
                }
                recycleVelocity();
                break;
        }


        return super.dispatchTouchEvent(event);
    }
先说下为什么我要处理 dispatchTouchEvent中的move事件,因为我的topview没有去实现嵌套滑动的子类,而且我也不希望去拦击事件,我只想在parent自己需要滑动的时候滑动一下而已。

接下来是最重要的fling处理了:就是把上面分析的逻辑处理一下。


@Override
public void computeScroll() {
    if(mScroller.computeScrollOffset()){
        postInvalidate();
        int dy =  mScroller.getFinalY()-mScroller.getCurrY();
        if(yVelocity>0){
          if(getScrollY()>=imgHeight){//此时top完全隐藏

              if(isChildScrollToTop()){//如果子view已经滑动到顶部,这个时候父亲自己滑动
                  scrollBy(0,dy);
              }else{
                  scrollContentView(dy);
              }

          }else if(getScrollY()==0){//parent自己完全显示,交给子view滑动
              if(!isChildScrollToTop()){
                 scrollContentView(dy);
              }
          }else {//此时top没有完全显示,让parent自己滑动
              scrollBy(0,dy);
          }
        }else if(yVelocity<0){

            if(getScrollY()>=imgHeight) {//此时top完全隐藏
                scrollContentView(dy);
            }else{

                scrollBy(0,dy);
            }

        }

    }
}

好了,要讲的就是这么多了,欢迎提出bug和技术交流,谢谢。

源码下载(源码中还有scrollview的判断,是为了兼容scrollview,不知道兼容性如何,我自己还没测试,写了一天了头晕,先把这些记下来有机会再研究。。。)







你可能感兴趣的:(自定义控件)