RecyclerView 學習之吸頂

 

上面兩個效果圖,圖一里面我爲了展現吸頂原理,我給吸頂加了背景,圖二是取消背景的效果圖。

我直接上相關代碼,然後再進行解釋:

這個activity 的 xml :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.scwang.smartrefresh.layout.SmartRefreshLayout
        android:id="@+id/smart_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.scwang.smartrefresh.layout.header.ClassicsHeader
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/xd_rcv"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <tyj.com.testlib.xd.StickyHeadContainer
                android:id="@+id/stick_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <!--android:background="#0a0"-->

                <FrameLayout
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:background="#ccc">
                    <!--android:background="#a0a"-->

                    <TextView
                        android:id="@+id/xd_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:text="test"
                        android:textColor="#fff"
                        android:textSize="16sp" />
                    <!--android:background="#00a"-->

                    <View
                        android:layout_width="match_parent"
                        android:layout_height="1dp" />
                </FrameLayout>
            </tyj.com.testlib.xd.StickyHeadContainer>
        </FrameLayout>

    </com.scwang.smartrefresh.layout.SmartRefreshLayout>

</RelativeLayout>

xml 裏面的StickHeadContainer 的代碼:

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import tyj.com.yedashenlib.log.CyLogger;

/**
 * @author ChenYe created by on 2019/3/20 0020. 16:27
 **/

public class StickyHeadContainer extends ViewGroup {

    private static final String TAG = "StickyHeadContainer";
    private int mOffset,mLeft,mRight,mTop,mBottom;
    private int mLastOffset = Integer.MIN_VALUE;
    private int mLastStickyHeadPosition = Integer.MIN_VALUE;

    private DataCallback mDataCallback;

    public StickyHeadContainer(Context context) {
        this(context, null);
    }

    public StickyHeadContainer(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StickyHeadContainer(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO: 2017/1/9 屏蔽點擊事件
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int desireHeight;
        int desireWidth;

        int count = getChildCount();

        if (count != 1) {
            throw new IllegalArgumentException("只允許容器添加1個子View!");
        }

        final View child = getChildAt(0);
        // 測量子元素並考慮外邊距
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        // 獲取子元素的佈局參數
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 計算子元素寬度,取子控件最大寬度
        desireWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
        // 計算子元素高度
        desireHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
        CyLogger.e(TAG,"desireWidth1:"+desireWidth);

        // 考慮父容器內邊距
        desireWidth += (getPaddingLeft() + getPaddingRight());
        desireHeight += (getPaddingTop() + getPaddingBottom());
        CyLogger.e(TAG,"desireWidth2:"+desireWidth);
        // 嘗試比較建議最小值和期望值的大小並取大值
        desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
        desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
        // 設置最終測量值
        CyLogger.e(TAG,"desireWidth3:"+desireWidth);
        setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec), resolveSize(desireHeight, heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        final View child = getChildAt(0);
        MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();

        mLeft = paddingLeft + lp.leftMargin;
        mRight = child.getMeasuredWidth() + mLeft;

        mTop = paddingTop + lp.topMargin + mOffset;
        mBottom = child.getMeasuredHeight() + mTop;

        child.layout(mLeft, mTop, mRight, mBottom);
    }

    // 生成默認的佈局參數
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return super.generateDefaultLayoutParams();
    }

    // 生成佈局參數,將佈局參數包裝成我們的
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    // 生成佈局參數,從屬性配置中生成我們的佈局參數
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    // 查當前佈局參數是否是我們定義的類型這在code聲明佈局參數時常常用到
    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        return p instanceof MarginLayoutParams;
    }

    public void scrollChild(int offset) {
        if (mLastOffset != offset) {
            mOffset = offset;
            ViewCompat.offsetTopAndBottom(getChildAt(0), mOffset - mLastOffset);
        }
        mLastOffset = mOffset;
    }

    public int getChildHeight() {
        return getChildAt(0).getHeight();
    }

    public void onDataChange(int stickyHeadPosition) {
        if (mDataCallback != null && mLastStickyHeadPosition != stickyHeadPosition) {
            mDataCallback.onDataChange(stickyHeadPosition);
        }
        mLastStickyHeadPosition = stickyHeadPosition;
    }

    public void reset() {
        mLastStickyHeadPosition = Integer.MIN_VALUE;
    }

    public interface DataCallback {
        void onDataChange(int pos);
    }

    public void setDataCallback(DataCallback dataCallback) {
        mDataCallback = dataCallback;
    }
}

