recycleview中單個item上滑到頂部後懸停頂部的功能實現

需求背景

需要將列表中的第二個子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);
            }
        }));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章