前言

项目里需要实现个可折叠,可展开的的二级列表,首先想到了用ExpandListView去实现,ExpandListView是继承ListView的。由于项目里所有列表都用Recycleview,再加上本身对于Recyclerview情有独钟,懂的都懂,就想着试试用它实现吧。

效果图

实现

网上找到了ExpandableRecyclerView这个库,读了一遍源码,然后将BaseExpandableRecyclerViewAdapter拷贝到项目中,简单能够显示出来出来,因为我们的业务需求需要实现可选择功能,刚好作者也封装好了BaseCheckableExpandableRecyclerViewAdapter这个类,我就也直接拿来用了。这里就不贴这两个类的代码了,具体可以去自己看。

然而选中的时候业务需求要改变数据库中子元素数据的check状态,这一改变就出问题了。。

java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1at 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() {@Overridepublic void onChanged() {// after notifyDataSetChange(),clear outdated listList<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的实际positionfinal 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;}@Overridepublic 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;}@Overridepublic 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));}}}@Overridepublic 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));}}@Overridepublic final void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {onBindViewHolder(holder, position, null);}@Overridepublic 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() {@Overridepublic boolean onLongClick(View v) {if (mListener != null) {return mListener.onGroupLongClicked(groupBean);}return false;}});if (!groupBean.isExpandable()) {holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mListener != null) {mListener.onGroupClicked(groupBean);}}});} else {holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {@Overridepublic 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++) {//是groupif (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);}}@Overridepublic 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() {@Overridepublic void onClick(View view) {onGroupChecked(groupBean,groupViewHolder,translateToDoubleIndex(groupViewHolder.getAdapterPosition())[0]);}});}}@Overrideprotected 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);}@Overridepublic 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() {@Overridepublic void onClick(View view) {onChildChecked(holder,groupBean,childBean);}});}}@Overrideprotected 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);}@Overrideprotected 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() {@Overridepublic 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) {//当前是选中的,执行未选中操作:先删除,再重新设置modeif (!onInterceptChildCheckStatusChanged(groupBean, childBean, false)&& removeFromCheckedList(groupBean, childBean)) {holder.setCheckMode(getChildCheckedMode(childBean),childBean);changeFlag = true;}} else {//未选中,执行选中操作,在设置modeif (!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;}@Overridepublic 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;}@Overridepublic 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() {//初始化选中数据CheckedSetfor (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);}@Overridepublic int getGroupCount() {return traitGroupList.size();}@Overridepublic TraitGroup getGroupItem(int groupIndex) {return traitGroupList.get(groupIndex);}@Overridepublic GroupVH onCreateGroupViewHolder(ViewGroup parent, int groupViewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_trait_group, parent, false);return new GroupVH(view);}@Overridepublic 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);}}@Overridepublic ChildVH onCreateChildViewHolder(ViewGroup parent, int childViewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_trait, parent, false);return new ChildVH(view, viewModel);}@Overridepublic 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@Overrideprotected 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());}@Overridepublic 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;}}@Overridepublic 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;}@Overridepublic 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;}}@Overridepublic 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。

Android实现RecyclerView二级列表可折叠展开选中相关推荐

  1. Android中RecyclerView点击item展开列表详细内容(超简单实现)

    请注明出处: http://blog.csdn.net/qq_23179075/article/details/79230457 Android中RecyclerView点击item展开列表详细内容( ...

  2. Android开发丶二级列表的联动功能实现

    通常我们在外卖或者商城app会看到竖排二级列表的界面,点击左边列表的分类菜单,右边列表则会显示对应分类下的列表内容,这篇博文将对该功能的实现归纳和整理. 首先看下效果图: 在第一个页面选择最多三个标签 ...

  3. android开发实现选择列表,Android使用RecyclerView实现列表数据选择操作

    Android使用RecyclerView实现列表数据选择操作 发布时间:2020-08-31 17:50:13 来源:脚本之家 阅读:76 作者:迟做总比不做强 这些时间做安卓盒子项目,因为安卓电视 ...

  4. android购物车栏,Android怎么实现二级列表购物车功能

    Android怎么实现二级列表购物车功能 发布时间:2021-04-16 12:45:40 来源:亿速云 阅读:61 作者:小新 小编给大家分享一下Android怎么实现二级列表购物车功能,希望大家阅 ...

  5. android开发 RecyclerView 瀑布列表布局

    android开发 RecyclerView 瀑布列表布局 1.写一个内容的自定义小布局: <?xml version="1.0" encoding="utf-8& ...

  6. Android 泽宇二级列表

    在开发 Android APP 的时候,难免会需要实现二级列表的情况,而在自己的项目中使用的列表是ExpandableListView 如若转发标明转载处:https://mp.csdn.net/po ...

  7. RecyclerView二级列表

    最近正好有做到二级列表,就记载一下怎样使用RecyclerView做二级列表吧. 效果大概就是这个样子,可以凑合用,主要是弄清楚大概原理,这样就知道步骤.代码地址在最下面. 需要了解 我们知道,写一个 ...

  8. Android利用RecyclerView实现列表倒计时

    利用RecyclerView实现一个非常简单的列表倒计时 一.先上效果图 二.GitHub 代码地址,欢迎指正https://github.com/MNXP/Countdown 三.思路 在adapt ...

  9. android 列表倒计时,Android利用RecyclerView实现列表倒计时效果

    最近面试时,面试官问了一个列表倒计时效果如何实现,现在记录一下. 运行效果图 实现思路 实现方法主要有两个: 1.为每个开始倒计时的item启动一个定时器,再做更新item处理: 2.只启动一个定时器 ...

最新文章

  1. SpringMVC:注解@ControllerAdvice的工作原理
  2. 【Python】青少年蓝桥杯_每日一题_7.19_电梯用量
  3. 深度学习原理与框架-卷积网络细节-数据增强策略 1.翻转 2.随机裁剪 3.平移 4.旋转角度...
  4. SAP CRM One order里user status和system status的mapping逻辑
  5. java 排序api_用java api进行sort
  6. element-ui 搜索框组件:监听input键盘事件 - 代码篇
  7. 杭州师范大学计算机与科学,杭州师范大学信息科学与工程学院
  8. springboot幂等性_SpringBoot+Redis实现接口幂等性,就看这篇了
  9. 查看redis数据_关于 Redis 的一些新特性、使用建议和最佳实践
  10. 友元函数类图_要达到形式的公平,需要具备的前提条件是()。
  11. i7处理器好吗_i5和i7区别有多大,性能差距大吗?i59400F和i79700F的区别对比
  12. java 动态执行代码_java中动态执行一段代码
  13. c52语言网红楼梯流水灯程序,单片机程序-利用C52库函数实现左右流水灯
  14. 小程序按钮如何制作?
  15. idea查看java类方法_用IntelliJ IDEA看Java类图的方法(图文)
  16. 三步生活法:土豆(Todo),优势,庆祝
  17. Python快速读取超大文件
  18. SpringBoot(6)自动配置 - Condition
  19. java 实现ps功能_JS实现在线ps功能详解
  20. 基于Python中docx与docxcompose批量合并多个Word文档文件并逐一添加分页符

热门文章

  1. 分享一个音乐API接口
  2. USART向串口助手发送数据(DMA方式)
  3. 一篇文章带你认识GraphQL
  4. 哈工程自考计算机应用数学,自考本科计算机应用数学 01332
  5. 各国通货膨胀率(1961-2019年)
  6. java excel api及详细教程_Java Excel Api及详细教程
  7. 腾讯竟然又偷偷开源了一套Android原生UI框架!不吃透都对不起自己
  8. host切换工具——SwitchHosts
  9. java下载本地excel文件
  10. 北大4位数学天才,如今齐聚美国搞科研,令人叹息