然後幾個adapter , XdAdapter -> BaseStickAdapter ->BaseDataAdapter :這三個adapter 寫到一起

 

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

import tyj.com.yedashenlib.log.CyLogger;
import tyj.com.yedashenlib.toast.ToastUtil;
import tyj.com.yedashenlib.widget.recylerview.holder.SuperViewHolder;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe
 *         created by on 2017/11/17 0017. 09:52
 *         <p>
 *         這個是單列的數據baseAdapter:如果你用這個自定義的recyclerView的話,你的使用場景是跟listview
 *         差不多的效果,就是單列的話,就可以寫一個adapter來繼承這個BaseAdapter,在你寫的adapter只
 *         需要對控件設置數據就可以了。這個BaseAdapter的使用範例的adapter是DataAdapter。
 **/

public abstract class BaseDataAdapter<T> extends RecyclerView.Adapter<SuperViewHolder> {

    private static final String TAG = "BaseDataAdapter";
    protected List<T> mDataList = new ArrayList<>();

    @Override
    public int getItemCount() {
        return mDataList == null ? 0 : mDataList.size();
    }

    @Override
    public int getItemViewType(int position) {
        if (null != mDataList && !mDataList.isEmpty()) {
            T t = mDataList.get(position);
            if (t instanceof MultiTypeEntity) {
                return ((MultiTypeEntity) t).getItemType();
            }
        }
        return super.getItemViewType(position);
    }

    @Override
    public SuperViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (setLayout(viewType) instanceof Integer) {
            View view = View.inflate(parent.getContext(), ((Integer) setLayout(viewType)), null);
            return new SuperViewHolder(view);
        } else if (setLayout(viewType) instanceof View) {
            return new SuperViewHolder(((View) setLayout(viewType)));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(SuperViewHolder holder, int position) {
        if (null == mDataList || mDataList.isEmpty() || position > mDataList.size() - 1) {
            return;
        }
        onBindItemHolder(holder, position, mDataList.get(position));
    }

    //這個是用來解決局部刷新數據的
    @Override
    public void onBindViewHolder(SuperViewHolder holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            //主要是這裏
            onBindItemHolder(holder, position, payloads);
        }
    }

    /**
     * 這個是繼承這個BaseAdapter需要傳進來的item佈局,可以傳資源id,可以傳view
     *
     * @param type 多類型,如果你是單一的類型,這裏就傳0就好了
     * @return
     */
    protected abstract Object setLayout(int type);

    /**
     * 這個是讓繼承這個BaseAdapter去實現的,然後在這個方法裏面去把數據設置到item的
     * 控件上去的
     *
     * @param holder   holder
     * @param position position
     * @param itemData item數據
     */
    protected abstract void onBindItemHolder(SuperViewHolder holder, int position, T itemData);

    public void onBindItemHolder(SuperViewHolder holder, int position, List<Object> payloads) {

    }

    /**
     * 向外部提供的傳遞數據到adapter裏面刷新數據用的
     *
     * @param data 傳一個集合進來,那麼代表是想刷新整個adapter的數據
     */
    public void setDataList(List<T> data) {
        mDataList.clear();
        mDataList.addAll(data);
        notifyDataSetChanged();
    }

    /**
     * 向外部提供插入一個集合數據到adapter使用的
     *
     * @param data
     */
    public void addDataList(List<T> data) {
        mDataList.addAll(data);
        notifyDataSetChanged();
    }

    /**
     * 在最後面添加一條數據,然後局部刷新一下
     *
     * @param data
     */
    public void insertLast(T data) {
        mDataList.add(data);
        notifyItemInserted(getItemCount());
    }

    /**
     * 返回點擊的實體的數據,這個數據是從adapter裏面的集合拿到這個數據,position也是adapter裏面的,
     * 這樣拿出來的數據纔是最安全的。
     *
     * @param position
     * @return
     */
    public T getItemData(int position) {
        if (position < mDataList.size()) {
            return mDataList.get(position);
        }
        return null;
    }

    /**
     * 刪除指定的那一條數據
     *
     * @param position
     */
    public void remove(int position) {
        this.mDataList.remove(position);
        notifyItemRemoved(position);
        if (position != getDataList().size()) {
            notifyItemRangeChanged(position, this.mDataList.size() - position);
        }
    }

