1. 程式人生 > >RecyclerView實現帶有頭部的頂部懸浮置頂佈局

RecyclerView實現帶有頭部的頂部懸浮置頂佈局

由於專案需求需要,需要一個帶有頭部的吸頂佈局,在網上搜索了好多實現辦法,都不太理想,最終使用對RecyclerView新增分割線的方式,重寫了RecyclerView的分割線來實現這個懸浮欄。效果比較簡單,但是比較實用,下面主要介紹實現過程。

首先先貼上效果圖:

這裡寫圖片描述

1.第一步

首先要明白資料的資料結構,我們需要將資料分為幾組,每一組都是相同的一類,我這裡將資料分為了三組,分別為類別一,類別二,類別三然後建立SectionDecoration讓其繼承 RecyclerView.ItemDecoration,類內部的實現如下:
private PowerGroupListener mGroupListener;
    /**
     * 懸浮欄高度
     */
private int mGroupHeight = 80; /** * 是否靠左邊 * true 靠左邊 * false 不靠左 */ private boolean isAlignLeft = true; /** 通過建構函式將groupListener傳遞進來 */ private SectionDecoration(PowerGroupListener groupListener) { this.mGroupListener = groupListener; }
PowerGroupListener 介面的實現:
package byd.com.byd.ceilingdemo.listener;

import android.view.View;

/**
 * author:byd666 on 2017/12/2 15:13
 */
public interface PowerGroupListener {
    //獲取每一組的組名
    String getGroupName(int position);
    //得到組View
    View getGroupView(int position);
}

2.第二步

實現RecyclerView.ItemDecoration裡的兩個方法:(1)

getItemOffsets和(2)onDrawOver,這兩個方法是幹嘛的呢,(1)查找出RecyclerView的指定item的位移。(2)是給RecyclerView提供適當的裝飾到畫布上。由這個方法將任何內容在專案檢視繪製。具體實現:

@Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
         //得到你新增的分割線View的位置
        int pos = parent.getChildAdapterPosition(view);
         //獲取組名
        String groupId = getGroupName(pos);
        if (groupId == null) {return;}
        //只有是同一組的第一個才顯示懸浮欄
        if (pos == 0 || isFirstInGroup(pos)) {
            outRect.top = mGroupHeight;
        }
    }
獲取組名的方法:
    /**
     * 獲取組名
     * @param position 
     * @return 組名
     */
    private String getGroupName(int position) {
        if (mGroupListener != null) {
            return mGroupListener.getGroupName(position);
        } else {
            return null;
        }
    }
判斷是不是同一組的第一個:
  /**
     * 判斷是不是組中的第一個位置
     * 根據前一個組名,判斷當前是否為新的組
     */
    private boolean isFirstInGroup(int pos) {
        if (pos == 0) {
            return true;
        } else {
            String prevGroupId = getGroupName(pos - 1);
            String groupId = getGroupName(pos);
            return !TextUtils.equals(prevGroupId, groupId);
        }
    }
最重要的方法onDrawOver的實現:

程式碼中註釋很清楚,自行理解其繪製過程

@Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        //獲取單條的數目
        int itemCount = state.getItemCount();
        int childCount = parent.getChildCount();
        //得出距離左邊和右邊的padding
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        //開始繪製
        String preGroupName;
        String currentGroupName = null;
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
            preGroupName = currentGroupName;
            currentGroupName = getGroupName(position);
            if (currentGroupName == null || TextUtils.equals(currentGroupName, preGroupName))
            {
                continue;
            }
            int viewBottom = view.getBottom();
            //top 決定當前頂部第一個懸浮Group的位置
            int top = Math.max(mGroupHeight, view.getTop());
            if (position + 1 < itemCount) {
                //獲取下個GroupName
                String nextGroupName = getGroupName(position + 1);
                //下一組的第一個View接近頭部
                if (!currentGroupName.equals(nextGroupName) && viewBottom < top) {
                    top = viewBottom;
                }
            }

            //根據position獲取View
            View groupView = getGroupView(position);
            if (groupView == null){ return;}
            ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mGroupHeight);
            groupView.setLayoutParams(layoutParams);
            groupView.setDrawingCacheEnabled(true);
            groupView.measure(
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            //指定高度、寬度的groupView
            groupView.layout(0, 0, right, mGroupHeight);
            groupView.buildDrawingCache();
            Bitmap bitmap = groupView.getDrawingCache();
            int marginLeft = isAlignLeft ? 0 : right - groupView.getMeasuredWidth();
            c.drawBitmap(bitmap, left + marginLeft, top - mGroupHeight, null);
        }
    }
