Android实现RecyclerView二级列表可折叠展开选中

前言

项目里需要实现个可折叠,可展开的的二级列表,首先想到了用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。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值