    /**
     * 刪除指定區間的
     */
    public void removeBegin(int beginPosition, int endPosition) {
        try {
            if (mDataList.size() > beginPosition && mDataList.size() > endPosition) {
                for (int i = beginPosition; i < endPosition; i++) {
                    mDataList.remove(beginPosition);
                }
            }
            notifyItemMoved(beginPosition, endPosition);
            notifyItemRangeChanged(beginPosition, this.mDataList.size() - beginPosition);
        } catch (Exception e) {
            ToastUtil.newInstance().showToast("出錯了!");
            CyLogger.e(TAG, e.getMessage());
        }
    }

    /**
     *
     */
    public List<T> getDataList() {
        return mDataList;
    }

}
import android.support.v7.widget.RecyclerView;

import tyj.com.yedashenlib.widget.recylerview.adapter.BaseDataAdapter;
import tyj.com.yedashenlib.widget.recylerview.holder.SuperViewHolder;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:38
 **/

public abstract class BaseStickyAdapter<T> extends BaseDataAdapter<T> {

    /**
     * title 、 normal,爲了抽取,下面的兩個類型定義我還是寫在了BaseStickyAdapter裏面,如果你後續還要增加類型,可以
     * 自己寫在繼承BaseStickyAdapter的類裏面
     */
    public static final int TYPE_TITLE = 2, TYPE_NORMAL = 3;

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        FullSpanUtil.onAttachedToRecyclerView(recyclerView, this, TYPE_TITLE);
    }

    @Override
    public void onViewAttachedToWindow(SuperViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        FullSpanUtil.onViewAttachedToWindow(holder, this, TYPE_TITLE);
    }
}
import android.view.View;

import tyj.com.testlib.R;
import tyj.com.testlib.entity.XdNormalEntity;
import tyj.com.testlib.entity.XdTitleEntity;
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.yedashenlib.widget.recylerview.holder.SuperViewHolder;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:33
 *         TODO 對title 的點擊事件,我這裏暫時只寫了一個,如果你的吸頂佈局裏面想設置更多的點擊事件,你可以自行設置
 **/

public class XdAdapter extends BaseStickyAdapter<MultiTypeEntity> {

    private OnXdTitleClickListener mXdClickListener = null;

    /**
     * 這裏有很多點擊事件需要設置,要根據實際情況,目前我這裏有兩個點擊事件,所以這個item點擊事件有兩個,我就
     * 直接寫點擊事件1和點擊事件2了
     *
     * @param listener
     */
    public void setOnItemClickListener(OnXdTitleClickListener listener) {
        mXdClickListener = listener;
    }

    @Override
    protected Object setLayout(int type) {
        int resId;
        switch (type) {
            case BaseStickyAdapter.TYPE_TITLE:
                resId = R.layout.item_multi_type_1;
                break;
            case BaseStickyAdapter.TYPE_NORMAL:
                resId = R.layout.item_multi_type_2;
                break;
            //如果要在多類型的基礎上再實現多類型,可以在這裏加case
            default:
                resId = R.layout.item_multi_type_1;
                break;
        }
        return resId;
    }

    @Override
    protected void onBindItemHolder(SuperViewHolder holder, int position, MultiTypeEntity itemData) {
        if (holder.getItemViewType() == BaseStickyAdapter.TYPE_TITLE) {
            final String titleName = ((XdTitleEntity) itemData).getTitleName();
            holder.setText(R.id.type_1_tv, titleName);
            if (null != mXdClickListener) {
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mXdClickListener.titleClick("點擊了吸頂的頂部title:" + titleName);
                    }
                });
            }
        } else {
            final String desc = ((XdNormalEntity) itemData).getDesc();
            holder.setText(R.id.type_2_tv, desc);
            if (null != mXdClickListener) {
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mXdClickListener.clickItem("點擊了普通item:" + desc);
                    }
                });
            }
        }
    }

    public interface OnXdTitleClickListener {

        /**
         * 可以在這裏自行添加並列的事件
         *
         * @param title msg
         */
        void titleClick(String title);

        /**
         * 普通item點擊事件
         *
         * @param msg
         */
        void clickItem(String msg);
    }
}

然後BaseStickAdapter 裏面用到了一個Util :

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.ViewGroup;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:41
 **/
