在上一篇博客中,学习了一下嵌套滑动,其实原理很简单,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,不知道兼容性如何,我自己还没测试,写了一天了头晕,先把这些记下来有机会再研究。。。)