[轉]Android輕鬆實現RecyclerView懸浮條

weixin_34253539發表於2019-01-04

Android輕鬆實現RecyclerView懸浮條

在我們在刷Instagram的動態時,你是否注意到這樣一個小小的動效,就是當一條動態(以卡片形式呈現)向上滑動時,動態卡片的頭部會始終懸浮在列表最上方,直到下一張動態卡片的頭部將它頂掉並替換它懸浮著。言語可能說不清楚,就直接來看一下它的效果好了。

730559-240be9957d29bd42.gif
5133552-2d540288a9636625.gif

Instagram的懸浮條

綜合我上面的文字描述加上這張Gif圖,我想大家應該知道這是個什麼樣的效果了吧。那麼不廢話了,接下來我就來說說一種很簡單的實現方法吧。

思路

雖然實現起來炒雞簡單,但還是花了我一個多小時的時間思考實現。先說說思考過程吧,那天中午,Instagram給我推了一條訊息(哈,就是我最喜歡的偶像金泰妍更新了Ins),於是我就點進去看了,喜歡了之後就開始研究這個效果,我反覆地上下滑這個列表,因為Ins的列表有滾動條,我就發現每次滾動條在那個懸浮條附近的時候就會特別短。看到這個現象,敏銳的你是不是察覺到了什麼?沒錯,我感覺這個就像是FrameLayout的效果,一個FrameLayout裡按順序有列表,懸浮條兩個View,懸浮條覆蓋在列表的上方,它在合適的時機更新自己的位置,在合適的時機更新自己的資訊,然後看上去就像是一個懸浮的效果。

接下來我們思考的核心就轉移到了如何確定並找到這個合適的時機。

再仔細觀察上面的Gif圖,我們可以確定當第二個列表項的頭部距離列表頂端一個懸浮條的距離時,懸浮條隨著列表的滑動改變自身的位置,從而看起來像是被頂掉的效果。畫一張簡單位置示意圖

              ![](http://upload-images.jianshu.io/upload_images/730559-54ac5092898a4983.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
5133552-7c3adcaed06b4b18.gif

那麼,資料更新的時機也很容易確定,就是在懸浮條恰好完全被頂掉的時候,更新自己的資料,並移動到列表頂部。

至於如何找到這個時機會在接下來的實現部分講解。

實現

建立佈局

如上面所言,就是一個簡單的FrameLayout。

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/feed_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:scrollbars="vertical" />

    <your-head-layout>
    ……
    </your-head-layout>
</FrameLayout>

注意這裡FrameLayout的第二個child應該為你列表項要懸浮顯示的佈局。

找到時機

根據我們的思路,我們首先要找到第二個列表項的頭部距離列表頂端一個懸浮條的距離時的那個時機,如果我們能找到這個時機,那麼第二個時機也相當於找出來了。

這裡我們使用的是RecyclerView來實現列表,我們都知道RecyclerView的列表佈局是由LayoutManager來確定的,由於一般要實現懸浮條顯示效果的列表一般都為線性列表,即我們一般會使用LinearLayoutManager。通過LinearLayoutManager,我們可以很方便的獲取到RecyclerView中相應位置的View,這裡我們需要獲取當前懸浮條資料來源的View和其下一個資料來源的View。這兩個View有什麼用呢?懸浮條顯示的資訊是來自第一個可見View的,而其下方的View正是第二個列表項,我們可以獲取到它的top值。好了接下來就真的很簡單了,我們只要給RecyclerView加一個ScrollListener,並在相應的回撥裡做之前我們想好的事就ok了,來看一下程式碼

mFeedList.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        mSuspensionHeight = mSuspensionBar.getHeight();
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        View view = linearLayoutManager.findViewByPosition(mCurrentPosition + 1);
        if (view != null) {
            if (view.getTop() <= mSuspensionHeight) {
                mSuspensionBar.setY(-(mSuspensionHeight - view.getTop()));
            } else {
                mSuspensionBar.setY(0);
            }
        }

        if (mCurrentPosition != linearLayoutManager.findFirstVisibleItemPosition()) {
            mCurrentPosition = linearLayoutManager.findFirstVisibleItemPosition();
            mSuspensionBar.setY(0);

            updateSuspensionBar();
        }
    }
});

Tips:其中mCurrentPosition為懸浮條資訊來自的那個列表項在RecyclerView的位置。還有這裡的ScrollListener可以新增多個,在RecyclerView中會檢查所有的ScrollListener並觸發。

One more thing...

接下來,我們還需要……開玩笑,哪來的One more thing,我們已經完成了?什麼?這麼快?這麼一點程式碼?恩,沒錯,就是隻要這麼一點程式碼就好了,我們來看一下最後我們實現的效果(當然最終效果的好壞還是取決與你列表項的佈局,比如在Ins裡這個效果就很好看呢~)

730559-b058ebf71de99352.gif
5133552-69ab1f37475ab2a8.gif

結語

哈哈,是不是很簡單呢,最後再說一下封裝的事,本來我是想封裝一下的,由於每個人的列表佈局都不一樣,資料更新方式也不一樣,就不封裝了,是的,我水平不行,雖然我不想承認~不過程式碼真心特別少哦,原始碼地址:https://github.com/wuapnjie/SuspensionBar

希望這篇文章可以對你有幫助,我也會繼續努力的。

補充

上面這種情況我們RecyclerView的Item是單一的,但是我們的列表Item通常有很多種,只有在滑到我們想要型別的Item時才需要更新我們的懸浮條資訊。比如很常見的通訊錄,在我們滑到從A開頭聯絡人滑到B開頭聯絡人時,懸浮條的資訊才從A變為B;再比如印象筆記的筆記列表,頂部的懸浮條是根據筆記的日期改變的。

那麼,遇到這種情況我們應該怎麼簡單修改程式碼來實現我們需求呢?

其實很簡單,思路已經由上面確定了,只是我們要讓懸浮條移動的時機變化,變得更窄了,同時我們要更新的資料內容也發生了變化(這當然需要我們變換相應的佈局)。

mFeedList.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        mSuspensionHeight = mSuspensionBar.getHeight();
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        //我們只是簡單的收窄了我們讓懸浮條移動的條件,這裡就是ItemType必須對應時才發生移動
          if (adapter.getItemViewType(mCurrentPosition + 1) == MultiFeedAdapter.TYPE_TIME) {
            View view = linearLayoutManager.findViewByPosition(mCurrentPosition + 1);
            if (view != null) {
                if (view.getTop() <= mSuspensionHeight) {
                    mSuspensionBar.setY(-(mSuspensionHeight - view.getTop()));
                } else {
                    mSuspensionBar.setY(0);
                }
            }
        }

        if (mCurrentPosition != linearLayoutManager.findFirstVisibleItemPosition()) {
            mCurrentPosition = linearLayoutManager.findFirstVisibleItemPosition();
            mSuspensionBar.setY(0);

            updateSuspensionBar();
        }
    }
});

上面的程式碼我們只要注意註釋處,其他的和之前給出的相同。

總之,雖然大家的需求可能不同,但萬變不離其宗。只要掌握了思路,什麼需求都不怕。

Github 上已增加相應程式碼,最後看一下我們的效果,只在時間變化時才移動懸浮條

730559-6e69a3819c711ec3.gif
5133552-9e5a66e98f96ded9.gif

相關文章