RecyclerView+GridView分组效果

时间:2021-06-11 15:10:57

首先,不上图就是耍流氓!
RecyclerView+GridView分组效果

项目中要做图中类似的效果。

方案一:
RecyclerView嵌套RecyclerView
缺点:页面卡顿
结果:放弃该方案

方案二:
重写适配器
缺点:需要做数据处理(可接受)
优点:顺滑无比,不用嵌套,不用修改recyclerview
结果:使用该方案
使用过程中,愈发感觉RecyclerView的强大,服,是真服!
借鉴了github的开源项目,在此对作者表示感谢。
地址:SectionedRecyclerView

开始使用
RecyclerView照常写就好,这里需要注意的Adapter。先说一下整个设计思路:在adapter 设置分组数getSectionCount , 设置每一组的个数getItemCountForSection,根据这个关键数据,把header和footer视为一个单项,计算出总共的单项数目,然后根据不同的类型,header,footer,item,让GridLayoutManager的getSpanSize不同,即可达到效果。

连个关键
一:在于记录每一个单项(包括头部和底部单项的 )详细属性,

1,是头部,正常,还是底部
2,在哪一个分组,又在分组的第一个位置

二:当是header或者footer是,将GridLayoutManger的spanSize设为全屏

 GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 5);
        SectionedSpanSizeLookup lookup = new SectionedSpanSizeLookup(mAdapter, layoutManager);
        layoutManager.setSpanSizeLookup(lookup);

来看下SectionedSpanSizeLookup的实现,这个就是用来处理每一项显示的宽度大小的,神奇的东西

public class SectionedSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {

    protected SectionedRecyclerViewAdapter<?, ?, ?> adapter = null;
    protected GridLayoutManager layoutManager = null;

    public SectionedSpanSizeLookup(SectionedRecyclerViewAdapter<?, ?, ?> adapter, GridLayoutManager layoutManager) {
        this.adapter = adapter;
        this.layoutManager = layoutManager;
    }

    @Override
    public int getSpanSize(int position) {

        if(adapter.isSectionHeaderPosition(position) || adapter.isSectionFooterPosition(position)){
            return layoutManager.getSpanCount();
        }else{
            return 1;
        }

    }
}

下面来看一下Adapter的实现,

public class RoomPreAdapter2 extends SimpleSectionedAdapter2<RoomPreAdapter2.RoomHodler> {
    private Context context;

    @Override
    protected String getSectionHeaderTitle(int section) {
        return section + "#";
    }

    /** * 获取分组数 * @return */
    @Override
    protected int getSectionCount() {
        return 10;
    }

    /** * 获取指定组的个数 * @param section * @return */
    @Override
    protected int getItemCountForSection(int section) {
        return section + 6;
    }

    @Override
    protected RoomHodler onCreateItemViewHolder(ViewGroup parent, int viewType) {
        context = parent.getContext();
        View itemView = LayoutInflater.from(context).inflate(R.layout.item_room, parent, false);
        return new RoomHodler(itemView);
    }

    @Override
    protected void onBindItemViewHolder(RoomHodler holder, int section, final int position) {
        holder.rb_room.setText(section + "-" + position);

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, "点了" + position, Toast.LENGTH_SHORT).show();
            }
        });
        holder.rb_room.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Toast.makeText(context, "选了" + position, Toast.LENGTH_SHORT).show();
            }
        });
    }

    class RoomHodler extends RecyclerView.ViewHolder {
        RadioButton rb_room;

        public RoomHodler(View itemView) {
            super(itemView);
            rb_room = (RadioButton) itemView.findViewById(R.id.rb_room);
        }
    }
}

这个没什么讲的,就是普通的用法,注意两个方法,getSectionCount()和getItemCountForSection(),还有一个方法getSectionHeaderTitle()用于设置显示的分组名,显然父类中已经进行了完美的封装,下面我们来看父类