public class FullSpanUtil {

    public static void onAttachedToRecyclerView(RecyclerView recyclerView, final RecyclerView.Adapter adapter, final int pinnedHeaderType) {
        // 如果是網格佈局,這裏處理標籤的佈局佔滿一行
        final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            final GridLayoutManager.SpanSizeLookup oldSizeLookup = gridLayoutManager.getSpanSizeLookup();
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    if (adapter.getItemViewType(position) == pinnedHeaderType) {
                        return gridLayoutManager.getSpanCount();
                    }
                    if (oldSizeLookup != null) {
                        return oldSizeLookup.getSpanSize(position);
                    }
                    return 1;
                }
            });
        }
    }

    public static void onViewAttachedToWindow(RecyclerView.ViewHolder holder, RecyclerView.Adapter adapter, int pinnedHeaderType) {
        // 如果是瀑布流佈局,這裏處理標籤的佈局佔滿一行
        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
            final StaggeredGridLayoutManager.LayoutParams slp = (StaggeredGridLayoutManager.LayoutParams) lp;
            slp.setFullSpan(adapter.getItemViewType(holder.getLayoutPosition()) == pinnedHeaderType);
        }
    }

}

adapter的 兩個xml ,按照先後順序是type_1的 ,第二個是type_2的xml:

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/type_1_tv"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#ccc"
        android:gravity="center"
        android:text="test"
        android:textColor="#fff"
        android:textSize="16sp" />

</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher" />

        <TextView
            android:id="@+id/type_2_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="40dp"
            android:gravity="center"
            android:text="test"
            android:textSize="16sp" />
    </LinearLayout>
</RelativeLayout>

activity代碼 :

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.TextView;

import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;

import java.util.ArrayList;
import java.util.List;

import io.reactivex.annotations.NonNull;
import tyj.com.testlib.adapter.XdAdapter;
import tyj.com.testlib.entity.XdNormalEntity;
import tyj.com.testlib.entity.XdTitleEntity;
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.testlib.xd.StickyHeadContainer;
import tyj.com.testlib.xd.StickyItemDecoration;
import tyj.com.yedashenlib.toast.ToastUtil;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/20 0020. 14:46
 *         需要注意,你的StickyHeadContainer 佈局需要跟吸頂佈局一直,不然會有顯示
 **/

public class XdActivity extends Activity {

    private static final String TAG = "XdActivity";
    private TextView mXdTitleTv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xd);
        final SmartRefreshLayout sm = (SmartRefreshLayout) findViewById(R.id.smart_layout);
        mXdTitleTv = (TextView) findViewById(R.id.xd_tv);
        RecyclerView rcv = (RecyclerView) findViewById(R.id.xd_rcv);
        rcv.setLayoutManager(new LinearLayoutManager(this));
        final StickyHeadContainer stickHeader = (StickyHeadContainer) findViewById(R.id.stick_layout);
        rcv.addItemDecoration(new StickyItemDecoration(stickHeader, BaseStickyAdapter.TYPE_TITLE));
        final XdAdapter adapter = new XdAdapter();
        rcv.setAdapter(adapter);
        adapter.setOnItemClickListener(new XdAdapter.OnXdTitleClickListener() {
            @Override
            public void titleClick(String title) {
                ToastUtil.newInstance().showToast(title);
            }

            @Override
            public void clickItem(String msg) {
                ToastUtil.newInstance().showToast(msg);
            }
        });
        stickHeader.setDataCallback(new StickyHeadContainer.DataCallback() {
            @Override
            public void onDataChange(int pos) {
                XdTitleEntity item = (XdTitleEntity) adapter.getItemData(pos);
                mXdTitleTv.setText(item.getTitleName());
            }
        });
        sm.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(@NonNull RefreshLayout refreshLayout) {
                adapter.setDataList(createList());
                sm.finishRefresh();
            }
        });

        sm.autoRefresh(200, 500, 1, false);
        adapter.setDataList(createList());
    }

    private List<MultiTypeEntity> createList() {
        List<MultiTypeEntity> list = new ArrayList<>();
        list.add(new XdTitleEntity("測試title 1"));
        for (int i = 0; i < 4; i++) {
            list.add(new XdNormalEntity("測試文本1_" + i));
        }

        list.add(new XdTitleEntity("測試title 2"));
        for (int i = 0; i < 8; i++) {
            list.add(new XdNormalEntity("測試文本2_" + i));
        }

        list.add(new XdTitleEntity("測試title 3"));
        for (int i = 0; i < 15; i++) {
            list.add(new XdNormalEntity("測試文本3_" + i));
        }
        return list;
    }
}

