700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 14_自定义ItemDecoration实现qq好友列表分组效果

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

时间:2020-12-11 21:29:54

相关推荐

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@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

@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 (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中看下效果:

@Overrideprotected 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中即可。

@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));}}}}

四.完整代码

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以及处理点击事件,就是仁者见仁、智者见智了。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。