需求背景
需要將列表中的第二個子view在滑動到頂端之後懸停在頂部,下拉之後取消懸停頂部,實現後效果如下圖中所示,當上滑時候需要將列表中的綜合銷量價格選擇欄目固定在頂部,當下拉後恢復列表中位置
實現過程:
看到這樣的需求首先想到的一個詞就是recycleview頂部吸附效果,在大略查找recycleview可用方法之後,發現沒有這樣的方法可以直接使用,google之後搜索到很多相似內容,都是stickyitemdecoration相關內容,直接下載源碼,加入項目後測試發現不符合需求,遂放棄。
1.首次實現過程,通過利用CollapsingToolbarLayout的可摺疊titlebar來實現該功能,通過將宮格區和需要懸停的區域都寫到CollapsingToolbarLayout中,然後將Toolbar也寫入到CollapsingToolbarLayout中,高度設置爲與需要懸停控件高度一致,顯示狀態設置爲invisible,這樣也可以實現該功能,一下是佈局文件詳細:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white_color"
app:elevation="0dp"
app:layout_behavior="android.support.design.widget.AppBarLayout$Behavior">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
//圖中的宮格區域控件
<com.android.hshq.coupon.fragment.main.MainViewTabLayout
android:id="@+id/scroll_other_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="parallax" />
//途中需要懸停的控件
<com.android.hshq.coupon.view.SortTitleEleven
android:id="@+id/scroll_other_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_size_10"
android:layout_marginBottom="@dimen/dp_size_10"
app:show_eleven="false"
app:show_voucher_new="false" />
</LinearLayout>
<android.support.v7.widget.Toolbar
android:id="@+id/hot_list_toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_size_46"
android:paddingStart="0dp"
android:visibility="invisible"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:layout_collapseMode="parallax">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll_nested"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/scroll_other_recycle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
MainViewTabLayout爲途中的宮格區域,SortTitleEleven爲需要實現懸停的控件,這樣做雖然可以實現需求中的功能,但是RecyclerView不斷的上滑加載更多,內存佔用大的問題也隨之而來,因在NestedScrollView中,RecyclerView的回收複用機制失效,導致內存得不到及時的釋放,佔用不斷增大,最終極易導致OOM,所以該方式pass掉。
2.再次實現,通過下載開源StickyItemDecoration代碼,發現其實現原理爲,通過繼承RecyclerView.ItemDecoration並重寫onDrawOver,通過LinearLayoutManager獲取當前屏幕顯示的item。然後輪詢這些item,如果該item是需要懸浮顯示的,就檢測該item是否已經或者超出了頂部,如果是,則將該item的內容繪製到頂端,如果還沒到頂部,則繼續將上一個需要顯示的item繪製到頂端,流程如下:
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
......
//獲取layoutmanager
mLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
mCurrentUIFindStickView = false;
//開始輪詢當前顯示的item
for (int m = 0, size = parent.getChildCount(); m < size; m++) {
View view = parent.getChildAt(m);
//如果是需要吸附顯示到頂端的item
if (mStickyView.isStickyView(view)) {
mCurrentUIFindStickView = true;
//獲取該viewholder,並且將該viewholder的位置緩存起來
getStickyViewHolder(parent);
cacheStickyViewPosition(m);
//如果已經到達或者超出了頂部,則將其繪製到頂部
if (view.getTop() <= 0) {
bindDataForStickyView(mLayoutManager.findFirstVisibleItemPosition(), parent.getMeasuredWidth());
} else {
//如果沒到達頂部,則獲取上一個需要吸附顯示的item,將它繪製到頂部
if (mStickyPositionList.size() > 0) {
if (mStickyPositionList.size() == 1) {
bindDataForStickyView(mStickyPositionList.get(0), parent.getMeasuredWidth());
} else {
int currentPosition = getStickyViewPositionOfRecyclerView(m);
int indexOfCurrentPosition = mStickyPositionList.lastIndexOf(currentPosition);
if (indexOfCurrentPosition >= 1) bindDataForStickyView(mStickyPositionList.get(indexOfCurrentPosition - 1), parent.getMeasuredWidth());
}
}
}
......
//繪製item到頂部
drawStickyItemView(c);
break;
}
}
//如果當前一整屏的item都不是需要吸附顯示的item,則還是將上一個吸附顯示的item繪製到頂部
if (!mCurrentUIFindStickView) {
mStickyItemViewMarginTop = 0;
if (mLayoutManager.findFirstVisibleItemPosition() + parent.getChildCount() == parent.getAdapter().getItemCount() && mStickyPositionList.size() > 0) {
bindDataForStickyView(mStickyPositionList.get(mStickyPositionList.size() - 1), parent.getMeasuredWidth());
}
drawStickyItemView(c);
}
}
從該開源代碼的閱讀中有一個重要發現,LinearLayoutManager可以獲取到當前屏幕顯示的item處於整個recycleview中的索引位置:findFirstVisibleItemPosition,因此可利用該接口實現需求功能。
實現邏輯爲,通過在recycleview頂部繪製好一個與需要懸浮顯示一樣的控件,然後通過實時監測當前顯示的第一個item在recycleview中的索引位置,來確定頂部view是否顯示,需求中需要實現吸附的item在recycleview中的索引位置爲1,所以,當一下代碼中獲取的index>=1時候,就通過回調接口將頂部的view設置visiable即可,最終實現效果如上圖,且recycleview可很好的回收複用,內存也不會無限增長規避了OOM問題。
public class StickyItemDecorator extends RecyclerView.ItemDecoration {
private static final String TAG = "StickyItemDecorator";
private LinearLayoutManager mLayoutManager;
private int index;
private SortShowListener listener;
public StickyItemDecorator(SortShowListener listener) {
this.listener = listener;
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getAdapter().getItemCount() <= 0) return;
mLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
index = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
MyLog.d(TAG,"ondrawover firstVisible:" + index);
if(index >= 1) {
listener.showSort(true);
} else {
listener.showSort(false);
}
}
public interface SortShowListener {
void showSort(boolean show);
}
}
佈局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/scroll_other_recycle"
android:layout_width="match_parent"
android:layout_height="match_parent" />
//該view爲需要吸附在頂部的view,由StickyItemDecorator控制顯示與否
<LinearLayout
android:id="@+id/sort_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white_color"
android:visibility="gone"
>
<com.android.hshq.coupon.view.SortTitleEleven
android:id="@+id/scroll_other_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_size_10"
android:layout_marginBottom="@dimen/dp_size_10"
app:show_eleven="false"
app:show_voucher_new="false" />
</LinearLayout>
</FrameLayout>
java代碼設置
recyclerView.addItemDecoration(new StickyItemDecorator(new StickyItemDecorator.SortShowListener() {
@Override
public void showSort(boolean show) {
sortLayout.setVisibility(show ? View.VISIBLE : View.GONE);
}
}));