ExpanableRecyclerView

[Image upload failed…(image-8fDB49-1510531851002)]

Github:github.com/hgDendi/Exp…

Custom support secondary menu RecyclerViewAdapter.

Packages will open closed operations in the BaseExpandableRecyclerViewAdapter, make the whole way of using full of elasticity.

Below are the specific usage methods. Generally, override is required for 6 methods:

  • getGroupCount
  • getGroupItem
  • onCreateGroupViewHolder
  • onCreateChildViewHolder
  • onBindGroupViewHolder
  • onBindChildViewHolder

Since onCreateViewHolder and onBindViewHolder are the RecyclerViewAdapter methods that need to force Override, they are split into two methods according to the parent-child relationship. However, getGroupCount and getGroupItem can be realized by a single line of code based on List in high probability, so they are very easy to use.

Gradle

Dependencies {the compile 'com. HgDendi: expandable recyclerview - adapter: 1.0.1'}Copy the code

advantages

  1. Easy to use, simple and clear
  2. Retain the original mechanism of RecyclerView to the maximum extent, slide to specific items before rendering, will not slide to another Group to render all sub-items under another Group
  3. Partial refresh of itemView. You can customize the refresh mechanism for expanded and closed itemView to avoid refreshing the entire GroupItem when expanded and closed (e.g. simple arrow pointing to change).
  4. Using generics, user – defined input parameters, higher scalability

Method of use

Define parent-child data structures

The GroupBean needs to inherit from BaseGroupBean and override three methods.

  • getChildCount
    • Obtain the number of child nodes
  • isExpandable
    • Whether it is an expandable node
    • The default implementation can be to determine whether the child node is zero, but you can do other things as well
  • getChildAt
    • Obtain the corresponding child node data structure according to index
class SampleGroupBean implements BaseExpandableRecyclerViewAdapter.BaseGroupBean<SampleChildBean> { @Override public int  getChildCount() { return mList.size(); } // whether this group is expandable @Override public boolean isExpandable() { return getChildCount() > 0; } @Override public SampleChildBean getChildAt(int index) { return mList.size() <= index ? null : mList.get(index); } } public class SampleChildBean { }Copy the code

Define the corresponding ViewHolder

The ViewHolder corresponding to the Group inherits BaseGroupViewHolder and overwrites onExpandStatusChanged.

This method is a method to realize the local refresh of item, which will be called back when expanded or closed. For example, in most cases, the switch closed state only needs to modify the left arrow pointing, without refreshing other parts of itemView.

The implementation principle is to use RecyclerView payload mechanism to realize local listening refresh.

static class GroupVH extends BaseExpandableRecyclerViewAdapter.BaseGroupViewHolder { GroupVH(View itemView) { super(itemView); } // 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. Update only the left expanding and closing arrows foldiv. setImageResource(isExpanding? R.drawable.ic_arrow_expanding : R.drawable.ic_arrow_folding); / / 2. The default refresh the entire Item relatedAdapter. NotifyItemChanged (getAdapterPosition ()); } } static class ChildVH extends RecyclerView.ViewHolder { ChildVH(View itemView) { super(itemView); }}Copy the code

Inherit the base class using a custom Adapter

/ /!!!!! Notice the generics used for inheritance, Respectively for the above mentioned beans and ViewHolder public class SampleAdapter extends BaseExpandableRecyclerViewAdapter < SampleGroupBean, SampleChildBean, SampleAdapter.GroupVH, Sampleadapter. ChildVH> @override public int getGroupCount() {// Number of parent nodes} @override public getGroupItem(int) @override public GroupVH onCreateGroupViewHolder(ViewGroup parent, int groupViewType) { } @Override public ChildVH onCreateChildViewHolder(ViewGroup parent, int childViewType) { } @Override public void onBindGroupViewHolder(GroupVH holder, SampleGroupBean sampleGroupBean, boolean isExpand) { } @Override public void onBindChildViewHolder(ChildVH holder, SampleGroupBean sampleGroupBean, SampleChildBean sampleChildBean) { } }Copy the code

Other USES

Increase the variety of fathers and sons

Override the getChildType and getGroupType methods to control type, which is passed back to onCreateGroupViewHolder and onCreateChildViewHolder.

protected int getGroupType(GroupBean groupBean) {
    return 0;
}

abstract public GroupViewHolder onCreateGroupViewHolder(ViewGroup parent, int groupViewType);
    
protected int getChildType(GroupBean groupBean, ChildBean childBean) {
    return 0;
}

abstract public ChildViewHolder onCreateChildViewHolder(ViewGroup parent, int childViewType);
Copy the code

Add EmptyView when the list is empty

adapter.setEmptyViewProducer(new ViewProducer() {
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        View emptyView = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty, parent, false);
        return new DefaultEmptyViewHolder(emptyView);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder) {
    }
});
Copy the code

Increase the HeaderView

adapter.setEmptyViewProducer(new ViewProducer() {
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        View emptyView = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
        return new DefaultEmptyViewHolder(emptyView);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder) {
    }
},false);
Copy the code