activity 裏面用到的 ItemDecoration 、 兩個實體 、實體裏面繼承的interface :

import android.graphics.Canvas;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;

/**
 * @author ChenYe created by on 2019/3/21 0021. 09:30
 **/
public class StickyItemDecoration extends RecyclerView.ItemDecoration {

    private static final String TAG = "StickyItemDecoration";

    private int mStickyHeadType;
    private int mFirstVisiblePosition;
    private int mStickyHeadPosition;
    private int[] mInto;

    private RecyclerView.Adapter mAdapter;

    private StickyHeadContainer mStickyHeadContainer;
    private boolean mEnableStickyHead = true;

    public StickyItemDecoration(StickyHeadContainer stickyHeadContainer, int stickyHeadType) {
        mStickyHeadContainer = stickyHeadContainer;
        mStickyHeadType = stickyHeadType;
    }

    // 當我們調用mRecyclerView.addItemDecoration()方法添加decoration的時候,RecyclerView在繪製的時候,去會繪製decorator,即調用該類的onDraw和onDrawOver方法,
    // 1.onDraw方法先於drawChildren
    // 2.onDrawOver在drawChildren之後,一般我們選擇複寫其中一個即可。
    // 3.getItemOffsets 可以通過outRect.set()爲每個Item設置一定的偏移量,主要用於繪製Decorator。

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);

        checkCache(parent);

        if (mAdapter == null) {
            // checkCache的話RecyclerView未設置之前mAdapter爲空
            return;
        }

        calculateStickyHeadPosition(parent);

        if (mEnableStickyHead && mFirstVisiblePosition >= mStickyHeadPosition && mStickyHeadPosition != -1) {
            View belowView = parent.findChildViewUnder(c.getWidth() / 2, mStickyHeadContainer.getChildHeight() + 0.01f);
            mStickyHeadContainer.onDataChange(mStickyHeadPosition);
            int offset;
            if (isStickyHead(parent, belowView) && belowView.getTop() > 0) {
                offset = belowView.getTop() - mStickyHeadContainer.getChildHeight();
            } else {
                offset = 0;
            }
            mStickyHeadContainer.scrollChild(offset);
            mStickyHeadContainer.setVisibility(View.VISIBLE);
//            CyLogger.e(TAG, "吸頂可見,高度:" + mStickyHeadContainer.getHeight() + ",寬度:" + mStickyHeadContainer.getWidth());
        } else {
            mStickyHeadContainer.reset();
            mStickyHeadContainer.setVisibility(View.INVISIBLE);
//            CyLogger.e(TAG, "吸頂不可見");
        }

    }

    public void enableStickyHead(boolean enableStickyHead) {
        mEnableStickyHead = enableStickyHead;
        if (!mEnableStickyHead) {
            mStickyHeadContainer.setVisibility(View.INVISIBLE);
        }
    }

    private void calculateStickyHeadPosition(RecyclerView parent) {
        final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();

        //        mFirstCompletelyVisiblePosition = findFirstCompletelyVisiblePosition(layoutManager);

        // 獲取第一個可見的item位置
        mFirstVisiblePosition = findFirstVisiblePosition(layoutManager);

        // 獲取標籤的位置,
        int stickyHeadPosition = findStickyHeadPosition(mFirstVisiblePosition);
        if (stickyHeadPosition >= 0 && mStickyHeadPosition != stickyHeadPosition) {
            // 標籤位置有效並且和緩存的位置不同
            mStickyHeadPosition = stickyHeadPosition;
        }
    }

    /**
     * 從傳入位置遞減找出標籤的位置
     *
     * @param formPosition
     * @return
     */
    private int findStickyHeadPosition(int formPosition) {

        for (int position = formPosition; position >= 0; position--) {
            // 位置遞減,只要查到位置是標籤,立即返回此位置
            final int type = mAdapter.getItemViewType(position);
            if (isStickyHeadType(type)) {
                return position;
            }
        }

        return -1;
    }

    /**
     * 通過適配器告知類型是否爲標籤
     *
     * @param type
     * @return
     */
    private boolean isStickyHeadType(int type) {
        return mStickyHeadType == type;
    }

    /**
     * 找出第一個可見的Item的位置
     *
     * @param layoutManager
     * @return
     */
    private int findFirstVisiblePosition(RecyclerView.LayoutManager layoutManager) {
        int firstVisiblePosition = 0;
        if (layoutManager instanceof GridLayoutManager) {
            firstVisiblePosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
        } else if (layoutManager instanceof LinearLayoutManager) {
            firstVisiblePosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            mInto = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
            ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(mInto);
            firstVisiblePosition = Integer.MAX_VALUE;
            for (int pos : mInto) {
                firstVisiblePosition = Math.min(pos, firstVisiblePosition);
            }
        }
        return firstVisiblePosition;
    }

    /**
     * 找出第一個完全可見的Item的位置
     *
     * @param layoutManager
     * @return
     */
    private int findFirstCompletelyVisiblePosition(RecyclerView.LayoutManager layoutManager) {
        int firstVisiblePosition = 0;
        if (layoutManager instanceof GridLayoutManager) {
            firstVisiblePosition = ((GridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
        } else if (layoutManager instanceof LinearLayoutManager) {
            firstVisiblePosition = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            mInto = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
            ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(mInto);
            firstVisiblePosition = Integer.MAX_VALUE;
            for (int pos : mInto) {
                firstVisiblePosition = Math.min(pos, firstVisiblePosition);
            }
        }
        return firstVisiblePosition;
    }

    /**
     * 檢查緩存
     *
     * @param parent
     */
    private void checkCache(final RecyclerView parent) {

        final RecyclerView.Adapter adapter = parent.getAdapter();
        if (mAdapter != adapter) {
            mAdapter = adapter;
            // 適配器爲null或者不同,清空緩存
            mStickyHeadPosition = -1;

            mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
                @Override
                public void onChanged() {
                    reset();
                }

                @Override
                public void onItemRangeChanged(int positionStart, int itemCount) {
                    reset();
                }

                @Override
                public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
                    reset();
                }

                @Override
                public void onItemRangeInserted(int positionStart, int itemCount) {
                    reset();
                }

                @Override
                public void onItemRangeRemoved(int positionStart, int itemCount) {
                    reset();
                }

                @Override
                public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                    reset();
                }
            });

        }
    }

    private void reset() {
        mStickyHeadContainer.reset();
    }

    /**
     * 查找到view對應的位置從而判斷出是否標籤類型
     *
     * @param parent
     * @param view
     * @return
     */
    private boolean isStickyHead(RecyclerView parent, View view) {
        final int position = parent.getChildAdapterPosition(view);
        if (position == RecyclerView.NO_POSITION) {
            return false;
        }
        final int type = mAdapter.getItemViewType(position);
        return isStickyHeadType(type);
    }

}
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:32
 **/

