Android輕松實現(xiàn)RecyclerView懸浮條
經(jīng)簡書網(wǎng)友[茶And狗狗]提醒。添加原創(chuàng)作者原文地址:簡書-_飛翔的荷蘭豆的文章地址。
在我們在刷Instagram的動態(tài)時,你是否注意到這樣一個小小的動效,就是當(dāng)一條動態(tài)(以卡片形式呈現(xiàn))向上滑動時,動態(tài)卡片的頭部會始終懸浮在列表最上方,直到下一張動態(tài)卡片的頭部將它頂?shù)舨⑻鎿Q它懸浮著。言語可能說不清楚,就直接來看一下它的效果好了。
?
Instagram的懸浮條
綜合我上面的文字描述加上這張Gif圖,我想大家應(yīng)該知道這是個什么樣的效果了吧。那么不廢話了,接下來我就來說說一種很簡單的實現(xiàn)方法吧。
思路
雖然實現(xiàn)起來炒雞簡單,但還是花了我一個多小時的時間思考實現(xiàn)。先說說思考過程吧,那天中午,Instagram給我推了一條消息(哈,就是我最喜歡的偶像金泰妍更新了Ins),于是我就點進(jìn)去看了,喜歡了之后就開始研究這個效果,我反復(fù)地上下滑這個列表,因為Ins的列表有滾動條,我就發(fā)現(xiàn)每次滾動條在那個懸浮條附近的時候就會特別短。看到這個現(xiàn)象,敏銳的你是不是察覺到了什么?沒錯,我感覺這個就像是FrameLayout
的效果,一個FrameLayout
里按順序有列表,懸浮條兩個View,懸浮條覆蓋在列表的上方,它在合適的時機(jī)更新自己的位置,在合適的時機(jī)更新自己的信息,然后看上去就像是一個懸浮的效果。
接下來我們思考的核心就轉(zhuǎn)移到了如何確定并找到這個合適的時機(jī)。
再仔細(xì)觀察上面的Gif圖,我們可以確定當(dāng)?shù)诙€列表項的頭部距離列表頂端一個懸浮條的距離時,懸浮條隨著列表的滑動改變自身的位置,從而看起來像是被頂?shù)舻男Ч.嬕粡埡唵挝恢檬疽鈭D
?
那么,數(shù)據(jù)更新的時機(jī)也很容易確定,就是在懸浮條恰好完全被頂?shù)舻臅r候,更新自己的數(shù)據(jù),并移動到列表頂部。
至于如何找到這個時機(jī)會在接下來的實現(xiàn)部分講解。
實現(xiàn)
建立布局
如上面所言,就是一個簡單的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應(yīng)該為你列表項要懸浮顯示的布局。
找到時機(jī)
根據(jù)我們的思路,我們首先要找到第二個列表項的頭部距離列表頂端一個懸浮條的距離時的那個時機(jī),如果我們能找到這個時機(jī),那么第二個時機(jī)也相當(dāng)于找出來了。
這里我們使用的是RecyclerView
來實現(xiàn)列表,我們都知道RecyclerView
的列表布局是由LayoutManager
來確定的,由于一般要實現(xiàn)懸浮條顯示效果的列表一般都為線性列表,即我們一般會使用LinearLayoutManager
。通過LinearLayoutManager
,我們可以很方便的獲取到RecyclerView
中相應(yīng)位置的View
,這里我們需要獲取當(dāng)前懸浮條數(shù)據(jù)來源的View
和其下一個數(shù)據(jù)來源的View
。這兩個View
有什么用呢?懸浮條顯示的信息是來自第一個可見View
的,而其下方的View
正是第二個列表項,我們可以獲取到它的top
值。好了接下來就真的很簡單了,我們只要給RecyclerView
加一個ScrollListener
,并在相應(yīng)的回調(diào)里做之前我們想好的事就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
并觸發(fā)。
One more thing...
接下來,我們還需要……開玩笑,哪來的One more thing,我們已經(jīng)完成了?什么?這么快?這么一點代碼?恩,沒錯,就是只要這么一點代碼就好了,我們來看一下最后我們實現(xiàn)的效果(當(dāng)然最終效果的好壞還是取決與你列表項的布局,比如在Ins里這個效果就很好看呢~)
?
結(jié)語
哈哈,是不是很簡單呢,最后再說一下封裝的事,本來我是想封裝一下的,由于每個人的列表布局都不一樣,數(shù)據(jù)更新方式也不一樣,就不封裝了,是的,我水平不行,雖然我不想承認(rèn)~不過代碼真心特別少哦,源碼地址:https://github.com/wuapnjie/SuspensionBar
希望這篇文章可以對你有幫助,我也會繼續(xù)努力的。
補(bǔ)充
上面這種情況我們RecyclerView
的Item是單一的,但是我們的列表Item通常有很多種,只有在滑到我們想要類型的Item時才需要更新我們的懸浮條信息。比如很常見的通訊錄,在我們滑到從A開頭聯(lián)系人滑到B開頭聯(lián)系人時,懸浮條的信息才從A變?yōu)锽;再比如印象筆記的筆記列表,頂部的懸浮條是根據(jù)筆記的日期改變的。
那么,遇到這種情況我們應(yīng)該怎么簡單修改代碼來實現(xiàn)我們需求呢?
其實很簡單,思路已經(jīng)由上面確定了,只是我們要讓懸浮條移動的時機(jī)變化,變得更窄了,同時我們要更新的數(shù)據(jù)內(nèi)容也發(fā)生了變化(這當(dāng)然需要我們變換相應(yīng)的布局)。
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必須對應(yīng)時才發(fā)生移動
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 上已增加相應(yīng)代碼,最后看一下我們的效果,只在時間變化時才移動懸浮條
?