ExpanableRecyclerView
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{
compile 'com. HgDendi: expandable recyclerview - adapter: 1.0.1'
}Copy the code
advantages
- Easy to use, simple and clear
- 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
- 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).
- 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(a) {
return mList.size();
}
// whether this group is expandable
@Override
public boolean isExpandable(a) {
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 open and closed arrows
foldIv.setImageResource(isExpanding ? R.drawable.ic_arrow_expanding : R.drawable.ic_arrow_folding);
// 2. Refresh the entire Item by defaultrelatedAdapter.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 here, the Bean and ViewHolder mentioned above
public class SampleAdapter extends BaseExpandableRecyclerViewAdapter
<SampleGroupBean.SampleChildBean.SampleAdapter.GroupVH.SampleAdapter.ChildVH>
@Override
public int getGroupCount(a){
// Number of parent nodes
}
@Override
public GroupBean getGroupItem(int groupIndex) {
// Get the parent node
}
@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> {
/** ** long time operation **@param groupItem
* @return* /
boolean onGroupLongClicked(GroupBean groupItem);
/** * A callback that is triggered when the group is clicked, and returns a Boolean indicating whether to intercept the operation **@param groupItem
* @param isExpand
* @returnTrue invalidates a click. False Expand or close operations normally. * /
boolean onInterceptGroupExpandEvent(GroupBean groupItem, boolean isExpand);
/** * GroupView (isExpandable of the Group returns false to trigger this callback) **@param groupItem
*/
void onGroupClicked(GroupBean groupItem);
/** * Click on the child View **@param groupItem
* @param childItem
*/
void onChildClicked(GroupBean groupItem, ChildBean childItem);
}Copy the code
Realize the principle of
Parent-child structure division
- 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, otherwise Exception will be reported
@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
- 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 implementation
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();
// The interface to call when partial refresh (wrapped, no need to be called by the user)
notifyItemChanged(position, EXPAND_PAYLOAD);
/ / processing content
protected void bindGroupViewHolder(final GroupViewHolder holder, final GroupBean groupBean, List<Object> payload) {
if(payload ! =null&& payload.size() ! =0) {
if (payload.contains(EXPAND_PAYLOAD)) {
// The holder method has abstract methods in which concrete expansion and closure logic is implemented
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/