public abstract class SimpleSectionedAdapter2<VH extends RecyclerView.ViewHolder> extends SectionedRecyclerViewAdapter<HeaderViewHolder,
        VH, RecyclerView.ViewHolder> {

    /** * 是否显示每一个分组的底部 * @param section * @return */
    @Override
    protected boolean hasFooterInSection(int section) {
        return true;
    }

    /* 创建头部header的ViewHolder */
    @Override
    protected HeaderViewHolder onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view = inflater.inflate(getLayoutResource(), parent, false);
        HeaderViewHolder holder = new HeaderViewHolder(view, getTitleTextID());
        return holder;
    }

    /* 创建底部footer的ViewHolder */
    @Override
    protected RecyclerView.ViewHolder onCreateSectionFooterViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view = inflater.inflate(getFooterLayoutResource(), parent, false);
        FooterViewHolder holder = new FooterViewHolder(view);

        return holder;
    }

    private int getFooterLayoutResource() {
        return R.layout.item_room_footer;
    }

    @Override
    protected void onBindSectionHeaderViewHolder(HeaderViewHolder holder, int section) {
        String title = getSectionHeaderTitle(section);
        holder.render(title);
    }

    @Override
    protected void onBindSectionFooterViewHolder(RecyclerView.ViewHolder holder, int section) {

    }

    /** * Provides a layout identifier for the header. Override it to change the appearance of the * header view. */
    protected
    @LayoutRes
    int getLayoutResource() {
        return R.layout.item_room_header;
    }

    /** * Provides the identifier of the TextView to render the section header title. Override it if * you provide a custom layout for a header. */
    protected
    @IdRes
    int getTitleTextID() {
        return R.id.tv_group_name;
    }

    /** * Returns the title for a given section */
    protected abstract String getSectionHeaderTitle(int section);

    /** * 底部Footer的ViewHolder */
    class FooterViewHolder extends RecyclerView.ViewHolder{
        public FooterViewHolder(View itemView) {
            super(itemView);
        }
    }
}

而这个父类这是封装了header和footer的显示,至于header和footer是如何判断?以及如何 根据分组数和每个分组的个数 绘制出我们要的效果?这些问题还得再找父类,找的爷爷辈,别急,继续看,

