前言
项目里需要实现个可折叠,可展开的的二级列表,首先想到了用ExpandListView
去实现,ExpandListView
是继承ListView的。由于项目里所有列表都用Recycleview,再加上本身对于Recyclerview情有独钟,懂的都懂,就想着试试用它实现吧。
效果图
实现
网上找到了ExpandableRecyclerView这个库,读了一遍源码,然后将BaseExpandableRecyclerViewAdapter拷贝到项目中,简单能够显示出来出来,因为我们的业务需求需要实现可选择功能,刚好作者也封装好了BaseCheckableExpandableRecyclerViewAdapter这个类,我就也直接拿来用了。这里就不贴这两个类的代码了,具体可以去自己看。
然而选中的时候业务需求要改变数据库中子元素数据的check状态,这一改变就出问题了。。
java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
at com.khb.mpcms.ui.trait.TraitAdapter.getGroupItem(TraitAdapter.java:52)
at com.khb.mpcms.ui.trait.TraitAdapter.getGroupItem(TraitAdapter.java:23)
at com.khb.mpcms.ui.trait.BaseExpandableRecyclerViewAdapter.getItemViewType(BaseExpandableRecyclerViewAdapter.java:262)
数组越界,苦逼。。追踪代码找原因吧,最后定位是在getItemViewType里调用translateToDoubleIndex
protected final int[] translateToDoubleIndex(int adapterPosition) {
if (mHeaderViewProducer != null) {
adapterPosition--;
}
final int[] result = new int[]{-1, -1};
final int groupCount = getGroupCount();
int adaptePositionCursor = 0;
for (int groupCursor = 0; groupCursor < groupCount; groupCursor++) {
if (adaptePositionCursor == adapterPosition) {
result[0] = groupCursor;
result[1] = -1;
break;
}
GroupBean groupBean = getGroupItem(groupCursor);
if (mExpandGroupSet.contains(groupBean)) {
final int childCount = groupBean.getChildCount();
final int offset = adapterPosition - adaptePositionCursor;
if (childCount >= offset) {
result[0] = groupCursor;
result[1] = offset - 1;
break;
}
adaptePositionCursor += childCount;
}
adaptePositionCursor++;
}
return result;
}
由于result返回坐标为{-1,-1}导致的数组越界,debug了下发现返回-1的原因是组元素在展开的时候mExpandGroupSet的contains方法依然返回false。草,于是我将mExpandGroupSet的类型HashSet换成了ArrayList。然后再试就没啥问题了。顺便我将BaseCheckableExpandableRecyclerViewAdapter里的mCheckedSet也换了。
贴出代码BaseExpandableRecyclerViewAdapter,里面有我看代码时加的一些注释注释
package com.khb.mpcms.ui.trait;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* 创建时间:2020/8/13
* 编写人:kanghb
* 功能描述:
*/
public abstract class BaseExpandableRecyclerViewAdapter<GroupBean extends BaseExpandableRecyclerViewAdapter.BaseGroupBean<ChildBean>,
ChildBean,
GroupViewHolder extends BaseExpandableRecyclerViewAdapter.BaseGroupViewHolder,
ChildViewHolder extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "BaseExpandableRecyclerV";
private static final Object EXPAND_PAYLOAD = new Object();
private static final int TYPE_EMPTY = ViewProducer.VIEW_TYPE_EMPTY;
private static final int TYPE_HEADER = ViewProducer.VIEW_TYPE_HEADER;
private static final int TYPE_GROUP = ViewProducer.VIEW_TYPE_EMPTY >> 2;
private static final int TYPE_CHILD = ViewProducer.VIEW_TYPE_EMPTY >> 3;
private static final int TYPE_MASK = TYPE_GROUP | TYPE_CHILD | TYPE_EMPTY | TYPE_HEADER;
//展开的组
private List<GroupBean> mExpandGroupSet;
private ExpandableRecyclerViewOnClickListener<GroupBean, ChildBean> mListener;
private boolean mIsEmpty;
private boolean mShowHeaderViewWhenEmpty;
private ViewProducer mEmptyViewProducer;
private ViewProducer mHeaderViewProducer;
public BaseExpandableRecyclerViewAdapter() {
mExpandGroupSet = new ArrayList<>();
registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
// after notifyDataSetChange(),clear outdated list
List<GroupBean> retainItem = new ArrayList<>();
for (int i = 0; i < getGroupCount(); i++) {
GroupBean groupBean = getGroupItem(i);
if (mExpandGroupSet.contains(groupBean)) {
retainItem.add(groupBean);
}
}
mExpandGroupSet.clear();
mExpandGroupSet.addAll(retainItem);
}
});
}
/**
* get group count
*
* @return group count
*/
abstract public int getGroupCount();
/**
* get groupItem related to GroupCount
*
* @param groupIndex the index of group item in group list
* @return related GroupBean
*/
abstract public GroupBean getGroupItem(int groupIndex);
protected int getGroupType(GroupBean groupBean) {
return 0;
}
/**
* create {@link GroupViewHolder} for group item
*
* @param parent
* @return
*/
abstract public GroupViewHolder onCreateGroupViewHolder(ViewGroup parent, int groupViewType);
/**
* bind {@link GroupViewHolder}
*
* @param holder
* @param groupBean
* @param isExpand
*/
abstract public void onBindGroupViewHolder(GroupViewHolder holder, GroupBean groupBean, boolean isExpand);
/**
* bind {@link GroupViewHolder} with payload , used to invalidate partially
*
* @param holder
* @param groupBean
* @param isExpand
* @param payload
*/
protected void onBindGroupViewHolder(GroupViewHolder holder, GroupBean groupBean, boolean isExpand, List<Object> payload) {
onBindGroupViewHolder(holder, groupBean, isExpand);
}
protected int getChildType(GroupBean groupBean, ChildBean childBean) {
return 0;
}
/**
* create {@link ChildViewHolder} for child item
*
* @param parent
* @return
*/
abstract public ChildViewHolder onCreateChildViewHolder(ViewGroup parent, int childViewType);
/**
* bind {@link ChildViewHolder}
*
* @param holder
* @param groupBean
* @param childBean
*/
abstract public void onBindChildViewHolder(ChildViewHolder holder, GroupBean groupBean, ChildBean childBean);
/**
* bind {@link ChildViewHolder} with payload , used to invalidate partially
*
* @param holder
* @param groupBean
* @param childBean
* @param payload
*/
protected void onBindChildViewHolder(ChildViewHolder holder, GroupBean groupBean, ChildBean childBean, List<Object> payload) {
onBindChildViewHolder(holder, groupBean, childBean);
}
public void setEmptyViewProducer(ViewProducer emptyViewProducer) {
if (mEmptyViewProducer != emptyViewProducer) {
mEmptyViewProducer = emptyViewProducer;
if (mIsEmpty) {
notifyDataSetChanged();
}
}
}
public void setHeaderViewProducer(ViewProducer headerViewProducer, boolean showWhenEmpty) {
mShowHeaderViewWhenEmpty = showWhenEmpty;
if (mHeaderViewProducer != headerViewProducer) {
mHeaderViewProducer = headerViewProducer;
notifyDataSetChanged();
}
}
public final void setListener(ExpandableRecyclerViewOnClickListener<GroupBean, ChildBean> listener) {
mListener = listener;
}
public final boolean isGroupExpanding(GroupBean groupBean) {
return mExpandGroupSet.contains(groupBean);
}
public final boolean expandGroup(GroupBean groupBean) {
if (groupBean.isExpandable() && !isGroupExpanding(groupBean)) {
mExpandGroupSet.add(groupBean);
//获取到groupbean的实际position
final int position = getAdapterPosition(getGroupIndex(groupBean));
notifyItemRangeInserted(position + 1, groupBean.getChildCount());
notifyItemChanged(position, EXPAND_PAYLOAD);
return true;
}
return false;
}
public final void foldAll() {
Iterator<GroupBean> iter = mExpandGroupSet.iterator();
while (iter.hasNext()) {
GroupBean groupBean = iter.next();
final int position = getAdapterPosition(getGroupIndex(groupBean));
notifyItemRangeRemoved(position + 1, groupBean.getChildCount());
notifyItemChanged(position, EXPAND_PAYLOAD);
iter.remove();
}
}
public final boolean foldGroup(GroupBean groupBean) {
if (mExpandGroupSet.remove(groupBean)) {
final int position = getAdapterPosition(getGroupIndex(groupBean));
notifyItemRangeRemoved(position + 1, groupBean.getChildCount());
notifyItemChanged(position, EXPAND_PAYLOAD);
return true;
}
return false;
}
@Override
public final int getItemCount() {
int result = getGroupCount();
if (result == 0 && mEmptyViewProducer != null) {
mIsEmpty = true;
return mHeaderViewProducer != null && mShowHeaderViewWhenEmpty ? 2 : 1;
}
mIsEmpty = false;
for (GroupBean groupBean : mExpandGroupSet) {
if (getGroupIndex(groupBean) < 0) {
Log.e(TAG, "invalid index in expandgroupList : " + groupBean);
continue;
}
result += groupBean.getChildCount();
}
if (mHeaderViewProducer != null) {
result++;
}
return result;
}
/**
* 获取group在列表中的实际位置
* @param groupIndex
* @return
*/
public final int getAdapterPosition(int groupIndex) {
int result = groupIndex;
for (GroupBean groupBean : mExpandGroupSet) {
if (getGroupIndex(groupBean) >= 0 && getGroupIndex(groupBean) < groupIndex) {
result += groupBean.getChildCount();
}
}
if (mHeaderViewProducer != null) {
result++;
}
return result;
}
public final int getGroupIndex(@NonNull GroupBean groupBean) {
for (int i = 0; i < getGroupCount(); i++) {
if (groupBean.equals(getGroupItem(i))) {
return i;
}
}
return -1;
}
@Override
public final int getItemViewType(int position) {
if (mIsEmpty) {
return position == 0 && mShowHeaderViewWhenEmpty && mHeaderViewProducer != null ? TYPE_HEADER : TYPE_EMPTY;
}
if (position == 0 && mHeaderViewProducer != null) {
return TYPE_HEADER;
}
int[] coord = translateToDoubleIndex(position);
GroupBean groupBean = getGroupItem(coord[0]);
if (coord[1] < 0) {
int groupType = getGroupType(groupBean);
if ((groupType & TYPE_MASK) == 0) {
return groupType | TYPE_GROUP;
} else {
throw new IllegalStateException(
String.format(Locale.getDefault(), "GroupType [%d] conflits with MASK [%d]", groupType, TYPE_MASK));
}
} else {
int childType = getChildType(groupBean, groupBean.getChildAt(coord[1]));
if ((childType & TYPE_MASK) == 0) {
return childType | TYPE_CHILD;
} else {
throw new IllegalStateException(
String.format(Locale.getDefault(), "ChildType [%d] conflits with MASK [%d]", childType, TYPE_MASK));
}
}
}
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType & TYPE_MASK) {
case TYPE_EMPTY:
return mEmptyViewProducer.onCreateViewHolder(parent);
case TYPE_HEADER:
return mHeaderViewProducer.onCreateViewHolder(parent);
case TYPE_CHILD:
return onCreateChildViewHolder(parent, viewType ^ TYPE_CHILD);
case TYPE_GROUP:
return onCreateGroupViewHolder(parent, viewType ^ TYPE_GROUP);
default:
throw new IllegalStateException(
String.format(Locale.getDefault(), "Illegal view type : viewType[%d]", viewType));
}
}
@Override
public final void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
onBindViewHolder(holder, position, null);
}
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
switch (holder.getItemViewType() & TYPE_MASK) {
case TYPE_EMPTY:
mEmptyViewProducer.onBindViewHolder(holder);
break;
case TYPE_HEADER:
mHeaderViewProducer.onBindViewHolder(holder);
break;
case TYPE_CHILD:
final int[] childCoord = translateToDoubleIndex(position);
GroupBean groupBean = getGroupItem(childCoord[0]);
bindChildViewHolder((ChildViewHolder) holder, groupBean, groupBean.getChildAt(childCoord[1]), payloads);
break;
case TYPE_GROUP:
bindGroupViewHolder((GroupViewHolder) holder, getGroupItem(translateToDoubleIndex(position)[0]), payloads);
break;
default:
throw new IllegalStateException(
String.format(Locale.getDefault(), "Illegal view type : position [%d] ,itemViewType[%d]", position, holder.getItemViewType()));
}
}
protected void bindGroupViewHolder(final GroupViewHolder holder, final GroupBean groupBean, List<Object> payload) {
if (payload != null && payload.size() != 0) {
if (payload.contains(EXPAND_PAYLOAD)) {
holder.onExpandStatusChanged(BaseExpandableRecyclerViewAdapter.this, isGroupExpanding(groupBean));
if (payload.size() == 1) {
return;
}
}
onBindGroupViewHolder(holder, groupBean, isGroupExpanding(groupBean), payload);
return;
}
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mListener != null) {
return mListener.onGroupLongClicked(groupBean);
}
return false;
}
});
if (!groupBean.isExpandable()) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onGroupClicked(groupBean);
}
}
});
} else {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final boolean isExpand = mExpandGroupSet.contains(groupBean);
if (mListener == null || !mListener.onInterceptGroupExpandEvent(groupBean, isExpand)) {
final int adapterPosition = holder.getAdapterPosition();
holder.onExpandStatusChanged(BaseExpandableRecyclerViewAdapter.this, !isExpand);
if (isExpand) {
mExpandGroupSet.remove(groupBean);
notifyItemRangeRemoved(adapterPosition + 1, groupBean.getChildCount());
} else {
mExpandGroupSet.add(groupBean);
notifyItemRangeInserted(adapterPosition + 1, groupBean.getChildCount());
}
}
}
});
}
onBindGroupViewHolder(holder, groupBean, isGroupExpanding(groupBean));
}
protected void bindChildViewHolder(ChildViewHolder holder, final GroupBean groupBean, final ChildBean childBean, List<Object> payload) {
onBindChildViewHolder(holder, groupBean, childBean, payload);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onChildClicked(groupBean, childBean);
}
}
});
}
/**
* position translation
* from adapterPosition to group-child coord
*
* @param adapterPosition adapterPosition
* @return int[]{groupIndex,childIndex}
*/
protected final int[] translateToDoubleIndex(int adapterPosition) {
if (mHeaderViewProducer != null) {
adapterPosition--;
}
final int[] result = new int[]{-1, -1};
final int groupCount = getGroupCount();
int adaptePositionCursor = 0;
for (int groupCursor = 0; groupCursor < groupCount; groupCursor++) {
//是group
if (adaptePositionCursor == adapterPosition) {
result[0] = groupCursor;
result[1] = -1;
break;
}
GroupBean groupBean = getGroupItem(groupCursor);
//当前groupBean是展开的
if (mExpandGroupSet.contains(groupBean)) {
final int childCount = groupBean.getChildCount();
final int offset = adapterPosition - adaptePositionCursor;
if (childCount >= offset) {
result[0] = groupCursor;
result[1] = offset - 1;
break;
}
//游标移到这一组最后一个
adaptePositionCursor += childCount;
}
//游标移动到下一组
adaptePositionCursor++;
}
return result;
}
public interface BaseGroupBean<ChildBean> {
/**
* get num of children
*
* @return
*/
int getChildCount();
/**
* get child at childIndex
*
* @param childIndex integer between [0,{@link #getChildCount()})
* @return
*/
ChildBean getChildAt(int childIndex);
/**
* whether this BaseGroupBean is expandable
*
* @return
*/
boolean isExpandable();
}
public static abstract class BaseGroupViewHolder extends RecyclerView.ViewHolder {
public BaseGroupViewHolder(View itemView) {
super(itemView);
}
/**
* optimize for partial invalidate,
* when switching fold status.
* Default implementation is update the whole {android.support.v7.widget.RecyclerView.ViewHolder#itemView}.
* <p>
* Warning:If the itemView is invisible , the callback will not be called.
*
* @param relatedAdapter
* @param isExpanding
*/
protected abstract void onExpandStatusChanged(RecyclerView.Adapter relatedAdapter, boolean isExpanding);
}
public interface ExpandableRecyclerViewOnClickListener<GroupBean extends BaseGroupBean, ChildBean> {
/**
* called when group item is long clicked
*
* @param groupItem
* @return
*/
boolean onGroupLongClicked(GroupBean groupItem);
/**
* called when an expandable group item is clicked
*
* @param groupItem
* @param isExpand
* @return whether intercept the click event
*/
boolean onInterceptGroupExpandEvent(GroupBean groupItem, boolean isExpand);
/**
* called when an unexpandable group item is clicked
*
* @param groupItem
*/
void onGroupClicked(GroupBean groupItem);
/**
* called when child is clicked
*
* @param groupItem
* @param childItem
*/
void onChildClicked(GroupBean groupItem, ChildBean childItem);
}
}
BaseCheckableExpandableRecyclerViewAdapter,需要注意的是由于我们需要改标数据库数据,所以我在setCheckMode加了自己需要的参数组或者子元素。
package com.khb.mpcms.ui.trait;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* 创建时间:2020/8/17
* 编写人:kanghb
* 功能描述:
*/
public abstract class BaseCheckableExpandableRecyclerViewAdapter
<GroupBean extends BaseCheckableExpandableRecyclerViewAdapter.CheckableGroupItem<ChildBean>,
ChildBean,
GroupViewHolder extends BaseCheckableExpandableRecyclerViewAdapter.BaseCheckableGroupViewHolder<GroupBean>,
ChildViewHolder extends BaseCheckableExpandableRecyclerViewAdapter.BaseCheckableChildViewHolder<ChildBean>>
extends BaseExpandableRecyclerViewAdapter<GroupBean, ChildBean, GroupViewHolder, ChildViewHolder> {
private static final String TAG = BaseCheckableExpandableRecyclerViewAdapter.class.getSimpleName();
private final Object PAYLOAD_CHECKMODE = this;
public static final int CHECK_MODE_NONE = 0;
public static final int CHECK_MODE_PARTIAL = CHECK_MODE_NONE + 1;
public static final int CHECK_MODE_ALL = CHECK_MODE_NONE + 2;
private final List<CheckedItem<GroupBean, ChildBean>> mCheckedSet = new ArrayList<>();
private CheckStatusChangeListener<GroupBean, ChildBean> mOnCheckStatusChangeListener;
/**
* max num of items to be selected at the same time
* if equals to 1 , new choice will override old choice
* otherwise , the new checking-clickevent will be ignored
*/
private int mMaxCheckedNum;
public BaseCheckableExpandableRecyclerViewAdapter(int maxCheckedNum) {
if (maxCheckedNum <= 0) {
throw new IllegalArgumentException("invalid maxCheckedNum " + maxCheckedNum);
}
mMaxCheckedNum = maxCheckedNum;
// registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
// @Override
// public void onChanged() {
// // after notifyDataSetChange(),clear outdated list
// mCheckedSet.clear();
// }
// });
}
public final List<CheckedItem<GroupBean, ChildBean>> getCheckedSet() {
return mCheckedSet;
}
public final int getSelectedCount() {
return mCheckedSet.size();
}
public final void setOnCheckStatusChangeListener(CheckStatusChangeListener<GroupBean, ChildBean> onCheckStatusChangeListener) {
mOnCheckStatusChangeListener = onCheckStatusChangeListener;
}
public final void setCheckedSet(List<CheckedItem<GroupBean, ChildBean>> checkedSet) {
clearCheckedListAndUpdateUI();
if (checkedSet == null || checkedSet.size() <= 0) {
return;
}
for (CheckedItem<GroupBean, ChildBean> checkedItem : checkedSet) {
addToCheckedList(checkedItem);
}
}
@Override
public void onBindGroupViewHolder(final GroupViewHolder groupViewHolder, final GroupBean groupBean, boolean isExpand) {
groupViewHolder.setCheckMode(getGroupCheckedMode(groupBean),groupBean);
if (groupViewHolder.getCheckableRegion() != null) {
groupViewHolder.getCheckableRegion().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onGroupChecked(
groupBean,
groupViewHolder,
translateToDoubleIndex(groupViewHolder.getAdapterPosition())[0]);
}
});
}
}
@Override
protected void onBindGroupViewHolder(GroupViewHolder groupViewHolder, GroupBean groupBean, boolean isExpand, List<Object> payload) {
if (payload != null && payload.size() != 0) {
if (payload.contains(PAYLOAD_CHECKMODE)) {
groupViewHolder.setCheckMode(getGroupCheckedMode(groupBean),groupBean);
}
return;
}
onBindGroupViewHolder(groupViewHolder, groupBean, isExpand);
}
@Override
public void onBindChildViewHolder(final ChildViewHolder holder, final GroupBean groupBean, final ChildBean childBean) {
holder.setCheckMode(getChildCheckedMode(childBean),childBean);
if (holder.getCheckableRegion() != null) {
holder.getCheckableRegion().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onChildChecked(
holder,
groupBean,
childBean);
}
});
}
}
@Override
protected void onBindChildViewHolder(ChildViewHolder holder, GroupBean groupBean, ChildBean childBean, List<Object> payload) {
if (payload != null && payload.size() != 0) {
if (payload.contains(PAYLOAD_CHECKMODE)) {
holder.setCheckMode(getChildCheckedMode(childBean),childBean);
}
return;
}
onBindChildViewHolder(holder, groupBean, childBean);
}
@Override
protected void bindChildViewHolder(final ChildViewHolder holder, final GroupBean groupBean, final ChildBean childBean, List<Object> payload) {
super.bindChildViewHolder(holder, groupBean, childBean, payload);
holder.setCheckMode(getChildCheckedMode(childBean),childBean);
if (holder.getCheckableRegion() != null) {
holder.getCheckableRegion().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onChildChecked(holder, groupBean, childBean);
}
});
}
}
/**
* 获取当前组的选中模式,有三种
* @param groupBean
* @return
*/
private int getGroupCheckedMode(GroupBean groupBean) {
//当前组不能展开
if (!groupBean.isExpandable()) {
return isItemSelected(groupBean) ? CHECK_MODE_ALL : CHECK_MODE_NONE;
} else {
//判断当前child有几个选中的,然后根据数量返回模式
int checkedCount = 0;
for (ChildBean childBean : groupBean.getChildren()) {
if (isItemSelected(childBean)) {
checkedCount++;
}
}
if (checkedCount == 0) {
return CHECK_MODE_NONE;
} else if (checkedCount == groupBean.getChildCount()) {
return CHECK_MODE_ALL;
} else {
return CHECK_MODE_PARTIAL;
}
}
}
/**
* 获取当前子项的选中模式,有两种
* @param childBean
* @return
*/
private int getChildCheckedMode(ChildBean childBean) {
return isItemSelected(childBean) ? CHECK_MODE_ALL : CHECK_MODE_NONE;
}
/**
* 执行选中某一组操作
* @param groupBean
* @param holder
* @param groupIndex
*/
private void onGroupChecked(GroupBean groupBean, GroupViewHolder holder, int groupIndex) {
//获取当前组的选中模式
int checkedMode = getGroupCheckedMode(groupBean);
if (groupBean.isExpandable()) {
switch (checkedMode) {
case CHECK_MODE_NONE:
case CHECK_MODE_PARTIAL:
selectAllInGroup(holder, groupBean, groupIndex, true);
break;
case CHECK_MODE_ALL:
default:
selectAllInGroup(holder, groupBean, groupIndex, false);
break;
}
} else {
if (isItemSelected(groupBean)) {
if (!onInterceptGroupCheckStatusChanged(groupBean, false)
&& removeFromCheckedList(groupBean)) {
holder.setCheckMode(getGroupCheckedMode(groupBean),groupBean);
}
} else {
if (!onInterceptGroupCheckStatusChanged(groupBean, true)
&& addToCheckedList(groupBean)) {
holder.setCheckMode(getGroupCheckedMode(groupBean),groupBean);
}
}
}
}
private void selectAllInGroup(GroupViewHolder holder, GroupBean groupBean, int groupIndex, boolean selectAll) {
//当前组没展开并且要选中全部的时候先展开当前组
if (selectAll && !isGroupExpanding(groupBean)) {
expandGroup(groupBean);
}
final List<ChildBean> children = groupBean.getChildren();
final int groupAdapterPosition = holder.getAdapterPosition();
final int originalGroupCheckedMode = getGroupCheckedMode(groupBean);
for (int i = 0; i < children.size(); i++) {
ChildBean childBean = children.get(i);
if (selectAll) {
if (isItemSelected(childBean)) {
continue;
}
if (!onInterceptChildCheckStatusChanged(groupBean, childBean, true)) {
addToCheckedList(groupBean, childBean);
notifyItemChanged(groupAdapterPosition + i + 1, PAYLOAD_CHECKMODE);
}
} else {
if (!isItemSelected(childBean)) {
continue;
}
if (!onInterceptChildCheckStatusChanged(groupBean, childBean, false)
&& removeFromCheckedList(groupBean, childBean)) {
notifyItemChanged(groupAdapterPosition + i + 1, PAYLOAD_CHECKMODE);
}
}
}
final int currentGroupCheckedMode = getGroupCheckedMode(groupBean);
if (currentGroupCheckedMode != originalGroupCheckedMode) {
holder.setCheckMode(currentGroupCheckedMode,groupBean);
}
}
private void onChildChecked(ChildViewHolder holder, GroupBean groupBean, ChildBean childBean) {
final int originalGroupMode = getGroupCheckedMode(groupBean);
boolean changeFlag = false;
if (getChildCheckedMode(childBean) == CHECK_MODE_ALL) {
//当前是选中的,执行未选中操作:先删除,再重新设置mode
if (!onInterceptChildCheckStatusChanged(groupBean, childBean, false)
&& removeFromCheckedList(groupBean, childBean)) {
holder.setCheckMode(getChildCheckedMode(childBean),childBean);
changeFlag = true;
}
} else {//未选中,执行选中操作,在设置mode
if (!onInterceptChildCheckStatusChanged(groupBean, childBean, true)
&& addToCheckedList(groupBean, childBean)) {
holder.setCheckMode(getChildCheckedMode(childBean),childBean);
changeFlag = true;
}
}
//判断group的mode模式是否和原来的一样,不一样就更新下
if (changeFlag && getGroupCheckedMode(groupBean) != originalGroupMode) {
notifyItemChanged(getAdapterPosition(getGroupIndex(groupBean)), PAYLOAD_CHECKMODE);
}
}
private boolean onInterceptGroupCheckStatusChanged(GroupBean groupBean, boolean targetStatus) {
return mOnCheckStatusChangeListener != null
&& mOnCheckStatusChangeListener.onInterceptGroupCheckStatusChange(groupBean, targetStatus);
}
private boolean onInterceptChildCheckStatusChanged(GroupBean groupBean, ChildBean childBean, boolean targetStatus) {
return mOnCheckStatusChangeListener != null
&& mOnCheckStatusChangeListener.onInterceptChildCheckStatusChange(groupBean, childBean, targetStatus);
}
private boolean isItemSelected(GroupBean groupBean) {
for (CheckedItem checkedItem : mCheckedSet) {
if (checkedItem.getCheckedItem().equals(groupBean)) {
return true;
}
}
return false;
}
private boolean isItemSelected(ChildBean childBean) {
for (CheckedItem checkedItem : mCheckedSet) {
if (checkedItem.getCheckedItem().equals(childBean)) {
return true;
}
}
return false;
}
private boolean addToCheckedList(GroupBean groupBean) {
return addToCheckedList(groupBean, null);
}
private boolean addToCheckedList(GroupBean groupBean, ChildBean childBean) {
return addToCheckedList(new CheckedItem<>(groupBean, childBean));
}
/**
* 加入到选中列表
* @param checkedItem
* @return
*/
private boolean addToCheckedList(CheckedItem<GroupBean, ChildBean> checkedItem) {
if (mMaxCheckedNum == 1) {
clearCheckedListAndUpdateUI();
} else if (mMaxCheckedNum <= mCheckedSet.size()) {
return false;
}
return mCheckedSet.add(checkedItem);
}
/**
* 清除全部选中列表
*/
private void clearCheckedListAndUpdateUI() {
Iterator<CheckedItem<GroupBean, ChildBean>> iter = mCheckedSet.iterator();
while (iter.hasNext()) {
final CheckedItem<GroupBean, ChildBean> checkedItem = iter.next();
final int[] coord = getCoordFromCheckedItem(checkedItem);
final GroupBean groupBean = getGroupItem(coord[0]);
final int originalGroupCheckedStatus = getGroupCheckedMode(groupBean);
iter.remove();
final int groupAdapterPosition = getAdapterPosition(coord[0]);
final int adapterPosition = groupAdapterPosition + coord[1] + 1;
notifyItemChanged(adapterPosition, PAYLOAD_CHECKMODE);
final int currentGroupCheckedStatus = getGroupCheckedMode(groupBean);
if (coord[1] >= 0 && currentGroupCheckedStatus != originalGroupCheckedStatus) {
notifyItemChanged(groupAdapterPosition, PAYLOAD_CHECKMODE);
}
}
}
/** 获取某一选中项的坐标
* @param checkedItem
* @return
*/
private int[] getCoordFromCheckedItem(CheckedItem<GroupBean, ChildBean> checkedItem) {
int[] result = new int[]{-1, -1};
for (int i = 0; i < getGroupCount(); i++) {
if (getGroupItem(i).equals(checkedItem.groupItem)) {
result[0] = i;
break;
}
}
if (checkedItem.childItem != null) {
result[1] = getGroupItem(result[0]).getChildren().indexOf(checkedItem.childItem);
}
return result;
}
private boolean removeFromCheckedList(GroupBean groupBean) {
return removeFromCheckedList(groupBean, null);
}
private boolean removeFromCheckedList(GroupBean groupBean, ChildBean childBean) {
return mCheckedSet.remove(new CheckedItem<>(groupBean, childBean));
}
public abstract static class BaseCheckableGroupViewHolder <GroupBean>extends BaseGroupViewHolder implements Selectable<GroupBean> {
public BaseCheckableGroupViewHolder(View itemView) {
super(itemView);
}
}
public abstract static class BaseCheckableChildViewHolder <ChildBean>extends RecyclerView.ViewHolder implements Selectable<ChildBean> {
public BaseCheckableChildViewHolder(View itemView) {
super(itemView);
}
}
public interface Selectable<Item> {
/**
* optimize for partial update
* if an item is switching check mode ,
* do not need to invalidate whole item,
* this is the optimized callback
*
* @param mode
*/
void setCheckMode(int mode,Item childBean);
/**
* checkable region
* correspond to the check operation
* <p>
* ect.
* the child item returns itself
* the group item returns its check icon
*
* @return
*/
View getCheckableRegion();
}
public interface CheckableGroupItem<ChildItem> extends BaseGroupBean<ChildItem> {
/**
* get children list
*
* @return
*/
List<ChildItem> getChildren();
}
public static class CheckedItem<GroupItem, ChildItem> {
GroupItem groupItem;
ChildItem childItem;
public CheckedItem(GroupItem groupItem, ChildItem childItem) {
this.groupItem = groupItem;
this.childItem = childItem;
}
public GroupItem getGroupItem() {
return groupItem;
}
public ChildItem getChildItem() {
return childItem;
}
Object getCheckedItem() {
return childItem != null ? childItem : groupItem;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CheckedItem that = (CheckedItem) o;
if (!groupItem.equals(that.groupItem)) {
return false;
}
return childItem != null ? childItem.equals(that.childItem) : that.childItem == null;
}
@Override
public int hashCode() {
int result = groupItem.hashCode();
result = 31 * result + (childItem != null ? childItem.hashCode() : 0);
return result;
}
}
/**
* Intercept of mode switch
* <p>
* returns true means intercept this mode switch
*
* @param <GroupItem>
* @param <ChildItem>
*/
public interface CheckStatusChangeListener<GroupItem extends BaseCheckableExpandableRecyclerViewAdapter.CheckableGroupItem<ChildItem>, ChildItem> {
boolean onInterceptGroupCheckStatusChange(GroupItem groupItem, boolean targetStatus);
boolean onInterceptChildCheckStatusChange(GroupItem groupItem, ChildItem childItem, boolean targetStatus);
}
}
接下来就是项目自己的Adapter了,可以根据需求自己定制。
public class TraitAdapter extends BaseCheckableExpandableRecyclerViewAdapter<TraitGroup, Trait, TraitAdapter.GroupVH, TraitAdapter.ChildVH> {
private List<TraitGroup> traitGroupList;
private TraitViewModel viewModel;
public static List<Trait> traitSelected = new ArrayList<>();
private Context context;
private final List<CheckedItem<TraitGroup, Trait>> mCheckedSet = new ArrayList<>();
public TraitAdapter(Context context, List<TraitGroup> traitGroups, TraitViewModel traitViewModel) {
// //获取最大的组数量
// int maxNum = 0;
// for (int i = 0; i < traitGroups.size(); i++) {
// int num = traitGroups.get(i).getChildList().size()
// if(maxNum < num){
// maxNum = num;
// }
// }
super(100);
traitGroupList = traitGroups;
viewModel = traitViewModel;
this.context = context;
}
public void setCheckedSet() {
//初始化选中数据CheckedSet
for (int i = 0; i < traitGroupList.size(); i++) {
TraitGroup traitGroup = traitGroupList.get(i);
List<Trait> traits = traitGroup.getChildList();
if (traits.size() > 0) {
for (int j = 0; j < traits.size(); j++) {
Trait trait = traits.get(j);
if (trait.getChecked()) {
mCheckedSet.add(new CheckedItem<>(traitGroup, trait));
}
}
}
}
setCheckedSet(mCheckedSet);
}
@Override
public int getGroupCount() {
return traitGroupList.size();
}
@Override
public TraitGroup getGroupItem(int groupIndex) {
return traitGroupList.get(groupIndex);
}
@Override
public GroupVH onCreateGroupViewHolder(ViewGroup parent, int groupViewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_trait_group, parent, false);
return new GroupVH(view);
}
@Override
public void onBindGroupViewHolder(GroupVH holder, TraitGroup groupBean, boolean isExpand) {
super.onBindGroupViewHolder(holder, groupBean, isExpand);
holder.tvTraitGroup.setText(groupBean.getGroupName());
if (groupBean.isExpandable()) {
holder.ivTraitGroup.setVisibility(View.VISIBLE);
holder.ivTraitGroup.setImageResource(isExpand ? R.drawable.ic_arrow_expanding : R.drawable.ic_arrow_folding);
} else {
holder.ivTraitGroup.setVisibility(View.INVISIBLE);
}
}
@Override
public ChildVH onCreateChildViewHolder(ViewGroup parent, int childViewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_trait, parent, false);
return new ChildVH(view, viewModel);
}
@Override
public void onBindChildViewHolder(ChildVH holder, TraitGroup groupBean, Trait trait) {
super.onBindChildViewHolder(holder, groupBean, trait);
holder.tvTrait.setText(trait.getTraitName());
}
static class GroupVH extends BaseCheckableExpandableRecyclerViewAdapter.BaseCheckableGroupViewHolder<TraitGroup> {
private TextView tvTraitGroup;
private ImageView ivTraitGroup;
private TextView cvSelect;
GroupVH(View itemView) {
super(itemView);
tvTraitGroup = itemView.findViewById(R.id.tv_trait_group);
ivTraitGroup = itemView.findViewById(R.id.iv_trait_group);
cvSelect = itemView.findViewById(R.id.cb_select);
}
// this method is used for partial update.Which means when expand status changed,only a part of this view need to invalidate
@Override
protected void onExpandStatusChanged(RecyclerView.Adapter relatedAdapter, boolean isExpanding) {
// 1.只更新左侧展开、闭合箭头
ivTraitGroup.setImageResource(isExpanding ? R.drawable.ic_arrow_expanding : R.drawable.ic_arrow_folding);
// // 2.默认刷新整个Item
// relatedAdapter.notifyItemChanged(getAdapterPosition());
}
@Override
public void setCheckMode(int mode, TraitGroup childBean) {
switch (mode) {
case CHECK_MODE_ALL:
cvSelect.setText("全选");
cvSelect.setBackground(ContextCompat.getDrawable(BaseApplication.INSTANCE, R.drawable.shape_title_gradient));
cvSelect.setTextColor(ContextCompat.getColor(BaseApplication.INSTANCE, R.color.white));
break;
case CHECK_MODE_PARTIAL:
case CHECK_MODE_NONE:
cvSelect.setText("取消");
cvSelect.setBackground(ContextCompat.getDrawable(BaseApplication.INSTANCE, R.drawable.shape_frame_black));
cvSelect.setTextColor(ContextCompat.getColor(BaseApplication.INSTANCE, R.color.black));
break;
default:
break;
}
}
@Override
public View getCheckableRegion() {
return cvSelect;
}
}
static class ChildVH extends BaseCheckableExpandableRecyclerViewAdapter.BaseCheckableChildViewHolder<Trait> {
private TextView tvTrait;
private TextView cvSelect;
private TraitViewModel traitViewModel;
ChildVH(View itemView, TraitViewModel viewModel) {
super(itemView);
tvTrait = itemView.findViewById(R.id.tv_trait);
cvSelect = itemView.findViewById(R.id.cb_select);
this.traitViewModel = viewModel;
}
@Override
public void setCheckMode(int mode, Trait childBean) {
switch (mode) {
case CHECK_MODE_ALL:
cvSelect.setText("显示");
cvSelect.setBackground(ContextCompat.getDrawable(BaseApplication.INSTANCE, R.drawable.shape_title_gradient));
cvSelect.setTextColor(ContextCompat.getColor(BaseApplication.INSTANCE, R.color.white));
if (!childBean.getChecked()) {
childBean.setChecked(true);
childBean.setCheckedTime(new Date());
traitViewModel.update(childBean);
traitSelected.add(childBean);
}
break;
case CHECK_MODE_NONE:
cvSelect.setText("隐藏");
cvSelect.setBackground(ContextCompat.getDrawable(BaseApplication.INSTANCE, R.drawable.shape_frame_black));
cvSelect.setTextColor(ContextCompat.getColor(BaseApplication.INSTANCE, R.color.black));
if (childBean.getChecked()) {
childBean.setChecked(false);
childBean.setCheckedTime(null);
traitViewModel.update(childBean);
traitSelected.remove(childBean);
}
break;
default:
break;
}
}
@Override
public View getCheckableRegion() {
return cvSelect;
}
}
}
在Activity中调用Adapter
val traitAdapter = TraitAdapter(this, traitList, traitViewModel)
rv_trait.layoutManager = LinearLayoutManager(this)
traitAdapter.setEmptyViewProducer(object : ViewProducer {
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder? {
return ViewProducer.DefaultEmptyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.empty, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder?) {}
})
traitAdapter.setHeaderViewProducer(object : ViewProducer {
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder? {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.header, parent, false)
//折叠全部
view.findViewById<TextView>(R.id.tv_fold_all).setOnClickListener {
traitAdapter.foldAll()
}
//展开全部
view.findViewById<TextView>(R.id.tv_expand_all).setOnClickListener {
traitList.forEach { traitAdapter.expandGroup(it) }
}
return ViewProducer.DefaultEmptyViewHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder?) {
}
}, false)
rv_trait.adapter = traitAdapter
从服务器或者数据库中获取到数据执行
traitAdapter.notifyDataSetChanged()
traitAdapter.setCheckedSet()
问题
前面讲过HashSet不行contains返回false的原因是当一个对象被存进HashSet后,修改了对象后,这个对象的Hash值和最初存进去的哈希值不一样,这种情况下就检索不到对象了,所以contains返回false。