getGroupView的方法實現:
    /**
     * 獲取組View
     * @param position
     * @return 組名
     */
    private View getGroupView(int position) {
        if (mGroupListener != null) {
            return mGroupListener.getGroupView(position);
        } else {
            return null;
        }
    }

3.第三步

為了方便初始化和使用,建立一個類部類,用來 初始化 listener,設定Group高度,確定其是否靠左邊:
public static class Builder {
        SectionDecoration mDecoration;
        private Builder(PowerGroupListener listener) {
            mDecoration = new SectionDecoration(listener);
        }
        /**
         * 初始化 listener
         * @param listener
         * @return
         */
        public static Builder init(PowerGroupListener listener) {
            return new Builder(listener);
        }
        /**
         * 設定Group高度
         * @param groutHeight 高度
         * @return this
         */
        public Builder setGroupHeight(int groutHeight) {
            mDecoration.mGroupHeight = groutHeight;
            return this;
        }
        /**
         * 是否靠左邊
         * true 靠左邊(預設)、false 靠右邊
         * @param b b
         * @return  this
         */
        public Builder isAlignLeft(boolean b){
            mDecoration.isAlignLeft = b;
            return this;
        }
        public SectionDecoration build() {
            return mDecoration;
        }
    }
到這咱們就將分割線建立完成了,具體怎麼使用呢,

4. 第四步

具體的使用 ,為RecyclerView新增分割線即可。
 /**
     * 新增懸浮佈局
     */
    private void initDecoration() {
        SectionDecoration decoration = SectionDecoration.Builder
                .init(new PowerGroupListener() {
                    @Override
                    public String getGroupName(int position) {
                        //獲取組名,用於判斷是否是同一組
                        if (dataList.size() > position) {
                            return dataList.get(position).getName();
                        }
                        return null;
                    }
                    @Override
                    public View getGroupView(int position) {
                        //獲取自定定義的組View
                        if (dataList.size() > position) {
                            View view = getLayoutInflater().inflate(R.layout.item_group, null, false);
                            ((TextView) view.findViewById(R.id.tv)).setText(dataList.get(position).getName());
                            ((ImageView) view.findViewById(R.id.iv)).setImageResource(dataList.get(position).getIcon());
                            return view;
                        } else {
                            return null;
                        }
                    }
                })
                //設定高度
                .setGroupHeight(ScreenUtil.dip2px(this, 40))
                .build();
        mRecyclerView.addItemDecoration(decoration);
    } 
最後貼上xml佈局
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay">

                <TextView
                    android:id="@+id/title_tv"
                    android:layout_width="match_parent"
                    android:layout_height="56dp"
                    android:text="我是主標題"
                    android:gravity="center"
                    android:textSize="18sp"/>

                <TextView
                    android:id="@+id/header_tv"
                    android:background="@color/colorAccent"
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:textColor="#ffffff"
                    android:text="我是頭佈局"
                    android:gravity="center"
                    android:textSize="16sp"/>
            </LinearLayout>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rlv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_above="@+id/bottom_navigation"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_gravity="bottom|right"
        android:layout_marginBottom="32dp"
        android:layout_marginRight="32dp"
        android:src="@mipmap/ic_launcher"
        app:backgroundTint="@color/colorPrimary"
        app:borderWidth="0dp"
        app:elevation="8dp"
        app:fabSize="normal" />
</android.support.design.widget.CoordinatorLayout>

整個帶有頭吸頂佈局的實現過程就完成了,如果有需要程式碼的同學,點開clone即可,別忘了star :ஐ٩(๑´ᵕ`)۶ஐ: