You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
*/
public static abstract class ExpandableAdapter<VH extends ExpandableViewHolder, T> extends Adapter implements ExpandHandler {
public static final boolean DEBUG = BuildConfig.DEBUG && false;
protected static final String LOG_TAG = "ExpandableRecyclerView";
public static final class LongParcelable implements Parcelable {
public static final Creator CREATOR = new Creator() { @OverRide
public LongParcelable createFromParcel(Parcel in) {
return new LongParcelable(in);
}
@Override
public LongParcelable[] newArray(int size) {
return new LongParcelable[size];
}
};
private final long value;
public LongParcelable(long value) {
this.value = value;
}
private LongParcelable(Parcel in) {
this.value = in.readLong();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(value);
}
}
private ExpandableRecyclerView recyclerView;
/**
Equivalent of {@link Adapter#onCreateViewHolder(ViewGroup, int)} for an {@code ExpandableRecyclerView}.
Called when ExpandableRecyclerView needs a new {@link ExpandableViewHolder} of the given type to represent
an item.
This new ExpandableViewHolder should be constructed with a new View that can represent the items
of the given type. You can either create a new View manually or inflate it from an XML
layout file.
The new ExpandableViewHolder will be used to display items of the adapter using
{@link #onBindGroupView(ExpandableViewHolder, int)} and child items with {@link #onBindChildView(ExpandableViewHolder, int, int)}.
Since it will be re-used to display different items in the data set, it is a good idea to cache references to sub views of the View to
Equivalent of {@link Adapter#onBindViewHolder(ViewHolder, int)} for a group View.
Called by ExpandableRecyclerView to display the data at the specified position. This method
should update the contents of the {@link ExpandableViewHolder#itemView} to reflect the item at
the given position.
Note that unlike {@link android.widget.ListView}, ExpandableRecyclerView will not call this
method again if the position of the item changes in the data set unless the item itself
is invalidated or the new position cannot be determined. For this reason, you should only
use the position parameter while acquiring the related data item inside this
method and should not keep a copy of it. If you need the position of an item later on
(e.g. in a click listener), use {@link ExpandableViewHolder#getPosition()} which will have the
updated position.
*
@param holder The ViewHolder which should be updated to represent the contents of the
item at the given position in the data set.
@param groupPosition The group position of the item within the adapter's data set.
*/
protected abstract void onBindGroupView(VH holder, int groupPosition);
/**
Equivalent of {@link Adapter#onBindViewHolder(ViewHolder, int)} for a child View.
Called by ExpandableRecyclerView to display the data at the specified position. This method
should update the contents of the {@link ExpandableViewHolder#itemView} to reflect the item at
the given position.
Note that unlike {@link android.widget.ListView}, ExpandableRecyclerView will not call this
method again if the position of the item changes in the data set unless the item itself
is invalidated or the new position cannot be determined. For this reason, you should only
use the position parameter while acquiring the related data item inside this
method and should not keep a copy of it. If you need the position of an item later on
(e.g. in a click listener), use {@link ExpandableViewHolder#getPosition()} which will have the
updated position.
*
@param holder The ViewHolder which should be updated to represent the contents of the
item at the given position in the data set.
@param groupPosition The group position of the item within the adapter's data set.
*/
protected abstract void onBindChildView(VH holder, int groupPosition, int childPosition);
/**
Get the amount of groups in the adapter. Similar to {@link android.widget.ExpandableListAdapter#getGroupCount() ExpandableListAdapter.getGroupCount()}.
*/
protected abstract int getGroupCount();
/**
Gets the number of children in a specified group. Similar to {@link android.widget.ExpandableListAdapter#getChildrenCount(int) ExpandableListAdapter.getChildrenCount()}.
*/
protected abstract int getChildrenCount(int groupPosition);
/**
Equivalent of {@link Adapter#getItemViewType(int)} for a group View.
Return the view type of the item at {@code groupPosition} for the purposes
of view recycling.
Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types.
*
@OverRide
public final long getItemId(int groupPosition) {
Parcelable stableId = getGroupStableId(groupPosition);
if (null == stableId)
return NO_ID;
return stableId.hashCode();
}
@OverRide
public final VH onCreateViewHolder(ViewGroup parent, int viewType) {
if (DEBUG) Log.d(LOG_TAG, this + " onCreateViewHolder(type=" + viewType + ')');
VH result = onCreateExpandableViewHolder(parent, viewType);
return result;
}
@OverRide
public final void onBindViewHolder(VH holder, int groupPosition) {
if (DEBUG)
Log.d(LOG_TAG, this + " onBindViewHolder(pos=" + groupPosition + ") expanded=" + expandedPosition + " count=" + expandedChildCount);
No stable IDs, equivalent to calling the old {@code setHasStableIds(false)} /
public static final int STABLE_IDS_NONE = 0;
/*
Stable IDs using the old system of {@link #getGroupId(int)} /
public static final int STABLE_IDS_LONG = 1;
/*
Stables IDs using Parcelable instead of long using {@link #getGroupStableId(int)} and {@link #getGroupStableIdPosition(Parcelable)}
*/
public static final int STABLE_IDS_PARCELABLE = 2;
@IntDef({STABLE_IDS_NONE, STABLE_IDS_LONG, STABLE_IDS_PARCELABLE})
public @interface StableIdsMode {
}
@param hasStableIds
*/ @deprecated @OverRide
public final void setHasStableIds(boolean hasStableIds) {
throw new IllegalAccessError("use setStableIdsMode()");
}
/**
Specify the stable ID mode used by this adapter.
This is the mode to handle the stable ID used to recover the position. It replaces {@link ExpandableRecyclerView.ExpandableAdapter#hasStableIds()}
by allowing a {@code Parcelable} rather than a {@code long}.
It can be {@link #STABLE_IDS_NONE}, {@link #STABLE_IDS_LONG} or {@link #STABLE_IDS_PARCELABLE}.
You must override {@link #getGroupStableId(int)} and {@link #getGroupStableIdPosition(Parcelable)} for this to work properly.
*/
public void setStableIdsMode(@StableIdsMode int stableIdsMode) {
if (stableIdsMode == STABLE_IDS_NONE) {
super.setHasStableIds(false);
useLegacyStableIds = false;
} else {
super.setHasStableIds(true);
useLegacyStableIds = stableIdsMode == STABLE_IDS_LONG;
}
}
/**
Similar to {@link android.widget.ExpandableListAdapter#getGroupId(int) ExpandableListAdapter.getGroupId()}
when using {@link #STABLE_IDS_LONG} with {@link #setStableIdsMode(int)}. Otherwise {@link #getGroupStableId(int)} is used.
*/
protected long getGroupId(int groupPosition) {
return NO_ID;
}
/**
Get the stable ID at group position so the position can be recovered properly. Returns {@code null} by default.
Used when {@link #STABLE_IDS_PARCELABLE} is set on {@link #setStableIdsMode(int)}.
void attachRecyclerView(ExpandableRecyclerView recyclerView) {
if (DEBUG)
Log.w(LOG_TAG, this + " attachRecyclerView recyclerView=" + recyclerView + " was " + this.recyclerView);
this.recyclerView = recyclerView;
}
@OverRide
public void onViewRecycled(VH holder) {
super.onViewRecycled(holder);
holder.setExpandHandler(null);
}
/**
Same as {@link #notifyDataSetChanged()} but overridable.
*/
public void notifyDataChanged() {
if (DEBUG) Log.i(LOG_TAG, this + " notifyDataChanged recyclerView=" + recyclerView);
if (null != recyclerView) {
recyclerView.stopScroll();
}
notifyDataSetChanged();
if (expandedStableId != null) {
// recover the position of the old expanded element (depends on stable IDs)
if (!useLegacyStableIds) {
if (DEBUG)
Log.i(LOG_TAG, this + " notifyDataChanged recovering expanded position for " + expandedStableId);
setExpandedPosition(getGroupStableIdPosition(expandedStableId));
}
} else {
setExpandedPosition(expandedPosition);
}
if (null != recyclerView && recyclerView.selectedStableId != null) {
if (!useLegacyStableIds) {
if (DEBUG)
Log.i(LOG_TAG, this + " notifyDataChanged recovering selected position for " + expandedStableId);
recyclerView.selectedGroup = getGroupStableIdPosition(recyclerView.selectedStableId);
}
}
}
Notifies the item at group position changed and the display should be updated.
*/
public void notifyGroupChanged(int groupPosition) {
if (null == recyclerView)
return;
final int modifiedStart;
final int itemChangedCount;
if (expandedPosition == RecyclerView.NO_POSITION || groupPosition < expandedPosition) {
// the modified item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
} else if (groupPosition == expandedPosition) {
// the modified item is the expanded item
itemChangedCount = expandedChildCount + 1;
modifiedStart = groupPosition;
} else {
// the modified item is after the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition + expandedChildCount;
}
if (DEBUG)
Log.d(LOG_TAG, this + " notifyGroupChanged(" + groupPosition + ") start=" + modifiedStart + " count=" + itemChangedCount + " expanded=" + expandedPosition + " headerCount=" + getHeaderViewsCount());
recyclerView.changeRange(modifiedStart, itemChangedCount);
}
/**
Notifies an item has been inserted at group position. The item insertion will be animated.
*/
public void notifyGroupInserted(int groupPosition) {
if (null == recyclerView)
return;
final int modifiedStart;
final int itemChangedCount;
if (expandedPosition == RecyclerView.NO_POSITION) {
// the inserted item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
} else if (groupPosition < expandedPosition) {
// the inserted item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
setExpandedPosition(expandedPosition + 1);
} else if (groupPosition == expandedPosition) {
// the inserted item is over the expanded item
itemChangedCount = expandedChildCount + 1;
modifiedStart = groupPosition;
setExpandedPosition(expandedPosition + 1);
} else {
// the inserted item is after the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition + expandedChildCount;
}
if (DEBUG)
Log.d(LOG_TAG, this + " notifyGroupInserted(" + groupPosition + ") start=" + modifiedStart + " count=" + itemChangedCount + " expanded=" + expandedPosition + " headerCount=" + getHeaderViewsCount());
recyclerView.insertRange(modifiedStart, itemChangedCount);
}
/**
Notifies the item at group position has been removed. The item removal will be animated.
*/
public void notifyGroupRemoved(int groupPosition) {
if (null == recyclerView)
return;
final int modifiedStart;
final int itemChangedCount;
if (expandedPosition == RecyclerView.NO_POSITION || groupPosition < expandedPosition) {
// the removed item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
} else if (groupPosition == expandedPosition) {
// the removed item is the expanded item
itemChangedCount = expandedChildCount + 1;
modifiedStart = groupPosition;
setExpandedPosition(RecyclerView.NO_POSITION);
} else {
// the removed item is after the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition + expandedChildCount;
setExpandedPosition(expandedPosition - 1);
}
if (DEBUG)
Log.d(LOG_TAG, this + " notifyGroupRemoved(" + groupPosition + ") start=" + modifiedStart + " count=" + itemChangedCount + " expanded=" + expandedPosition + " headerCount=" + getHeaderViewsCount());
recyclerView.removeRange(modifiedStart, itemChangedCount);
}
/**
Get the currently expanded element or {@code null} if no item is expanded.
*/
public @nullable
T getExpandedGroup() {
if (expandedPosition == RecyclerView.NO_POSITION)
return null;
return getGroup(expandedPosition);
}
}
public ExpandableRecyclerView(Context context) {
super(context);
init();
}
public ExpandableRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ExpandableRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mUserItemAnimator = super.getItemAnimator();
if (DEBUG_ANIMATOR) Log.d(ANIM_TAG, "init user animator to " + mUserItemAnimator);
}
@OverRide
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!firstLayoutPassed) {
if (ExpandableAdapter.DEBUG) Log.d(ExpandableAdapter.LOG_TAG, this + " first layout");
firstLayoutPassed = true;
}
super.onLayout(changed, l, t, r, b);
}
public boolean isFirstLayoutPassed() {
return firstLayoutPassed;
}
Use {@link #setExpandableAdapter(ExpandableRecyclerView.ExpandableAdapter)}.
*/ @deprecated @OverRide
public void setAdapter(Adapter adapter) {
if (adapter != null && !(adapter instanceof ExpandableAdapter))
throw new IllegalStateException("use a ExpandableAdapter not " + adapter);
setExpandableAdapter((ExpandableAdapter) adapter);
}
public void setExpandableAdapter(ExpandableAdapter adapter) {
if (getAdapter() instanceof ExpandableAdapter) {
ExpandableAdapter expandableAdapter = (ExpandableAdapter) getAdapter();
expandableAdapter.attachRecyclerView(null);
}
super.setAdapter(adapter);
if (null != adapter)
adapter.attachRecyclerView(this);
}
public ExpandableAdapter getExpandableAdapter() {
return (ExpandableAdapter) getAdapter();
}
/**
Get the {@link ItemAnimator} used to handle animations other than the collapse/expand.
*/ @OverRide
public ItemAnimator getItemAnimator() {
return mUserItemAnimator;
}
/**
Set the {@link ItemAnimator} used to handle animations other than the collapse/expand.
*
@param animator
*/ @OverRide
public void setItemAnimator(final ItemAnimator animator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "setItemAnimator to " + animator + " mUserItemAnimator=" + mUserItemAnimator);
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR) Log.d(ANIM_TAG, " change user animator");
super.setItemAnimator(animator);
}
mUserItemAnimator = animator;
}
private void expandAndCollapse(final int expandPosition, final int collapsePosition) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "expandAndCollapse " + expandPosition + '/' + collapsePosition + " currentAnimator=" + super.getItemAnimator());
final ItemAnimator currentItemAnimator = super.getItemAnimator();
if (DEBUG_ANIMATOR)
Log.d(ANIM_TAG, "expandAndCollapse(" + expandPosition + ',' + collapsePosition + ") with current animator=" + currentItemAnimator + " isRunning=" + (currentItemAnimator != null && currentItemAnimator.isRunning()) + " mUserItemAnimator=" + mUserItemAnimator);
if (currentItemAnimator != null) {
// wait until that ItemAnimator has finished processing its queue to go on with ours
currentItemAnimator.isRunning(new ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
ExpandAndCollapseItemAnimator expandAnimator = new ExpandAndCollapseItemAnimator(expandPosition, collapsePosition);
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before expandAndCollapse with current animator=" + currentItemAnimator + " finished (running=" + currentItemAnimator.isRunning() + "), use expand ItemAnimator=" + expandAnimator);
ExpandableRecyclerView.super.setItemAnimator(expandAnimator);
doExpandAndCollapse(expandPosition, collapsePosition);
}
});
return;
}
doExpandAndCollapse(expandPosition, collapsePosition);
}
private void doExpandAndCollapse(final int expandPosition, final int collapsePosition) {
ExpandableAdapter expandableAdapter = getExpandableAdapter();
boolean collapseChanged = expandableAdapter.expandedPosition != RecyclerView.NO_POSITION && collapsePosition == expandableAdapter.expandedPosition;
// collapse
if (collapseChanged) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "collapse group " + collapsePosition + " in " + getExpandableAdapter());
getAdapter().notifyItemRangeRemoved(collapsePosition + getHeaderViewsCount() + 1, expandableAdapter.getChildrenCount(collapsePosition));
expandableAdapter.setExpandedPosition(RecyclerView.NO_POSITION);
}
// expand
boolean expandedChanged = expandableAdapter.setExpandedPosition(expandPosition);
if (expandedChanged) {
int childViewCount = expandableAdapter.getChildrenCount(expandPosition);
getAdapter().notifyItemRangeInserted(expandPosition + getHeaderViewsCount() + 1, childViewCount);
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "expand group " + expandPosition + " in " + getExpandableAdapter());
}
if (collapseChanged || expandedChanged) {
boolean expandedIsShown = false;
if (expandPosition != RecyclerView.NO_POSITION) {
if (getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
//Log.e(LOG_TAG, "lastFull = "+linearLayoutManager.findLastCompletelyVisibleItemPosition()+" group+Child"+(groupPosition + childViewCount));
if (linearLayoutManager.findFirstVisibleItemPosition() < expandPosition + getHeaderViewsCount() &&
linearLayoutManager.findLastCompletelyVisibleItemPosition() < expandPosition + getHeaderViewsCount()) {
expandedIsShown = true;
if (ExpandableAdapter.DEBUG)
Log.e(ExpandableAdapter.LOG_TAG, "doExpandAndCollapse() the expandedIsShown");
}
}
}
// dirty trick to scroll to show the last visible expandable item when it's not visible and do some other stuff
ViewCompat.postOnAnimationDelayed(this, new Runnable() {
@Override
public void run() {
ExpandableRecyclerView.super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation to expand finished, do scroll if needed");
if (getExpandableAdapter() == null)
return;
if (collapsePosition != RecyclerView.NO_POSITION) {
ExpandableViewHolder viewHolder = (ExpandableViewHolder) findViewHolderForPosition(collapsePosition + getHeaderViewsCount());
if (null != viewHolder) {
getExpandableAdapter().setExpandedViewHolder(viewHolder, false, false);
}
}
if (expandPosition != RecyclerView.NO_POSITION) {
ExpandableViewHolder viewHolder = (ExpandableViewHolder) findViewHolderForPosition(expandPosition + getHeaderViewsCount());
if (null != viewHolder) {
getExpandableAdapter().setExpandedViewHolder(viewHolder, true, false);
}
if (getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
int childViewCount = getExpandableAdapter().getChildrenCount(expandPosition);
//Log.e(LOG_TAG, "lastFull = "+linearLayoutManager.findLastCompletelyVisibleItemPosition()+" group+Child"+(groupPosition + childViewCount));
if (linearLayoutManager.findFirstVisibleItemPosition() < expandPosition + getHeaderViewsCount() &&
linearLayoutManager.findLastCompletelyVisibleItemPosition() < expandPosition + getHeaderViewsCount() + childViewCount) {
if (ExpandableAdapter.DEBUG)
Log.i(ExpandableAdapter.LOG_TAG, "scroll to show more expanded items");
smoothScrollToPosition(expandPosition + getHeaderViewsCount() + childViewCount);
}
}
}
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation finished set back mUserItemAnimator=" + mUserItemAnimator);
ExpandableRecyclerView.super.setItemAnimator(mUserItemAnimator);
}
});
}
}, ExpandableRecyclerView.super.getItemAnimator().getMoveDuration());
}
}
/**
Force expanding of the specified group.
*/
public void expandGroup(final int groupPosition) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "expandGroup " + groupPosition);
expandAndCollapse(groupPosition, RecyclerView.NO_POSITION);
}
/**
Force collapsing of the specified group.
*/
public void collapseGroup(int groupPosition) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "collapseGroup " + groupPosition);
expandAndCollapse(RecyclerView.NO_POSITION, groupPosition);
}
if (groupPosition != selectedGroup) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "doSetSelectedGroup(" + groupPosition + ") selectedGroup=" + selectedGroup);
if (selectedGroup != RecyclerView.NO_POSITION) {
ExpandableViewHolder selectedViewHolder = (ExpandableViewHolder) findViewHolderForPosition(selectedGroup);
if (null != selectedViewHolder) {
selectedViewHolder.isSelected = false;
getExpandableAdapter().notifyGroupChanged(selectedGroup);
}
}
selectedGroup = groupPosition;
if (getExpandableAdapter().hasStableIds()) {
if (getExpandableAdapter().useLegacyStableIds)
selectedStableId = new ExpandableAdapter.LongParcelable(getExpandableAdapter().getGroupId(groupPosition));
else
selectedStableId = getExpandableAdapter().getGroupStableId(groupPosition);
} else {
selectedStableId = null;
}
if (selectedGroup != RecyclerView.NO_POSITION) {
ExpandableViewHolder selectedViewHolder = (ExpandableViewHolder) findViewHolderForPosition(selectedGroup);
if (null != selectedViewHolder) {
selectedViewHolder.isSelected = true;
getExpandableAdapter().notifyGroupChanged(selectedGroup);
}
}
}
}
/**
Equivalent of {@link ExpandableListView#setSelectedGroup(int)} for an {@code ExpandableRecyclerView}.
*/
public void setSelectedGroup(final int groupPosition) {
if (ExpandableAdapter.DEBUG)
Log.v(ExpandableAdapter.LOG_TAG, "setSelectedGroup(" + groupPosition + ')');
final ItemAnimator currentItemAnimator = super.getItemAnimator();
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "setSelectedGroup current animator=" + currentItemAnimator);
if (super.getItemAnimator() != null) {
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() { @OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before selectGroup finished, do item selection");
doSetSelectedGroup(groupPosition);
}
});
return;
}
doSetSelectedGroup(groupPosition);
}
/**
Equivalent of {@link ExpandableListView#getSelectedPosition()} for an {@code ExpandableRecyclerView}.
*/
public long getSelectedPosition() {
if (selectedGroup == RecyclerView.NO_POSITION)
return ExpandableListView.PACKED_POSITION_VALUE_NULL;
return ExpandableListView.getPackedPositionForGroup(selectedGroup);
}
public void collapseAll() {
if (ExpandableAdapter.DEBUG) Log.d(ExpandableAdapter.LOG_TAG, "collapseAll");
expandAndCollapse(RecyclerView.NO_POSITION, getExpandableAdapter().expandedPosition);
}
/**
Equivalent of {@link ExpandableListView#setOnGroupExpandListener(ExpandableListView.OnGroupExpandListener)} for an {@code ExpandableRecyclerView}.
*/
public void setOnGroupExpandListener(ExpandableListView.OnGroupExpandListener onGroupExpandListener) {
this.onGroupExpandListener = onGroupExpandListener;
}
/**
Equivalent of {@link ExpandableListView#setOnGroupCollapseListener(ExpandableListView.OnGroupCollapseListener)} for an {@code ExpandableRecyclerView}.
*/
public void setOnGroupCollapseListener(ExpandableListView.OnGroupCollapseListener onGroupCollapseListener) {
this.onGroupCollapseListener = onGroupCollapseListener;
}
/**
Equivalent of {@link ExpandableListView#setOnGroupClickListener(ExpandableListView.OnGroupClickListener)} for an {@code ExpandableRecyclerView}.
*/
public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) {
this.onGroupClickListener = onGroupClickListener;
}
public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
this.onChildClickListener = onChildClickListener;
}
private void changeRange(final int groupPosition, final int childCount) {
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "changeRange with current animator mUserItemAnimator=" + mUserItemAnimator);
getAdapter().notifyItemRangeChanged(groupPosition + getHeaderViewsCount(), childCount);
} else if (super.getItemAnimator() == null) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "changeRange with no current animator mUserItemAnimator=" + mUserItemAnimator);
super.setItemAnimator(mUserItemAnimator);
getAdapter().notifyItemRangeChanged(groupPosition + getHeaderViewsCount(), childCount);
} else {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "changeRange with current custom animator " + super.getItemAnimator() + " isRunning=" + super.getItemAnimator().isRunning());
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() { @OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before changeRange finished set mUserItemAnimator");
if (ExpandableRecyclerView.super.getItemAnimator() != mUserItemAnimator) {
ExpandableRecyclerView.super.setItemAnimator(mUserItemAnimator);
}
changeRange(groupPosition, childCount);
}
});
}
}
private void insertRange(final int groupPosition, final int childCount) {
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "insertRange with current animator mUserItemAnimator=" + mUserItemAnimator);
getAdapter().notifyItemRangeInserted(groupPosition + getHeaderViewsCount(), childCount);
} else if (super.getItemAnimator() == null) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "insertRange with no current animator mUserItemAnimator=" + mUserItemAnimator);
super.setItemAnimator(mUserItemAnimator);
getAdapter().notifyItemRangeInserted(groupPosition + getHeaderViewsCount(), childCount);
} else {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "insertRange with current custom animator " + super.getItemAnimator() + " isRunning=" + super.getItemAnimator().isRunning());
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() { @OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before insertRange finished set mUserItemAnimator");
if (ExpandableRecyclerView.super.getItemAnimator() != mUserItemAnimator) {
ExpandableRecyclerView.super.setItemAnimator(mUserItemAnimator);
}
insertRange(groupPosition, childCount);
}
});
}
}
private void removeRange(final int groupPosition, final int childCount) {
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "removeRange with current animator mUserItemAnimator=" + mUserItemAnimator);
getAdapter().notifyItemRangeRemoved(groupPosition + getHeaderViewsCount(), childCount);
} else if (super.getItemAnimator() == null) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "removeRange with no current animator mUserItemAnimator=" + mUserItemAnimator);
super.setItemAnimator(mUserItemAnimator);
getAdapter().notifyItemRangeRemoved(groupPosition + getHeaderViewsCount(), childCount);
} else {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "removeRange with current custom animator " + super.getItemAnimator() + " isRunning=" + super.getItemAnimator().isRunning());
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() { @OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before removeRange finished set mUserItemAnimator");
if (ExpandableRecyclerView.super.getItemAnimator() != mUserItemAnimator) {
ExpandableRecyclerView.super.setItemAnimator(mUserItemAnimator);
}
removeRange(groupPosition, childCount);
}
});
}
}
private final Runnable refreshDisplay = new Runnable() { @OverRide
public void run() {
if (getLayoutManager() instanceof LinearLayoutManager && isFirstLayoutPassed() && getExpandableAdapter() != null) {
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
int first = layoutManager.findFirstVisibleItemPosition();
int last = layoutManager.findLastVisibleItemPosition();
getAdapter().notifyItemRangeChanged(first, last);
}
}
};
/**
Refresh all the displayed items (rebind the data to update the content)
*/
public void refreshDisplay() {
final ItemAnimator currentItemAnimator = getItemAnimator();
if (DEBUG_ANIMATOR)
Log.d(ANIM_TAG, "refreshDisplay current animator=" + currentItemAnimator + " running=" + (currentItemAnimator != null && currentItemAnimator.isRunning()));
if (currentItemAnimator == null) {
removeCallbacks(refreshDisplay);
post(refreshDisplay);
} else {
currentItemAnimator.isRunning(new ItemAnimator.ItemAnimatorFinishedListener() { @OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before refreshDisplay with current animator=" + currentItemAnimator + " finished, do refresh");
removeCallbacks(refreshDisplay);
post(refreshDisplay);
}
});
}
}
private class ExpandAndCollapseItemAnimator extends DefaultItemAnimator {
private final int expandPosition;
private final int collapsePosition;
private boolean expandListenerCalled;
private boolean collapseListenerCalled;
public ExpandAndCollapseItemAnimator(int expandPosition, int collapsePosition) {
this.expandPosition = expandPosition;
this.collapsePosition = collapsePosition;
collapseListenerCalled = collapsePosition == RecyclerView.NO_POSITION;
expandListenerCalled = expandPosition == RecyclerView.NO_POSITION;
setAddDuration(0);
setRemoveDuration(0);
setMoveDuration(200);
}
@Override
public void onRemoveFinished(ViewHolder item) {
if (DEBUG_ANIMATOR)
Log.v(ANIM_TAG, this + " onRemoveFinished finished item=" + item + " isRunning=" + isRunning());
super.onRemoveFinished(item);
// TODO even when the element was not shown
if (!collapseListenerCalled && item instanceof ExpandableViewHolder) {
int holderPosition = getExpandableAdapter().getHolderGroupPosition((ExpandableViewHolder) item, false);
if (holderPosition == collapsePosition) {
if (ExpandableAdapter.DEBUG)
Log.v(ExpandableAdapter.LOG_TAG, "removed the collapsed item");
if (null != onGroupCollapseListener)
onGroupCollapseListener.onGroupCollapse(collapsePosition);
collapseListenerCalled = true;
}
}
if (DEBUG_ANIMATOR)
Log.v(ANIM_TAG, this + " onRemoveFinished finished item=" + item + " isRunning=" + isRunning());
}
@Override
public void onAddFinished(ViewHolder item) {
if (DEBUG_ANIMATOR)
Log.v(ANIM_TAG, this + " onAddFinished finished item=" + item + " isRunning=" + isRunning());
super.onAddFinished(item);
// TODO even when the element was not shown
if (!expandListenerCalled && item instanceof ExpandableViewHolder) {
int holderPosition = getExpandableAdapter().getHolderGroupPosition((ExpandableViewHolder) item, false);
if (holderPosition == expandPosition) {
if (ExpandableAdapter.DEBUG)
Log.v(ExpandableAdapter.LOG_TAG, "added the expanded item");
if (null != onGroupExpandListener)
onGroupExpandListener.onGroupExpand(expandPosition);
expandListenerCalled = true;
}
}
if (DEBUG_ANIMATOR)
Log.v(ANIM_TAG, this + " onAddFinished finished item=" + item + " isRunning=" + isRunning());
}
@Override
public boolean isRunning() {
return (!collapseListenerCalled || !expandListenerCalled) && super.isRunning();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
DebugUtils.buildShortClassTag(this, sb);
sb.append(" expandPosition=");
sb.append(expandPosition);
sb.append(" collapsePosition=");
sb.append(collapsePosition);
sb.append('}');
return sb.toString();
}
}
static class SavedState extends AbsSavedState {
public Parcelable selectedStableId;
public Parcelable expandedStableId;
public SavedState(Parcel in) {
super(in.readParcelable(RecyclerView.class.getClassLoader()));
selectedStableId = in.readParcelable(getClass().getClassLoader());
expandedStableId = in.readParcelable(getClass().getClassLoader());
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(selectedStableId, 0);
dest.writeParcelable(expandedStableId, 0);
}
public static final Creator<SavedState> CREATOR
= new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.DebugUtils;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.AbsSavedState;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import com.ruanko.jiaxiaotong.tv.parent.BuildConfig;
//https://github.com/levelup/Expandable-RecyclerView
/**
A class equivalent to {@link ExpandableListView ExpandableListView} with the {@code RecyclerView} features.
You must use an {@link ExpandableAdapter} instead of a {@link RecyclerView.Adapter}
and an {@link ExpandableViewHolder} instead of a {@link RecyclerView.ViewHolder}.
Only one element can be expanded at a time.
*@author Created by robUx4 on 02/10/2014.
*/
public class ExpandableRecyclerView extends RecyclerViewWithHeader {
private static final String ANIM_TAG = "Animator";
private static final boolean DEBUG_ANIMATOR = false;
private ExpandableListView.OnGroupExpandListener onGroupExpandListener;
private ExpandableListView.OnGroupCollapseListener onGroupCollapseListener;
private OnGroupClickListener onGroupClickListener;
private OnChildClickListener onChildClickListener;
/**
*/
private ItemAnimator mUserItemAnimator;
private int selectedGroup = RecyclerView.NO_POSITION;
private Parcelable selectedStableId;
private boolean firstLayoutPassed;
/**
/
public interface OnGroupClickListener {
/*
*
*/
boolean onGroupClick(ExpandableRecyclerView parent, View v, int groupPosition, long id);
}
public interface OnChildClickListener {
boolean onChildClick(ExpandableRecyclerView parent, View v, int groupPosition, int childPosition, long id);
}
/**
A ViewHolder describes an item view and metadata about its place within the ExpandableRecyclerView.
{@link ExpandableAdapter} implementations should subclass ExpandableViewHolder and add fields for caching
potentially expensive {@link View#findViewById(int)} results.
While {@link LayoutParams} belong to the {@link LayoutManager},
{@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
their own custom ViewHolder implementations to store data that makes binding view contents
easier. Implementations should assume that individual item views will hold strong references
to {@code ExpandableViewHolder} objects and that <{@code ExpandableRecyclerView} instances may hold
strong references to extra off-screen item views for caching purposes
*/
public static class ExpandableViewHolder extends ViewHolder implements OnClickListener {
private ExpandHandler expandHandler;
private boolean expanded;
boolean isSelected;
public ExpandableViewHolder(@nonnull View itemView) {
super(itemView);
itemView.setOnClickListener(this);
}
void setExpandHandler(ExpandHandler expandHandler) {
this.expandHandler = expandHandler;
}
/**
*
*/
protected boolean onViewClicked(View view) {
return false;
}
/**
*
*/
protected void onExpandedChanged() {
}
/**
*
*/
public final boolean isExpanded() {
return expanded;
}
/**
*
*/
public final boolean isSelected() {
return isSelected;
}
@OverRide
public final void onClick(View view) {
if (!onViewClicked(view) && view == itemView && canExpand() && null != expandHandler) {
expandHandler.onViewExpand(this);
}
}
/**
*/
protected boolean canExpand() {
return false;
}
@OverRide
public String toString() {
return super.toString() + ',' + "expanded=" + expanded + ',' + "selected=" + isSelected;
}
}
private static interface ExpandHandler {
void onViewExpand(ExpandableViewHolder holder);
}
/**
Base class for an ExpandableAdapter
Adapters provide a binding from an app-specific data set to views that are displayed
within a {@link ExpandableRecyclerView}.
*/
public static abstract class ExpandableAdapter<VH extends ExpandableViewHolder, T> extends Adapter implements ExpandHandler {
public static final boolean DEBUG = BuildConfig.DEBUG && false;
protected static final String LOG_TAG = "ExpandableRecyclerView";
public static final class LongParcelable implements Parcelable {
public static final Creator CREATOR = new Creator() {
@OverRide
public LongParcelable createFromParcel(Parcel in) {
return new LongParcelable(in);
}
}
private ExpandableRecyclerView recyclerView;
/**
*
*/
@nonnull
protected abstract VH onCreateExpandableViewHolder(ViewGroup parent, int viewType);
/**
position
parameter while acquiring the related data item inside this*
*/
protected abstract void onBindGroupView(VH holder, int groupPosition);
/**
position
parameter while acquiring the related data item inside this*
*/
protected abstract void onBindChildView(VH holder, int groupPosition, int childPosition);
/**
*/
protected abstract int getGroupCount();
/**
*/
protected abstract int getChildrenCount(int groupPosition);
/**
Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types. *
*/
protected abstract int getGroupViewType(int groupPosition);
/**
Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types. *
*/
protected abstract int getChildViewType(int groupPosition, int childPosition);
/**
*
*/
public abstract T getGroup(int groupPosition);
private int expandedPosition = RecyclerView.NO_POSITION;
private final static int CHILD_CLICK = -100;
private int expandedChildCount;
private boolean useLegacyStableIds;
private Parcelable expandedStableId;
@OverRide
public final long getItemId(int groupPosition) {
Parcelable stableId = getGroupStableId(groupPosition);
if (null == stableId)
return NO_ID;
return stableId.hashCode();
}
@OverRide
public final VH onCreateViewHolder(ViewGroup parent, int viewType) {
if (DEBUG) Log.d(LOG_TAG, this + " onCreateViewHolder(type=" + viewType + ')');
VH result = onCreateExpandableViewHolder(parent, viewType);
return result;
}
@OverRide
public final void onBindViewHolder(VH holder, int groupPosition) {
if (DEBUG)
Log.d(LOG_TAG, this + " onBindViewHolder(pos=" + groupPosition + ") expanded=" + expandedPosition + " count=" + expandedChildCount);
}
private void setExpandedViewHolder(@nonnull ExpandableViewHolder expandedViewHolder, boolean isExpanded, boolean forceUpdate) {
if (forceUpdate || isExpanded != expandedViewHolder.isExpanded()) {
if (DEBUG)
Log.d(LOG_TAG, this + " setExpandedViewHolder(" + expandedViewHolder + ")=" + isExpanded);
expandedViewHolder.expanded = isExpanded;
expandedViewHolder.onExpandedChanged();
}
}
/**
/
public static final int STABLE_IDS_NONE = 0;
/*
/
public static final int STABLE_IDS_LONG = 1;
/*
*/
public static final int STABLE_IDS_PARCELABLE = 2;
@IntDef({STABLE_IDS_NONE, STABLE_IDS_LONG, STABLE_IDS_PARCELABLE})
public @interface StableIdsMode {
}
/**
*
*/
@deprecated
@OverRide
public final void setHasStableIds(boolean hasStableIds) {
throw new IllegalAccessError("use setStableIdsMode()");
}
/**
It can be {@link #STABLE_IDS_NONE}, {@link #STABLE_IDS_LONG} or {@link #STABLE_IDS_PARCELABLE}.
You must override {@link #getGroupStableId(int)} and {@link #getGroupStableIdPosition(Parcelable)} for this to work properly.
*/ public void setStableIdsMode(@StableIdsMode int stableIdsMode) { if (stableIdsMode == STABLE_IDS_NONE) { super.setHasStableIds(false); useLegacyStableIds = false; } else { super.setHasStableIds(true); useLegacyStableIds = stableIdsMode == STABLE_IDS_LONG; } }/**
*/
protected long getGroupId(int groupPosition) {
return NO_ID;
}
/**
Used when {@link #STABLE_IDS_PARCELABLE} is set on {@link #setStableIdsMode(int)}.
*/ protected Parcelable getGroupStableId(int groupPosition) { return null; }/**
*/
protected int getGroupStableIdPosition(Parcelable stableId) {
return NO_POSITION;
}
@OverRide
public final int getItemCount() {
return getGroupCount() + (expandedPosition != RecyclerView.NO_POSITION ? expandedChildCount : 0);
}
@OverRide
public final int getItemViewType(int groupPosition) {
final int viewType;
if (expandedPosition == RecyclerView.NO_POSITION || groupPosition <= expandedPosition) {
viewType = getGroupViewType(groupPosition);
if (viewType < 0) {
throw new IllegalStateException("invalid viewType " + viewType + " for position " + groupPosition + " expandedPosition=" + expandedPosition + " expandedChildCount=" + expandedChildCount);
}
} else if (groupPosition - expandedPosition <= expandedChildCount) {
viewType = getChildViewType(expandedPosition, groupPosition - expandedPosition - 1);
if (viewType < 0) {
throw new IllegalStateException("invalid viewType " + viewType + " for position " + groupPosition + " expandedPosition=" + expandedPosition + " expandedChildCount=" + expandedChildCount);
}
} else {
viewType = getGroupViewType(groupPosition - expandedChildCount);
if (viewType < 0) {
throw new IllegalStateException("invalid viewType " + viewType + " for position " + groupPosition + " expandedPosition=" + expandedPosition + " expandedChildCount=" + expandedChildCount);
}
}
if (DEBUG)
Log.v(LOG_TAG, this + " getItemViewType(" + groupPosition + ") =" + viewType);
}
protected boolean setExpandedPosition(int expandedGroupPosition) {
if (expandedGroupPosition >= getGroupCount()) {
if (DEBUG)
Log.d(LOG_TAG, ExpandableAdapter.this + " the expanded position is not valid anymore expandedPosition=" + expandedGroupPosition + " groupCount=" + getGroupCount());
expandedGroupPosition = RecyclerView.NO_POSITION;
}
}
private int getHolderGroupPosition(ExpandableViewHolder holder, boolean strict) {
int holderGroupPosition = holder.getPosition();
if (holderGroupPosition != RecyclerView.NO_POSITION) {
holderGroupPosition -= getHeaderViewsCount();
}
@OverRide
public final void onViewExpand(ExpandableViewHolder holder) {
int holderGroupPosition = getHolderGroupPosition(holder, true);
if (DEBUG)
Log.w(LOG_TAG, this + " onViewExpand groupPos=" + holderGroupPosition + " holder=" + holder + " recyclerView=" + recyclerView);
if (null != recyclerView.onGroupClickListener && recyclerView.onGroupClickListener.onGroupClick(recyclerView, holder.itemView, holderGroupPosition, 0))
return; // tap already handled
}
void attachRecyclerView(ExpandableRecyclerView recyclerView) {
if (DEBUG)
Log.w(LOG_TAG, this + " attachRecyclerView recyclerView=" + recyclerView + " was " + this.recyclerView);
this.recyclerView = recyclerView;
}
@OverRide
public void onViewRecycled(VH holder) {
super.onViewRecycled(holder);
holder.setExpandHandler(null);
}
/**
Same as {@link #notifyDataSetChanged()} but overridable.
*/
public void notifyDataChanged() {
if (DEBUG) Log.i(LOG_TAG, this + " notifyDataChanged recyclerView=" + recyclerView);
if (null != recyclerView) {
recyclerView.stopScroll();
}
notifyDataSetChanged();
if (expandedStableId != null) {
// recover the position of the old expanded element (depends on stable IDs)
if (!useLegacyStableIds) {
if (DEBUG)
Log.i(LOG_TAG, this + " notifyDataChanged recovering expanded position for " + expandedStableId);
setExpandedPosition(getGroupStableIdPosition(expandedStableId));
}
} else {
setExpandedPosition(expandedPosition);
}
if (null != recyclerView && recyclerView.selectedStableId != null) {
if (!useLegacyStableIds) {
if (DEBUG)
Log.i(LOG_TAG, this + " notifyDataChanged recovering selected position for " + expandedStableId);
recyclerView.selectedGroup = getGroupStableIdPosition(recyclerView.selectedStableId);
}
}
}
private int getHeaderViewsCount() {
return recyclerView == null ? 0 : recyclerView.getHeaderViewsCount();
}
/**
Notifies the item at group position changed and the display should be updated.
*/
public void notifyGroupChanged(int groupPosition) {
if (null == recyclerView)
return;
final int modifiedStart;
final int itemChangedCount;
if (expandedPosition == RecyclerView.NO_POSITION || groupPosition < expandedPosition) {
// the modified item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
} else if (groupPosition == expandedPosition) {
// the modified item is the expanded item
itemChangedCount = expandedChildCount + 1;
modifiedStart = groupPosition;
} else {
// the modified item is after the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition + expandedChildCount;
}
if (DEBUG)
Log.d(LOG_TAG, this + " notifyGroupChanged(" + groupPosition + ") start=" + modifiedStart + " count=" + itemChangedCount + " expanded=" + expandedPosition + " headerCount=" + getHeaderViewsCount());
recyclerView.changeRange(modifiedStart, itemChangedCount);
}
/**
Notifies an item has been inserted at group position. The item insertion will be animated.
*/
public void notifyGroupInserted(int groupPosition) {
if (null == recyclerView)
return;
final int modifiedStart;
final int itemChangedCount;
if (expandedPosition == RecyclerView.NO_POSITION) {
// the inserted item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
} else if (groupPosition < expandedPosition) {
// the inserted item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
setExpandedPosition(expandedPosition + 1);
} else if (groupPosition == expandedPosition) {
// the inserted item is over the expanded item
itemChangedCount = expandedChildCount + 1;
modifiedStart = groupPosition;
setExpandedPosition(expandedPosition + 1);
} else {
// the inserted item is after the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition + expandedChildCount;
}
if (DEBUG)
Log.d(LOG_TAG, this + " notifyGroupInserted(" + groupPosition + ") start=" + modifiedStart + " count=" + itemChangedCount + " expanded=" + expandedPosition + " headerCount=" + getHeaderViewsCount());
recyclerView.insertRange(modifiedStart, itemChangedCount);
}
/**
Notifies the item at group position has been removed. The item removal will be animated.
*/
public void notifyGroupRemoved(int groupPosition) {
if (null == recyclerView)
return;
final int modifiedStart;
final int itemChangedCount;
if (expandedPosition == RecyclerView.NO_POSITION || groupPosition < expandedPosition) {
// the removed item is before the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition;
} else if (groupPosition == expandedPosition) {
// the removed item is the expanded item
itemChangedCount = expandedChildCount + 1;
modifiedStart = groupPosition;
setExpandedPosition(RecyclerView.NO_POSITION);
} else {
// the removed item is after the expanded item
itemChangedCount = 1;
modifiedStart = groupPosition + expandedChildCount;
setExpandedPosition(expandedPosition - 1);
}
if (DEBUG)
Log.d(LOG_TAG, this + " notifyGroupRemoved(" + groupPosition + ") start=" + modifiedStart + " count=" + itemChangedCount + " expanded=" + expandedPosition + " headerCount=" + getHeaderViewsCount());
recyclerView.removeRange(modifiedStart, itemChangedCount);
}
/**
Get the currently expanded element or {@code null} if no item is expanded.
*/
public
@nullable
T getExpandedGroup() {
if (expandedPosition == RecyclerView.NO_POSITION)
return null;
return getGroup(expandedPosition);
}
}
public ExpandableRecyclerView(Context context) {
super(context);
init();
}
public ExpandableRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ExpandableRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mUserItemAnimator = super.getItemAnimator();
if (DEBUG_ANIMATOR) Log.d(ANIM_TAG, "init user animator to " + mUserItemAnimator);
}
@OverRide
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!firstLayoutPassed) {
if (ExpandableAdapter.DEBUG) Log.d(ExpandableAdapter.LOG_TAG, this + " first layout");
firstLayoutPassed = true;
}
super.onLayout(changed, l, t, r, b);
}
public boolean isFirstLayoutPassed() {
return firstLayoutPassed;
}
@OverRide
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
@OverRide
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
/**
*/
@deprecated
@OverRide
public void setAdapter(Adapter adapter) {
if (adapter != null && !(adapter instanceof ExpandableAdapter))
throw new IllegalStateException("use a ExpandableAdapter not " + adapter);
setExpandableAdapter((ExpandableAdapter) adapter);
}
public void setExpandableAdapter(ExpandableAdapter adapter) {
if (getAdapter() instanceof ExpandableAdapter) {
ExpandableAdapter expandableAdapter = (ExpandableAdapter) getAdapter();
expandableAdapter.attachRecyclerView(null);
}
}
public ExpandableAdapter getExpandableAdapter() {
return (ExpandableAdapter) getAdapter();
}
/**
*/
@OverRide
public ItemAnimator getItemAnimator() {
return mUserItemAnimator;
}
/**
*
*/
@OverRide
public void setItemAnimator(final ItemAnimator animator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "setItemAnimator to " + animator + " mUserItemAnimator=" + mUserItemAnimator);
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR) Log.d(ANIM_TAG, " change user animator");
super.setItemAnimator(animator);
}
mUserItemAnimator = animator;
}
private void expandAndCollapse(final int expandPosition, final int collapsePosition) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "expandAndCollapse " + expandPosition + '/' + collapsePosition + " currentAnimator=" + super.getItemAnimator());
}
private void doExpandAndCollapse(final int expandPosition, final int collapsePosition) {
ExpandableAdapter expandableAdapter = getExpandableAdapter();
boolean collapseChanged = expandableAdapter.expandedPosition != RecyclerView.NO_POSITION && collapsePosition == expandableAdapter.expandedPosition;
// collapse
if (collapseChanged) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "collapse group " + collapsePosition + " in " + getExpandableAdapter());
getAdapter().notifyItemRangeRemoved(collapsePosition + getHeaderViewsCount() + 1, expandableAdapter.getChildrenCount(collapsePosition));
expandableAdapter.setExpandedPosition(RecyclerView.NO_POSITION);
}
}
/**
*/
public void expandGroup(final int groupPosition) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "expandGroup " + groupPosition);
expandAndCollapse(groupPosition, RecyclerView.NO_POSITION);
}
/**
*/
public void collapseGroup(int groupPosition) {
if (ExpandableAdapter.DEBUG)
Log.d(ExpandableAdapter.LOG_TAG, "collapseGroup " + groupPosition);
expandAndCollapse(RecyclerView.NO_POSITION, groupPosition);
}
private void doSetSelectedGroup(int groupPosition) {
if (groupPosition < 0)
groupPosition = RecyclerView.NO_POSITION;
}
/**
Equivalent of {@link ExpandableListView#setSelectedGroup(int)} for an {@code ExpandableRecyclerView}.
*/
public void setSelectedGroup(final int groupPosition) {
if (ExpandableAdapter.DEBUG)
Log.v(ExpandableAdapter.LOG_TAG, "setSelectedGroup(" + groupPosition + ')');
final ItemAnimator currentItemAnimator = super.getItemAnimator();
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "setSelectedGroup current animator=" + currentItemAnimator);
if (super.getItemAnimator() != null) {
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() {
@OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before selectGroup finished, do item selection");
doSetSelectedGroup(groupPosition);
}
});
return;
}
doSetSelectedGroup(groupPosition);
}
/**
*/
public long getSelectedPosition() {
if (selectedGroup == RecyclerView.NO_POSITION)
return ExpandableListView.PACKED_POSITION_VALUE_NULL;
return ExpandableListView.getPackedPositionForGroup(selectedGroup);
}
public void collapseAll() {
if (ExpandableAdapter.DEBUG) Log.d(ExpandableAdapter.LOG_TAG, "collapseAll");
expandAndCollapse(RecyclerView.NO_POSITION, getExpandableAdapter().expandedPosition);
}
/**
*/
public void setOnGroupExpandListener(ExpandableListView.OnGroupExpandListener onGroupExpandListener) {
this.onGroupExpandListener = onGroupExpandListener;
}
/**
*/
public void setOnGroupCollapseListener(ExpandableListView.OnGroupCollapseListener onGroupCollapseListener) {
this.onGroupCollapseListener = onGroupCollapseListener;
}
/**
*/
public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) {
this.onGroupClickListener = onGroupClickListener;
}
public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
this.onChildClickListener = onChildClickListener;
}
private void changeRange(final int groupPosition, final int childCount) {
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "changeRange with current animator mUserItemAnimator=" + mUserItemAnimator);
getAdapter().notifyItemRangeChanged(groupPosition + getHeaderViewsCount(), childCount);
} else if (super.getItemAnimator() == null) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "changeRange with no current animator mUserItemAnimator=" + mUserItemAnimator);
super.setItemAnimator(mUserItemAnimator);
getAdapter().notifyItemRangeChanged(groupPosition + getHeaderViewsCount(), childCount);
} else {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "changeRange with current custom animator " + super.getItemAnimator() + " isRunning=" + super.getItemAnimator().isRunning());
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() {
@OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before changeRange finished set mUserItemAnimator");
if (ExpandableRecyclerView.super.getItemAnimator() != mUserItemAnimator) {
ExpandableRecyclerView.super.setItemAnimator(mUserItemAnimator);
}
changeRange(groupPosition, childCount);
}
});
}
}
private void insertRange(final int groupPosition, final int childCount) {
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "insertRange with current animator mUserItemAnimator=" + mUserItemAnimator);
getAdapter().notifyItemRangeInserted(groupPosition + getHeaderViewsCount(), childCount);
} else if (super.getItemAnimator() == null) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "insertRange with no current animator mUserItemAnimator=" + mUserItemAnimator);
super.setItemAnimator(mUserItemAnimator);
getAdapter().notifyItemRangeInserted(groupPosition + getHeaderViewsCount(), childCount);
} else {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "insertRange with current custom animator " + super.getItemAnimator() + " isRunning=" + super.getItemAnimator().isRunning());
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() {
@OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before insertRange finished set mUserItemAnimator");
if (ExpandableRecyclerView.super.getItemAnimator() != mUserItemAnimator) {
ExpandableRecyclerView.super.setItemAnimator(mUserItemAnimator);
}
insertRange(groupPosition, childCount);
}
});
}
}
private void removeRange(final int groupPosition, final int childCount) {
if (super.getItemAnimator() == mUserItemAnimator) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "removeRange with current animator mUserItemAnimator=" + mUserItemAnimator);
getAdapter().notifyItemRangeRemoved(groupPosition + getHeaderViewsCount(), childCount);
} else if (super.getItemAnimator() == null) {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "removeRange with no current animator mUserItemAnimator=" + mUserItemAnimator);
super.setItemAnimator(mUserItemAnimator);
getAdapter().notifyItemRangeRemoved(groupPosition + getHeaderViewsCount(), childCount);
} else {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "removeRange with current custom animator " + super.getItemAnimator() + " isRunning=" + super.getItemAnimator().isRunning());
super.getItemAnimator().isRunning(new ItemAnimator.ItemAnimatorFinishedListener() {
@OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before removeRange finished set mUserItemAnimator");
if (ExpandableRecyclerView.super.getItemAnimator() != mUserItemAnimator) {
ExpandableRecyclerView.super.setItemAnimator(mUserItemAnimator);
}
removeRange(groupPosition, childCount);
}
});
}
}
private final Runnable refreshDisplay = new Runnable() {
@OverRide
public void run() {
if (getLayoutManager() instanceof LinearLayoutManager && isFirstLayoutPassed() && getExpandableAdapter() != null) {
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
int first = layoutManager.findFirstVisibleItemPosition();
int last = layoutManager.findLastVisibleItemPosition();
getAdapter().notifyItemRangeChanged(first, last);
}
}
};
/**
*/
public void refreshDisplay() {
final ItemAnimator currentItemAnimator = getItemAnimator();
if (DEBUG_ANIMATOR)
Log.d(ANIM_TAG, "refreshDisplay current animator=" + currentItemAnimator + " running=" + (currentItemAnimator != null && currentItemAnimator.isRunning()));
if (currentItemAnimator == null) {
removeCallbacks(refreshDisplay);
post(refreshDisplay);
} else {
currentItemAnimator.isRunning(new ItemAnimator.ItemAnimatorFinishedListener() {
@OverRide
public void onAnimationsFinished() {
if (DEBUG_ANIMATOR)
Log.i(ANIM_TAG, "animation before refreshDisplay with current animator=" + currentItemAnimator + " finished, do refresh");
removeCallbacks(refreshDisplay);
post(refreshDisplay);
}
});
}
}
private class ExpandAndCollapseItemAnimator extends DefaultItemAnimator {
private final int expandPosition;
private final int collapsePosition;
private boolean expandListenerCalled;
private boolean collapseListenerCalled;
}
static class SavedState extends AbsSavedState {
}
@OverRide
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
state.selectedStableId = this.selectedStableId;
ExpandableAdapter adapter = getExpandableAdapter();
state.expandedStableId = adapter.expandedStableId;
return state;
}
@OverRide
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
this.selectedStableId = savedState.selectedStableId;
ExpandableAdapter adapter = getExpandableAdapter();
if (null != adapter && adapter.hasStableIds()) {
adapter.expandedStableId = savedState.expandedStableId;
}
}
}
The text was updated successfully, but these errors were encountered: