14_自定义ItemDecoration实现qq好友列表分组效果

14_自定义ItemDecoration实现qq好友列表分组效果

一.先上效果

在这里插入图片描述

二.RecyclerView实现简单分组

RecyclerView比较常规的用法,显示多item布局,直接上代码:

public class SectionListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<SectionItem> datas;
    private Context context;
    public static final int ITEM_TYPE_SECTION = 1000;
    public static final int ITEM_TYPE_NORMAL = 1001;

    public SectionListAdapter(Context context, List<SectionItem> datas) {
        this.context = context;
        this.datas = datas;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder holder = null;
        if (viewType == ITEM_TYPE_SECTION) {
            View itemView = LayoutInflater.from(context).inflate(R.layout.section_item, parent, false);
            holder = new SectionItemHolder(itemView);
        } else {
            View itemView = LayoutInflater.from(context).inflate(R.layout.group_item, parent, false);
            holder = new NormalItemHolder(itemView);
        }
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        SectionItem sectionItem = datas.get(position);
        if(getItemViewType(position) == ITEM_TYPE_SECTION) {
            SectionItemHolder sectionItemHolder = (SectionItemHolder) holder;
            sectionItemHolder.tvSectionName.setText(sectionItem.name);
            sectionItemHolder.tvButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(context, sectionItem.name, Toast.LENGTH_SHORT).show();
                }
            });
        } else {
            NormalItemHolder normalItemHolder = (NormalItemHolder) holder;
            normalItemHolder.tvGroupName.setText(sectionItem.name);
        }
    }

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

    @Override
    public int getItemViewType(int position) {
        if(datas != null && datas.size() > position) {
            if(datas.get(position).isSection) {
                return ITEM_TYPE_SECTION;
            }
        }
        return ITEM_TYPE_NORMAL;
    }

    protected static class SectionItemHolder extends RecyclerView.ViewHolder {

        private Button tvButton;
        private TextView tvSectionName;

        public SectionItemHolder(@NonNull View itemView) {
            super(itemView);
            tvSectionName = itemView.findViewById(R.id.tv_section_item_name);
            tvButton = itemView.findViewById(R.id.tv_button);
        }

    }

    protected static class NormalItemHolder extends RecyclerView.ViewHolder {

        TextView tvGroupName;

        public NormalItemHolder(@NonNull View itemView) {
            super(itemView);
            tvGroupName = itemView.findViewById(R.id.tv_group_item_name);
        }

    }
}
public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding dataBinding;
    private List<SectionItem> datas = new ArrayList<>();
    private SectionListAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        dataBinding =  DataBindingUtil.setContentView(this, R.layout.activity_main);
        dataBinding.groupList.setLayoutManager(new LinearLayoutManager(this));

        datas.add(new SectionItem("group1", "group1", true));
        for(int i=0; i < 10; i++) {
            datas.add(new SectionItem("item" + i, "group1", false));
        }

        datas.add(new SectionItem("group2", "group2", true));
        for(int i=0; i < 5; i++) {
            datas.add(new SectionItem("item" + i, "group2", false));
        }

        datas.add(new SectionItem("group3", "group3", true));
        for(int i=0; i < 20; i++) {
            datas.add(new SectionItem("item" + i, "group3", false));
        }

        mAdapter = new SectionListAdapter(this, datas);
        dataBinding.groupList.setAdapter(mAdapter);
    }
}

在这里插入图片描述

三.自定义ItemDecoration实现吸顶

在ItemDecoration的实现方法中,有两个方法是用于绘制的,一个是onDraw,一个是onDrawOver,onDraw方法中绘制的内容如果和RecyclerView的item有重叠,重叠区域会被item覆盖,常用于在RecyclerView的item周围绘制分割,而在onDrawOver方法中绘制的内容会覆盖在RecyclerView的item之上,因此我们需要在onDrawOver中进行绘制,才能实现吸顶。具体实现步骤如下:

  • 重写ItemDecoration的getItemOffsets方法,在getItemOffsets方法中直接调用adapter的createViewHolder创建一个新的分组的ViewHolder,后续会提供给onDrawOver进行绘制,并缓存RecyclerView中所有已经创建的分组ViewHolder
public class SectionDecoration extends RecyclerView.ItemDecoration {

    Map<Integer, RecyclerView.ViewHolder> sections = new LinkedHashMap<>();
    private RecyclerView.ViewHolder overViewHolder = null;

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        RecyclerView.Adapter adapter = parent.getAdapter();
        int position = parent.getChildLayoutPosition(view);
        if(adapter != null) {
            if(overViewHolder == null) {
                overViewHolder = adapter.createViewHolder(null, SectionListAdapter.ITEM_TYPE_SECTION);
            }
            if(adapter.getItemViewType(position) == SectionListAdapter.ITEM_TYPE_SECTION) {
                if(sections.get(position) == null) {
                    sections.put(position, parent.getChildViewHolder(view));
                }
            }
        }
    }
  
}
  • 在绘制吸顶之前,我们需要先找到需要吸顶的目标ViewHolder,目标ViewHolder有多大,overViewHolder就有多大,而overViewHolder的位置始终是在RecyclerView的最顶部,因此问题的关键在于怎么寻找目标ViewHolder的position,我们通过一张图来说明怎么寻找目标ViewHolder的position,找到目标ViewHolder的position之后,从缓存中取出Viewholder即为目标ViewHolder。

在这里插入图片描述

public class SectionDecoration extends RecyclerView.ItemDecoration {

    Map<Integer, RecyclerView.ViewHolder> sections = new LinkedHashMap<>();
    private RecyclerView.ViewHolder overViewHolder = null;

    ...

    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        RecyclerView.Adapter adapter = parent.getAdapter();
        if(adapter != null) {
            int count = parent.getChildCount();
            int firstVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
            int position = firstVisibleItemPosition;
            if (position > 0) {
                for (int i = 0; i < count; i++) {
                    View child = parent.getChildAt(i);
                    position = parent.getChildLayoutPosition(child);
                    if (child.getBottom() >= (parent.getPaddingTop()) && adapter.getItemViewType(position + 1) == SectionListAdapter.ITEM_TYPE_SECTION) {
                        break;
                    }
                }
            }

            int sectionPosition = -1;
            for (int i = 0; i <= position; i++) {
                if(adapter.getItemViewType(i) == SectionListAdapter.ITEM_TYPE_SECTION) {
                    sectionPosition = i;
                }
            }

            RecyclerView.ViewHolder targetViewHolder = null;
            if(sectionPosition != -1) {
                targetViewHolder = sections.get(sectionPosition);
            }
        }
    }
}
  • 绘制overViewHolder
@Override
public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.onDrawOver(canvas, parent, state);
    RecyclerView.Adapter adapter = parent.getAdapter();
    if(adapter != null) {
        int count = parent.getChildCount();
        int firstVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
        int position = firstVisibleItemPosition;
        if (position > 0) {
            for (int i = 0; i < count; i++) {
                View child = parent.getChildAt(i);
                position = parent.getChildLayoutPosition(child);
                if (adapter.getItemViewType(position + 1) == SectionListAdapter.ITEM_TYPE_SECTION) {
                    break;
                }
            }
        }

        int sectionPosition = -1;
        for (int i = 0; i <= position; i++) {
            if(adapter.getItemViewType(i) == SectionListAdapter.ITEM_TYPE_SECTION) {
              	sectionPosition = i;
            }
        }

        RecyclerView.ViewHolder targetViewHolder = null;
        if(sectionPosition != -1) {
          	targetViewHolder = sections.get(sectionPosition);
        }

        if(targetViewHolder != null) {
            int width = targetViewHolder.itemView.getMeasuredWidth();
            int height = targetViewHolder.itemView.getMeasuredHeight();
            overViewHolder.itemView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

            int top = parent.getPaddingTop();
            int left = parent.getPaddingLeft();
            overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);

            drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);
        }
    }
}

private void drawOverViewHolder(Canvas canvas, View overSectionView, RecyclerView parent, int left, int top) {
    canvas.save();
    canvas.clipRect(left, parent.getPaddingTop(), left + overSectionView.getMeasuredWidth(), top + overSectionView.getMeasuredHeight());
    canvas.translate(left, top);
    overSectionView.draw(canvas);
    canvas.restore();
}
  • 我们先把它添加到RecyclerView中看下效果:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    dataBinding =  DataBindingUtil.setContentView(this, R.layout.activity_main);
    dataBinding.groupList.setLayoutManager(new LinearLayoutManager(this));
    dataBinding.groupList.addItemDecoration(new SectionDecoration());

    ...
}

在这里插入图片描述

可以看到吸顶的ViewHolder已经被绘制出来了,但是吸顶item的数据没有动态变化,并且下一个需要吸顶的ViewHolder滑动到当前吸顶的ViewHolder位置时,不会往上推,并且吸顶ViewHolder上的按钮是不可点击状态。

  • 动态改变吸顶ViewHolder的数据
adapter.onBindViewHolder(overViewHolder, sectionPosition);

int width = targetViewHolder.itemView.getMeasuredWidth();
int height = targetViewHolder.itemView.getMeasuredHeight();
overViewHolder.itemView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

在这里插入图片描述

  • 什么时候触发上推

找到下一个需要吸顶的ViewHolder的前一个ViewHolder的itemView,如果没找到,那么说明不需要上推,如果找到了:

if(itemView.getBottom() - recyclerView.getPaddingTop() <= 当前吸顶item.getMeasuredHeight()) {

​ 开始上推

}

在这里插入图片描述

int top = parent.getPaddingTop();
int left = parent.getPaddingLeft();

RecyclerView.ViewHolder sectionTailHolder = null;
if(position > 0) {
  	sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
} else {
    if(adapter.getItemViewType(1) == SectionListAdapter.ITEM_TYPE_SECTION) {
        sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
    }
}
if(sectionTailHolder != null && (sectionTailHolder.itemView.getBottom() - top) <= height) {
  	//上推
}

overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);
drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);
  • 实现吸顶ViewHolder上推

在这里插入图片描述

int top = parent.getPaddingTop();
int left = parent.getPaddingLeft();

RecyclerView.ViewHolder sectionTailHolder = null;
if(position > 0) {
  	sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
} else {
    if(adapter.getItemViewType(1) == SectionListAdapter.ITEM_TYPE_SECTION) {
        sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
    }
}
if(sectionTailHolder != null && (sectionTailHolder.itemView.getBottom() - top) <= height) {
  	//上推
  	top = sectionTailHolder.itemView.getBottom() - overViewHolder.itemView.getMeasuredHeight();
}

overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);
drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);

在这里插入图片描述

  • 处理点击事件

吸顶的itemView中的button不能点击的原因在于,吸顶的itemView只是被摆放和绘制到了屏幕上,并没有attachToWindow,而View的点击事件是从window开始分发的,所以我们可以考虑把吸顶的itemView添加到一个已经attachToWindow的ViewGroup中,我们把它添加到RecyclerView的父View中即可。

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);
    RecyclerView.Adapter adapter = parent.getAdapter();
    int position = parent.getChildLayoutPosition(view);
    if(adapter != null) {
        if(overViewHolder == null) {
            ViewGroup recyclerViewParent = (ViewGroup) parent.getParent();
            overViewHolder = adapter.createViewHolder(recyclerViewParent, SectionListAdapter.ITEM_TYPE_SECTION);
            overViewHolder.itemView.setClipBounds(new Rect(0, 0, 0, 0));
            recyclerViewParent.addView(overViewHolder.itemView);
        }
        if(adapter.getItemViewType(position) == SectionListAdapter.ITEM_TYPE_SECTION) {
            if(sections.get(position) == null) {
                sections.put(position, parent.getChildViewHolder(view));
            }
        }
    }
}

在这里插入图片描述

四.完整代码
public class SectionDecoration extends RecyclerView.ItemDecoration {

    Map<Integer, RecyclerView.ViewHolder> sections = new LinkedHashMap<>();
    private RecyclerView.ViewHolder overViewHolder = null;

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        RecyclerView.Adapter adapter = parent.getAdapter();
        int position = parent.getChildLayoutPosition(view);
        if(adapter != null) {
            if(overViewHolder == null) {
                ViewGroup recyclerViewParent = (ViewGroup) parent.getParent();
                overViewHolder = adapter.createViewHolder(recyclerViewParent, SectionListAdapter.ITEM_TYPE_SECTION);
                overViewHolder.itemView.setClipBounds(new Rect(0, 0, 0, 0));
                recyclerViewParent.addView(overViewHolder.itemView);
            }
            if(adapter.getItemViewType(position) == SectionListAdapter.ITEM_TYPE_SECTION) {
                if(sections.get(position) == null) {
                    sections.put(position, parent.getChildViewHolder(view));
                }
            }
        }
    }

    @Override
    public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(canvas, parent, state);
        RecyclerView.Adapter adapter = parent.getAdapter();
        if(adapter != null) {
            int count = parent.getChildCount();
            int firstVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
            int position = firstVisibleItemPosition;
            if (position > 0) {
                for (int i = 0; i < count; i++) {
                    View child = parent.getChildAt(i);
                    position = parent.getChildLayoutPosition(child);
                    if (child.getBottom() >= (parent.getPaddingTop()) && adapter.getItemViewType(position + 1) == SectionListAdapter.ITEM_TYPE_SECTION) {
                        break;
                    }
                }
            }

            int sectionPosition = -1;
            for (int i = 0; i <= position; i++) {
                if(adapter.getItemViewType(i) == SectionListAdapter.ITEM_TYPE_SECTION) {
                    sectionPosition = i;
                }
            }

            RecyclerView.ViewHolder targetViewHolder = null;
            if(sectionPosition != -1) {
                targetViewHolder = sections.get(sectionPosition);
            }

            if(targetViewHolder != null) {
                adapter.onBindViewHolder(overViewHolder, sectionPosition);
                int width = targetViewHolder.itemView.getMeasuredWidth();
                int height = targetViewHolder.itemView.getMeasuredHeight();
                overViewHolder.itemView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

                int top = parent.getPaddingTop();
                int left = parent.getPaddingLeft();

                RecyclerView.ViewHolder sectionTailHolder = null;
                if(position > 0) {
                    sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
                } else {
				    if(adapter.getItemViewType(1) == SectionListAdapter.ITEM_TYPE_SECTION) {
				        sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
				    }
				}
                if(sectionTailHolder != null && (sectionTailHolder.itemView.getBottom() - top) <= height) {
                    //上推
                    top = sectionTailHolder.itemView.getBottom() - overViewHolder.itemView.getMeasuredHeight();
                }

                overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);
                drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);
            }
        }
    }

    private void drawOverViewHolder(Canvas canvas, View overSectionView, RecyclerView parent, int left, int top) {
        canvas.save();
        canvas.clipRect(left, parent.getPaddingTop(), left + overSectionView.getMeasuredWidth(), top + overSectionView.getMeasuredHeight());
        canvas.translate(left, top);
        overSectionView.draw(canvas);
        canvas.restore();
    }
}
五.总结

RecyclerView实现item吸顶的突破口在于ItemDecoration的onDrawOver方法,至于怎么去绘制吸顶item以及处理点击事件,就是仁者见仁、智者见智了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值