本文已受權微信公衆號「玉剛說」獨家原創發佈java
這些天遇到一個列表數據吸底需求,若是不滿一屏就所有展現,若是超過一屏就讓底部懸浮在屏幕底部。android
大概效果以下圖:git
列表咱們通常用RecyclerView來實現,關於底部懸浮這裏有兩種實現方法,一種是經過測量RecyclerView內容高度,另外一種是用咱們熟悉的ItemDecoration來實現。github
下面就具體介紹這兩種實現方式。canvas
這種方式很直觀,咱們先獲取RecyclerView控件的高度h1,設置完數據後再獲取RecyclerView的內容高度h2,而後將h1與h2進行比較:bash
①若是h1大於等於h2,則說明內容沒有超出屏幕高度,此時只須要將數據徹底展現便可。微信
②若是h1小於h2,則說明RecyclerView內容高度超出屏幕,此時RecyclerView可滾動,因此咱們須要在RecyclerView底部顯示吸底的View。app
RecyclerView控件的高度咱們定義爲h1,以下圖所示:ide
經過recyclerView#getHeight方法獲取到的高度是固定的,就是佈局文件中設定的recyclerView高度。佈局
具體代碼爲:
// 獲取RecyclerView控件高度
int recyclerViewHeight = recyclerView.getHeight();
LogUtils.e(TAG, "recyclerViewHeight: " + recyclerViewHeight);
複製代碼
RecyclerView內容的高度咱們定義爲h2,以下圖所示:
由上圖可知,h2的高度須要在RecyclerView繪製完成之後動態獲取,具體代碼以下所示:
// 獲取recyclerView的內容高度
int recyclerViewRealHeight = recyclerView.computeVerticalScrollRange();
LogUtils.e(TAG, "recyclerViewRealHeight: " + recyclerViewRealHeight);
複製代碼
h1>=h2的狀況,具體以下圖所示:
咱們只須要讓Recycler的Adapter普通Item佈局和底部的Footer佈局就能夠了。
最後咱們看下h1<h2的狀況,具體以下圖所示:
咱們在RecyclerView控件的上方,蓋一個佈局,這個懸浮佈局的實現要和Adapter中的Footer佈局實現同樣。
接着咱們看下如何實現。具體分爲以下幾個步驟: ①將RecyclerView的父佈局修改成RelativeLayouot,在RelativeLayouot的底部、RecyclerView的上方添加一個Footer佈局。 ②讓Adapter支持兩種佈局,普通Item和Footer佈局 ③在給RecyclerView設置完數據後,獲取RecyclerView的控件高度h1和RecyclerView的內容高度h2 ④若是h1<h2,就讓RecyclerView上方的Footer佈局顯示,不然就不顯示。
接下來看代碼:
①佈局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.view.RecyclerViewBottomFloatByViewHeightActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
<TextView
android:id="@+id/tv_bottom"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:background="#BCEAC1"
android:gravity="center"
android:text="我是底部"
android:visibility="gone" />
</RelativeLayout>
複製代碼
②關於RecyclerView.Adapter如何支持多種ViewType,這裏就再也不細說了,具體代碼實現文末有連接。
③獲取h1和h2的值:爲了不recyclerView獲取到的高度0,咱們須要在給RecyclerView設置完數據以後,經過View#post(Runnable)方法獲取。具體代碼以下:
recyclerView.post(() -> {
// 獲取RecyclerView控件高度
int recyclerViewHeight = recyclerView.getHeight();
LogUtils.e(TAG, "recyclerViewHeight: " + recyclerViewHeight);
// 獲取recyclerView的內容高度
int recyclerViewRealHeight = recyclerView.computeVerticalScrollRange();
LogUtils.e(TAG, "recyclerViewRealHeight: " + recyclerViewRealHeight);
});
複製代碼
④默認狀況下懸浮佈局不顯示,只有h1<h2時,該懸浮佈局才顯示,核心代碼以下:
// 根據剩餘空間肯定是否須要顯示吸底的圖表底部
if (recyclerViewHeight < recyclerViewRealHeight) {
tvBottom.setVisibility(View.VISIBLE);
} else {
tvBottom.setVisibility(View.GONE);
}
複製代碼
須要說明的是,這種經過獲取View高度來實現單個View懸浮效果
的方式,不只僅適用於RecyclerView,它更是一種通用的方式。但它的缺點也很明顯,須要根據不容的業務去計算不一樣的View的高度。
通常不推薦這種方式去實現,不過它能夠當作一個保底方案,畢竟簡單粗暴易理解易實現。
接下來咱們來說解如何使用ItemDecoration來實現底部View懸浮效果。
咱們知道,系統提供了DividerItemDecoration組件,讓咱們方便的給RecyclerView繪製分割線。
DividerItemDecoration的具體使用方式請看RecyclerView設置分割線---DividerItemDecoration,具體代碼示例請看RecyclerViewDividerItemDecorationActivity。
這裏簡單介紹下ItemDecoration。
接觸過ItemDecoration的同窗知道,經過自定義ItemDecoration就能夠實現酷炫的分組懸停效果。
ItemDecoration中有三個重要方法,源碼以下:
public static abstract class ItemDecoration {
...
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
複製代碼
這三個方法的做用以下:
ItemDecoration#getItemOffsets:經過Rect爲每一個Item設置偏移,爲onDraw和onDrawOver方法中的繪製預留空間。
ItemDecoration#onDraw:經過該方法,在Canvas上繪製內容,在繪製Item以前調用。(若是沒有經過getItemOffsets設置偏移的話,Item的內容會將其覆蓋)
ItemDecoration#onDrawOver:經過該方法,在Canvas上繪製內容,在Item以後調用。(畫的內容會覆蓋在item的上層)
他們的層級關係以下圖所示:
須要說明的是,這三個方法都是針對每一個可見Item的區域的,若是不加限制的話,每一個Item都會調用它。
若是咱們重寫了ItemDecoration#getItemOffsets
方法,該方法就會在現有Item空間的基礎上新增空間,因此這個操做也會修改咱們RecyclerView內容高度。
具體實例請看RecyclerViewCustomItemDecorationDividerActivity和MyDividerItemDecoration。頁面打開方式以下所示:
在用ItemDecoration實現分組懸停的過程當中,又能夠細分爲兩種方法。
一種是經過getItemOffsets方法預留空間,而後在onDrawOver中對應的區域繪製懸停的頭部。懸停的部分須要額外繪製,不會複用Adapter中的Item的View。
另外一種方法是,將須要懸停的部分也繪製到Item中,Adapter中的Item是以組爲基本單位,一個Item會包含組中的全部View,Item內部第一個元素就是須要繪製的懸停頭部。而後咱們就能夠在onDrawOver獲取第一個可見Item的頭部View,接着複用這個頭部View,將其繪製在頂部便可。
接下來對這兩種方式進行介紹。
先看下不添加ItemDecoration的效果:
再看下添加完ItemDecoration後的效果:
具體代碼請參照RecyclerViewCustomItemDecorationFloatGroupActivity。這個類中的實現實際上是簡化了Gavin-ZYX/StickyDecoration項目中的實現。
這裏須要說明的是,這種方法實現的核心是getItemOffsets預留空間,onDrawOver直接在Item上層繪製新的懸停佈局,懸停佈局不復用ItemView
。從上面的示例能夠看出,分組的頭部View是在ItemDecoration中繪製的,在Adapter中不用繪製分組的頭部。
這種方法,將須要懸停的部分也繪製到Item中,Adapter中的Item是一個組的全部元素,Item內部第一個元素就是須要繪製的懸停頭部。而後咱們就能夠在onDrawOver獲取第一個可見Item的頭部View,接着複用這個頭部View,將其繪製在頂部便可。
示意圖以下:
咱們在onDrawOver中獲取到第一個可見子View,而後根據id從裏面獲取到頭部View,接着將這個用canvas將這個View繪製出來便可。
有興趣的同窗能夠自行實現。
咱們的這個吸底效果跟分組懸停效果是有所不一樣的,分組懸停效果針對的是第一個可見的子View,吸底效果針對的是最後一個可見的子View。
咱們的實現思路以下: ①讓RecyclerView.Adapter支持普通的Item和Footer類型的Item。 ②經過ItemDecoration繪製懸停View。
emmmmm,看起來很簡單的樣子。
經過上面對ItemDecoration中三個核心方法的分析,這裏咱們選擇onDrawOver方法來完成繪製,直接在最後一個Item上方繪製一個如出一轍的Footer便可。
咱們前面說過,onDrawOver這幾個方法是針對全部Item的,若是不加限制,則全部的Item都會繪製。
接下來就是選擇使用哪一個可見子View
繪製這個Footer的問題了。咱們有兩種選擇,一個是最後一個可見的子View——lastView
,一個是最後一個徹底可見的子View——lastVisibleView
,他們的位置分別經過下面方法獲取到:
int lastPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastVisibleItemPosition();
複製代碼
int lastCompletelyVisibleItemPosition = ((LinearLayoutManager)parent.getLayoutManager()).findLastCompletelyVisibleItemPosition();
複製代碼
關於RecyclerView經常使用方法的總結,請看RecyclerView經常使用方法總結。
在多數狀況下,lastView跟lastVisibleView不是同一個,只有在最後一個可見View的底部恰好達到RecyclerView下邊界的時候,lastView跟lastVisibleView就是同一個了。
大多數狀況下,lastView跟lastVisibleView都不是同一個,具體以下圖所示:
當某個Item的底部與RecyclerView的底部重疊時,lastView跟lastVisibleView就是同一個了,具體以下圖:
咱們先看使用lastVisibleView來繪製底部懸浮View的狀況。 lastVisibleView永遠在RecyclerView內部顯示,它的bottom的值會一直小於等於RecyclerView.getHeight的值的。
默認狀況下,懸浮View會繪製在lastVisibleView內部,跟lastVisibleView底部對齊。因此咱們須要給懸浮View設置一個向下的偏移量,這個偏移量的值就是RecyclerView.getHeight - lastVisibleView.getBottom的值。具體以下圖所示:
咱們只須要給繪製好的Footer添加一個offset
的值,讓其向下偏移offset的值便可。
然而不幸的是,經過onDrawOver繪製的View,是不能超出Item下邊界範圍
的。若是超出對應Item的bottom區域的話就沒法顯示,也就是說此路不通。
沒辦法了,只能看下lastView了。
咱們以lastView.getTop的值-懸浮View高度
的結果做爲繪製懸浮View的top值,因此懸浮View至關於一直懸浮在lastView的頂部。
幸運的是,即便超出Item上方區域,onDrawOver的內容也是正常顯示的。
接下來咱們須要給top值設置一個偏移量,這個偏移量就是RecyclerView.getHeight - lastVisibleView.getTop的值。
具體以下圖所示:
最後咱們看下效果:
具體實現請看RecyclerViewBottomFloatByItemDecorationActivity和BottomFloatItemDecoration。
github項目地址:Android_Base_Demo
RecyclerView相關的demo打開方式以下:
喜歡的話就點個贊吧!
一、【Android】RecyclerView:打造懸浮效果