public class XdTitleEntity implements MultiTypeEntity {

    private String titleName;

    public XdTitleEntity(String titleName) {
        this.titleName = titleName;
    }

    public String getTitleName() {
        return titleName;
    }

    public void setTitleName(String titleName) {
        this.titleName = titleName;
    }

    @Override
    public int getItemType() {
        return BaseStickyAdapter.TYPE_TITLE;
    }
}
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:32
 **/

public class XdNormalEntity implements MultiTypeEntity {

    private String desc;

    public XdNormalEntity(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public int getItemType() {
        return BaseStickyAdapter.TYPE_NORMAL;
    }
}
/**
 * @author ChenYe created by on 2019/3/20 0020. 13:50
 **/

public interface MultiTypeEntity {

    /**
     * 返回數據的type
     *
     * @return
     */
    int getItemType();
}

你把這些類拿起來然後自己寫成一個小demo應該是可以跑起來的,我是借鑑了好幾個大神的demo,然後再綜合一下弄出來。

需要注意的地方是activity 的xml :

1) 你自仔細看這個xml 就會感覺  箭頭 3標記的像是多餘的,但是其實是有原因的,因爲箭頭 2 標記的 TextView 無法android:layout_width = "match_parent" ,寫了就出現bug了。我是不斷的加背景顏色才試出來的。所以我用箭頭 3 來將箭頭 1 的佈局撐起來成match_parent ,然後再將textView 居中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章