Skip to content

Instantly share code, notes, and snippets.

@consp1racy
Last active January 15, 2018 17:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save consp1racy/0d70656167d9b46367c8deb4018c93c0 to your computer and use it in GitHub Desktop.
Save consp1racy/0d70656167d9b46367c8deb4018c93c0 to your computer and use it in GitHub Desktop.
package com.xwray.groupie;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.util.DiffUtil;
import android.support.v7.util.ListUpdateCallback;
import java.util.ArrayList;
import java.util.Collection;
/**
* A group which has a list of contents and an optional header and footer.
* <p>
* https://github.com/lisawray/groupie/issues/151
*/
public final class Section2 extends NestedGroup {
@Nullable
private Group header;
@Nullable
private Group footer;
@Nullable
private Group placeholder;
private final ArrayList<Group> children = new ArrayList<>();
private boolean hideWhenEmpty = false;
private boolean isHeaderAndFooterVisible = true;
private boolean isPlaceholderVisible = false;
public Section2() {
this(null, new ArrayList<Group>());
}
public Section2(@Nullable Group header) {
this(header, new ArrayList<Group>());
}
public Section2(@NonNull Collection<? extends Group> children) {
this(null, children);
}
public Section2(@Nullable Group header, @NonNull Collection<? extends Group> children) {
this.header = header;
addAll(children);
}
@Override
public void add(int position, @NonNull Group group) {
super.add(position, group);
children.add(position, group);
final int notifyPosition = getHeaderItemCount() + getItemCount(children.subList(0, position));
notifyItemRangeInserted(notifyPosition, group.getItemCount());
refreshEmptyState();
}
@Override
public void addAll(@NonNull Collection<? extends Group> groups) {
if (groups.isEmpty()) return;
super.addAll(groups);
int position = getItemCountWithoutFooter();
this.children.addAll(groups);
notifyItemRangeInserted(position, getItemCount(groups));
refreshEmptyState();
}
@Override
public void addAll(int position, @NonNull Collection<? extends Group> groups) {
if (groups.isEmpty()) {
return;
}
super.addAll(position, groups);
this.children.addAll(position, groups);
final int notifyPosition = getHeaderItemCount() + getItemCount(children.subList(0, position));
notifyItemRangeInserted(notifyPosition, getItemCount(groups));
refreshEmptyState();
}
@Override
public void add(@NonNull Group group) {
super.add(group);
int position = getItemCountWithoutFooter();
children.add(group);
notifyItemRangeInserted(position, group.getItemCount());
refreshEmptyState();
}
@Override
public void remove(@NonNull Group group) {
super.remove(group);
int position = getItemCountBeforeGroup(group);
children.remove(group);
notifyItemRangeRemoved(position, group.getItemCount());
refreshEmptyState();
}
/** @deprecated Don't use until behavior is verified error-free. */
@Deprecated
@Override
public void removeAll(@NonNull Collection<? extends Group> groups) {
if (groups.isEmpty()) {
return;
}
super.removeAll(groups);
for (Group group : groups) {
int position = getItemCountBeforeGroup(group);
children.remove(group);
notifyItemRangeRemoved(position, group.getItemCount());
}
refreshEmptyState();
}
/**
* Replace all existing body content and dispatch fine-grained change notifications to the
* parent using DiffUtil.
* <p>
* Item comparisons are made using:
* - Item.isSameAs(Item otherItem) (are items the same?)
* - Item.equals() (are contents the same?)
* <p>
* If you don't customize getId() or isSameAs() and equals(), the default implementations will return false,
* meaning your Group will consider every update a complete change of everything.
*
* @param groups The new content of the section
*/
public void update(@NonNull final Collection<? extends Group> groups) {
// Dummy section to give us access to the flattened list of items in the new groups.
final Section2 section = new Section2(groups);
final int headerItemCount = getHeaderItemCount();
final int oldBodyItemCount = getItemCount(children);
final int newBodyItemCount = section.getItemCount();
final DiffUtil.DiffResult diffResult;
if (oldBodyItemCount > 0 && newBodyItemCount > 0) {
diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldBodyItemCount;
}
@Override
public int getNewListSize() {
return newBodyItemCount;
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Item oldItem = getItem(headerItemCount + oldItemPosition);
Item newItem = section.getItem(newItemPosition);
return newItem.isSameAs(oldItem);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Item oldItem = getItem(headerItemCount + oldItemPosition);
Item newItem = section.getItem(newItemPosition);
return newItem.equals(oldItem);
}
});
} else {
diffResult = null;
}
super.removeAll(groups);
children.clear();
children.addAll(groups);
super.addAll(groups);
if (diffResult != null) {
diffResult.dispatchUpdatesTo(listUpdateCallback);
} else if (newBodyItemCount == 0) {
notifyItemRangeRemoved(headerItemCount, oldBodyItemCount);
refreshEmptyState();
} else if (oldBodyItemCount == 0) {
notifyItemRangeInserted(headerItemCount, newBodyItemCount);
refreshEmptyState();
}
}
private ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(getHeaderItemCount() + position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(getHeaderItemCount() + position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
final int headerItemCount = getHeaderItemCount();
notifyItemMoved(headerItemCount + fromPosition, headerItemCount + toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
notifyItemRangeChanged(getHeaderItemCount() + position, count);
}
};
/**
* Optional. Set a placeholder for when the section's body is empty.
* <p>
* If setHideWhenEmpty(true) is set, then the empty placeholder will not be shown.
*
* @param placeholder A placeholder to be shown when there is no body content
*/
public void setPlaceholder(@NonNull Group placeholder) {
//noinspection ConstantConditions
if (placeholder == null)
throw new NullPointerException("Placeholder can't be null. Please use removePlaceholder() instead!");
if (this.placeholder != null) {
removePlaceholder();
}
this.placeholder = placeholder;
refreshEmptyState();
}
public void removePlaceholder() {
hidePlaceholder();
this.placeholder = null;
}
private void showPlaceholder() {
if (isPlaceholderVisible || placeholder == null) return;
isPlaceholderVisible = true;
notifyItemRangeInserted(getHeaderItemCount(), placeholder.getItemCount());
}
private void hidePlaceholder() {
if (!isPlaceholderVisible || placeholder == null) return;
isPlaceholderVisible = false;
notifyItemRangeRemoved(getHeaderItemCount(), placeholder.getItemCount());
}
/**
* Whether a section's contents are visually empty
*
* @return
*/
protected boolean isEmpty() {
return children.isEmpty() || getItemCount(children) == 0;
}
private void hideDecorations() {
if (!isHeaderAndFooterVisible && !isPlaceholderVisible) return;
int count = getHeaderItemCount() + getPlaceholderItemCount() + getFooterItemCount();
isHeaderAndFooterVisible = false;
isPlaceholderVisible = false;
notifyItemRangeRemoved(0, count);
}
protected void refreshEmptyState() {
boolean isEmpty = isEmpty();
if (isEmpty) {
if (hideWhenEmpty) {
hideDecorations();
} else {
showPlaceholder();
showHeadersAndFooters();
}
} else {
hidePlaceholder();
showHeadersAndFooters();
}
}
private void showHeadersAndFooters() {
if (isHeaderAndFooterVisible) return;
isHeaderAndFooterVisible = true;
notifyItemRangeInserted(0, getHeaderItemCount());
notifyItemRangeInserted(getItemCountWithoutFooter(), getFooterItemCount());
}
private int getBodyItemCount() {
return isPlaceholderVisible ? getPlaceholderItemCount() : getItemCount(children);
}
private int getItemCountWithoutFooter() {
return getBodyItemCount() + getHeaderItemCount();
}
private int getHeaderCount() {
return header == null || !isHeaderAndFooterVisible ? 0 : 1;
}
private int getHeaderItemCount() {
return getHeaderCount() == 0 ? 0 : header.getItemCount();
}
private int getFooterItemCount() {
return getFooterCount() == 0 ? 0 : footer.getItemCount();
}
private int getFooterCount() {
return footer == null || !isHeaderAndFooterVisible ? 0 : 1;
}
private int getPlaceholderCount() {
return isPlaceholderVisible ? 1 : 0;
}
@Override
@NonNull
public Group getGroup(int position) {
if (isHeaderShown() && position == 0) return header;
position -= getHeaderCount();
if (isPlaceholderShown() && position == 0) return placeholder;
position -= getPlaceholderCount();
if (position == children.size()) {
if (isFooterShown()) {
return footer;
} else {
throw new IndexOutOfBoundsException("Wanted group at position " + position +
" but there are only " + getGroupCount() + " groups");
}
} else {
return children.get(position);
}
}
@Override
public int getGroupCount() {
return getHeaderCount() + getFooterCount() + getPlaceholderCount() + children.size();
}
@Override
public int getPosition(@NonNull Group group) {
int count = 0;
if (isHeaderShown()) {
if (group == header) return count;
}
count += getHeaderCount();
if (isPlaceholderShown()) {
if (group == placeholder) return count;
}
count += getPlaceholderCount();
int index = children.indexOf(group);
if (index >= 0) return count + index;
count += children.size();
if (isFooterShown()) {
if (footer == group) {
return count;
}
}
return -1;
}
private boolean isHeaderShown() {
return getHeaderCount() > 0;
}
private boolean isFooterShown() {
return getFooterCount() > 0;
}
private boolean isPlaceholderShown() {
return getPlaceholderCount() > 0;
}
public void setHeader(@NonNull Group header) {
if (header == null)
throw new NullPointerException("Header can't be null. Please use removeHeader() instead!");
int previousHeaderItemCount = getHeaderItemCount();
this.header = header;
notifyHeaderItemsChanged(previousHeaderItemCount);
}
public void removeHeader() {
int previousHeaderItemCount = getHeaderItemCount();
this.header = null;
notifyHeaderItemsChanged(previousHeaderItemCount);
}
private void notifyHeaderItemsChanged(int previousHeaderItemCount) {
int newHeaderItemCount = getHeaderItemCount();
if (previousHeaderItemCount > 0) {
notifyItemRangeRemoved(0, previousHeaderItemCount);
}
if (newHeaderItemCount > 0) {
notifyItemRangeInserted(0, newHeaderItemCount);
}
}
public void setFooter(@NonNull Group footer) {
if (footer == null)
throw new NullPointerException("Footer can't be null. Please use removeFooter() instead!");
int previousFooterItemCount = getFooterItemCount();
this.footer = footer;
notifyFooterItemsChanged(previousFooterItemCount);
}
public void removeFooter() {
int previousFooterItemCount = getFooterItemCount();
this.footer = null;
notifyFooterItemsChanged(previousFooterItemCount);
}
private void notifyFooterItemsChanged(int previousFooterItemCount) {
int newFooterItemCount = getFooterItemCount();
if (previousFooterItemCount > 0) {
notifyItemRangeRemoved(getItemCountWithoutFooter(), previousFooterItemCount);
}
if (newFooterItemCount > 0) {
notifyItemRangeInserted(getItemCountWithoutFooter(), newFooterItemCount);
}
}
public void setHideWhenEmpty(boolean hide) {
if (hideWhenEmpty == hide) return;
hideWhenEmpty = hide;
refreshEmptyState();
}
@Override
public void onItemInserted(@NonNull Group group, int position) {
super.onItemInserted(group, position);
refreshEmptyState();
}
@Override
public void onItemRemoved(@NonNull Group group, int position) {
super.onItemRemoved(group, position);
refreshEmptyState();
}
@Override
public void onItemRangeInserted(@NonNull Group group, int positionStart, int itemCount) {
super.onItemRangeInserted(group, positionStart, itemCount);
refreshEmptyState();
}
@Override
public void onItemRangeRemoved(@NonNull Group group, int positionStart, int itemCount) {
super.onItemRangeRemoved(group, positionStart, itemCount);
refreshEmptyState();
}
private int getPlaceholderItemCount() {
if (isPlaceholderVisible && placeholder != null) {
return placeholder.getItemCount();
}
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment