Android 輕鬆實現 RecyclerView 懸浮條

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

Instagram的懸浮條

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

思路

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

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

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

那麼,數據更新的時機也很容易肯定,就是在懸浮條剛好徹底被頂掉的時候,更新本身的數據,並移動到列表頂部。ide

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

實現

創建佈局

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

<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應該爲你列表項要懸浮顯示的佈局。code

找到時機

根據咱們的思路,咱們首先要找到第二個列表項的頭部距離列表頂端一個懸浮條的距離時的那個時機,若是咱們能找到這個時機,那麼第二個時機也至關於找出來了。cdn

這裏咱們使用的是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裏這個效果就很好看呢~)

結語

哈哈,是否是很簡單呢,最後再說一下封裝的事,原本我是想封裝一下的,因爲每一個人的列表佈局都不同,數據更新方式也不同,就不封裝了,是的,我水平不行,雖然我不想認可~不過代碼真心特別少哦,源碼地址:github.com/wuapnjie/Su…

但願這篇文章能夠對你有幫助,我也會繼續努力的。

補充

上面這種狀況咱們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 上已增長相應代碼,最後看一下咱們的效果,只在時間變化時才移動懸浮條

相關文章
相關標籤/搜索