1. 程式人生 > >android NestedScrolling巢狀滑動實戰之聯合滾動fling效果

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和技術交流,謝謝。