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也往上滑動一段距離
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和技術交流,謝謝。