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@Overridepublic 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;}@Overridepublic 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() {@Overridepublic void onClick(View v) {Toast.makeText(context, sectionItem.name, Toast.LENGTH_SHORT).show();}});} else {NormalItemHolder normalItemHolder = (NormalItemHolder) holder;normalItemHolder.tvGroupName.setText(sectionItem.name);}}@Overridepublic int getItemCount() {return datas != null ? datas.size():0;}@Overridepublic 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;@Overrideprotected 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;@Overridepublic 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;...@Overridepublic 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;@Overridepublic 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));}}}}@Overridepublic 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以及处理点击事件,就是仁者见仁、智者见智了。

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

  1. android 仿qq好友列表分组效果及联系人分组效果

     历史记录仿QQ好友列表的动态效果 以及联系人的分组效果 QQ朋友分组的功能做的不错,大家都很认可,那么到底他的分组并且滑动的时候,标题能停留在顶部是如何实现的呢?今天从网上搜索了一下资料,自己运行了 ...

  2. android 仿qq好友动态,Android UI仿QQ好友列表分组悬浮效果

    本文实例为大家分享了Android UI仿QQ好友列表分组悬浮效果的具体代码,供大家参考,具体内容如下 楼主是在平板上測试的.图片略微有点大,大家看看效果就好 接下来贴源代码: PinnedHeade ...

  3. 仿QQ好友列表分组折叠效果

    最近要一个类似QQ好友列表分组折叠效果,经过网友提醒应该使用ExpandableListView,因为其就集成了这个功能,我到网上随便找了文章一看,果然如此,因为工作需要和兴趣的推动,下班做完事后决定 ...

  4. 基于Qt的类似QQ好友列表抽屉效果的实现

    转载地址: http://blog.csdn.net/shuideyidi/article/details/30619167 前段时间在忙毕业设计,所以一直没有更新博客.今天答辩完以后,将对我的毕业设 ...

  5. Android UI视图效果篇之仿QQ好友列表分组悬浮PinnedHeaderExpandableListView

    楼主是在平板上测试的,图片稍微有点大,大家看看效果就好 接下来贴源码: PinnedHeaderExpandableListView.java 要注意的是 在 onGroupClick方法中paren ...

  6. java如何实现qq分组_Android仿QQ好友列表分组实现增删改及持久化

    Android自带的控件ExpandableListView实现了分组列表功能,本案例在此基础上进行优化,为此控件添加增删改分组及子项的功能,以及列表数据的持久化. Demo实现效果: GroupLi ...

  7. Android仿QQ好友列表分组实现增删改及持久化

    Android自带的控件ExpandableListView实现了分组列表功能,本案例在此基础上进行优化,为此控件添加增删改分组及子项的功能,以及列表数据的持久化. Demo实现效果:     Dem ...

  8. 使用RecyclerView 简单实现QQ好友列表展开效果

    最近自己想捣鼓个社交类的app,所以想了解一下QQ列表的实现.对于这样的展开效果,我们很容易想到使用ExpandableListView类,当然我也是,但是我在网上看到有人用ListView套用lis ...

  9. C# 高仿腾讯QQ (实现QQ好友列表-基于ListBox的绘法)

    C#实现QQ好友列表,一直是我网上百搜却不得其解的控件,几乎找不到相关的信息,而找到的也不是我想要的那种.不过,曾在CSDN上看过到过网上一位大牛用C++实现QQ好友列表这种效果,当然,对于我这个C+ ...

最新文章

  1. 关于数据中台的深度思考与总结(超级干货)
  2. oracle 删除空间不足,oracle表空间扩容、创建、删除(解决表空间不足问题)
  3. java面试题36 已知如下的命令执行 java MyTest a b c 请问哪个语句是正确的? ( )
  4. git pull时冲突的几种解决方式
  5. Windows平台基于RTMP实现一对一互动直播
  6. 改行了 写一篇 PLC 相关的 西门子 S7 300/400 控制器
  7. nuxt vue ssr实现
  8. html特殊文字符号
  9. Ubuntu不能挂载移动硬盘问题Error mounting /dev/sda1 at /media/XXXX: Command-line `mount -t ntfs -o
  10. Linux高级程序设计第三版电子版PDF
  11. 计算机科学与技术b类大学名单,双一流a类大学和b类大学名单及学科
  12. IE打印A4,表格缩小问题剖析
  13. python+mysql逆向_Python js逆向 爬取X天下数据,好好看,好好学
  14. vue首次赋值不触发watch及watch和computed的区别
  15. 什么是三次握手和四次握手
  16. springcloud-eureka启动报错,提示The following method did not exist: org.springframework.boot.actuate.health
  17. (六)python共享代码步骤
  18. 频繁通过win32api的createfile函数打开文件句柄导致内存泄漏
  19. fama matlab源码_用matlab程序做Fama-MacBeth回归的代码
  20. samba服务器搭建指南

热门文章

  1. 百瑞BARROT BR2262e蓝牙模块的使用
  2. SVG 入门指南(看完,对SVG结构不在陌生)
  3. html渐变色css3渐变,css3渐变
  4. 国际移动设备识别码IMEI
  5. 快速掌握SAP BDC数据导入
  6. EZ-CUBE调试设置
  7. bilibili直播间利用python爬虫自动发送弹幕
  8. 李开复给中国大学生的第六封信—选择的智慧
  9. 课程1 谈论你喜欢的音乐
  10. uni-app的生命周期说明及平台差异性说明