Listen for an event

Listener events can be set using setListener.

public interface ExpandableRecyclerViewOnClickListener<GroupBean extends BaseGroupBean, ChildBean> {/** * @param groupItem * @return */ Boolean onGroupLongClicked(GroupBean groupItem); /** * A callback triggered when the expanded group is clicked, returns a Boolean value indicating whether to intercept the operation ** @param groupItem * @param isExpand * @return true invalidates the click. False Expand or close operations normally. */ boolean onInterceptGroupExpandEvent(GroupBean groupItem, boolean isExpand); /** * Clicked on GroupView (isExpandable of the Group returns false) ** @param groupItem */ void onGroupClicked(GroupBean) groupItem); ** @param groupItem * @param childItem */ void onChildClicked(GroupBean groupItem, ChildBean childItem); }Copy the code

Realize the principle of

Parent-child structure division

  1. GetItemType is used to determine the type, and the base class is divided into four types of Views.
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; // Use getItemView to determine the type of MASK defined above by default. You can subclass Group and Child, but do not allow conflicts with TYPE_MASK. Exception @Override public Final int getItemViewType(int position) {if (mIsEmpty) {return position == 0 && mShowHeaderViewWhenEmpty ? 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)); }}}Copy the code
  1. Type judgments are made in onCreateViewHolder and onBindViewHolder, calling different methods that are overridden in subclasses. Note that we make all three methods final to prevent subclasses from overloading, which can only override specific methods of different types.
@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())); }}Copy the code

Open and close operation

operation

When the groupBean isExpandable returns true, set the click event for itemView to expand and close.

The specific principle of expansion and closure is to record expansion and closure in Set, update when expansion and closure occur, and use notifyItemChange interface to refresh the list locally.

private Set<GroupBean> mExpandGroupSet; protected void bindGroupViewHolder(final GroupViewHolder holder, final GroupBean groupBean, List<Object> payload) { // ... if (! groupBean.isExpandable()) { // ... } 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()); }}}}); } subclass onBindGroupViewHolder(holder, groupBean, isGroupExpand(groupBean)); }Copy the code

Local refresh principle

Payload is defined and partially refreshed using the Payload mechanism.

When notifyItemChange is passed in payload, the onBindViewHolder operation checks whether there is a payload. If there is a payload, the partial refresh operation is performed.

private static final Object EXPAND_PAYLOAD = new Object(); NotifyItemChanged (position, EXPAND_PAYLOAD); // notifyItemChanged(position, EXPAND_PAYLOAD); Payload protected void bindGroupViewHolder(final GroupViewHolder holder, final GroupBean GroupBean, List<Object> payload) { if (payload ! = null && payload.size() ! = 0) {if (payload. Contains (EXPAND_PAYLOAD)) { In this method to achieve specific, closed logical holder. OnExpandStatusChanged (BaseExpandableRecyclerViewAdapter. This isGroupExpand (groupBean)); if (payload.size() == 1) { return; } } onBindGroupViewHolder(holder, groupBean, isGroupExpand(groupBean), payload); return; }}Copy the code

License

MIT

rem.mit-license.org/