public abstract class SectionedRecyclerViewAdapter<H extends RecyclerView.ViewHolder, VH extends RecyclerView.ViewHolder, F extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    protected static final int TYPE_SECTION_HEADER = -1;
    protected static final int TYPE_SECTION_FOOTER = -2;
    protected static final int TYPE_ITEM = -3;

    //用四个数组,对应保存每一个单项(包括header和footer)的四个属性
    //1.哪个分组;2.在该组的位置;3.是否是头部;4.是否是底部;
    private int[] sectionForPosition = null;
    private int[] positionWithinSection = null;
    private boolean[] isHeader = null;
    private boolean[] isFooter = null;
    private int count = 0;

    public SectionedRecyclerViewAdapter() {
        super();
        registerAdapterDataObserver(new SectionDataObserver());
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        setupIndices();
    }

    /** * Returns the sum of number of items for each section plus headers and footers if they * are provided. */
    @Override
    public int getItemCount() {
        return count;
    }

    private void setupIndices(){
        count = countItems();
        allocateAuxiliaryArrays(count);
        precomputeIndices();
    }

    private int countItems() {
        int count = 0;
        int sections = getSectionCount();
        //计算总个数
        for(int i = 0; i < sections; i++){
            count += 1 + getItemCountForSection(i) + (hasFooterInSection(i) ? 1 : 0);
        }
        return count;
    }
    //为每一个单项,设置四大属性
    private void precomputeIndices(){
        int sections = getSectionCount();
        int index = 0;

        for(int i = 0; i < sections; i++){
            setPrecomputedItem(index, true, false, i, 0);
            index++;

            for(int j = 0; j < getItemCountForSection(i); j++){
                setPrecomputedItem(index, false, false, i, j);
                index++;
            }

            if(hasFooterInSection(i)){
                setPrecomputedItem(index, false, true, i, 0);
                index++;
            }
        }
    }

    private void allocateAuxiliaryArrays(int count) {
        sectionForPosition = new int[count];
        positionWithinSection = new int[count];
        isHeader = new boolean[count];
        isFooter = new boolean[count];
    }

    private void setPrecomputedItem(int index, boolean isHeader, boolean isFooter, int section, int position) {
        this.isHeader[index] = isHeader;
        this.isFooter[index] = isFooter;
        sectionForPosition[index] = section;
        positionWithinSection[index] = position;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder viewHolder;

        if(isSectionHeaderViewType(viewType)){
            viewHolder = onCreateSectionHeaderViewHolder(parent, viewType);
        }else if(isSectionFooterViewType(viewType)){
            viewHolder = onCreateSectionFooterViewHolder(parent, viewType);
        }else{
            viewHolder = onCreateItemViewHolder(parent, viewType);
        }

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int section = sectionForPosition[position];
        int index = positionWithinSection[position];

        if(isSectionHeaderPosition(position)){
            onBindSectionHeaderViewHolder((H) holder, section);
        }else if(isSectionFooterPosition(position)){
            onBindSectionFooterViewHolder((F) holder, section);
        }else{
            onBindItemViewHolder((VH) holder, section, index);
        }

    }

    @Override
    public int getItemViewType(int position) {

        if(sectionForPosition == null){
            setupIndices();
        }

        int section = sectionForPosition[position];
        int index = positionWithinSection[position];

        if(isSectionHeaderPosition(position)){
            return getSectionHeaderViewType(section);
        }else if(isSectionFooterPosition(position)){
            return getSectionFooterViewType(section);
        }else{
            return getSectionItemViewType(section, index);
        }

    }

    protected int getSectionHeaderViewType(int section){
        return TYPE_SECTION_HEADER;
    }

    protected int getSectionFooterViewType(int section){
        return TYPE_SECTION_FOOTER;
    }

    protected int getSectionItemViewType(int section, int position){
        return TYPE_ITEM;
    }

    /** * Returns true if the argument position corresponds to a header */
    public boolean isSectionHeaderPosition(int position){
        if(isHeader == null){
            setupIndices();
        }
        return isHeader[position];
    }

    /** * Returns true if the argument position corresponds to a footer */
    public boolean isSectionFooterPosition(int position){
        if(isFooter == null){
            setupIndices();
        }
        return isFooter[position];
    }

    protected boolean isSectionHeaderViewType(int viewType){
        return viewType == TYPE_SECTION_HEADER;
    }

    protected boolean isSectionFooterViewType(int viewType){
        return viewType == TYPE_SECTION_FOOTER;
    }

    /** * Returns the number of sections in the RecyclerView */
    protected abstract int getSectionCount();

    /** * Returns the number of items for a given section */
    protected abstract int getItemCountForSection(int section);

    /** * Returns true if a given section should have a footer */
    protected abstract boolean hasFooterInSection(int section);

    /** * Creates a ViewHolder of class H for a Header */
    protected abstract H  onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType);

    /** * Creates a ViewHolder of class F for a Footer */
    protected abstract F  onCreateSectionFooterViewHolder(ViewGroup parent, int viewType);

    /** * Creates a ViewHolder of class VH for an Item */
    protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);

    /** * Binds data to the header view of a given section */
    protected abstract void onBindSectionHeaderViewHolder(H holder, int section);

    /** * Binds data to the footer view of a given section */
    protected abstract void onBindSectionFooterViewHolder(F holder, int section);

    /** * Binds data to the item view for a given position within a section */
    protected abstract void onBindItemViewHolder(VH holder, int section, int position);

    class SectionDataObserver extends RecyclerView.AdapterDataObserver{
        @Override
        public void onChanged() {
            setupIndices();
        }

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

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

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

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