Skip to content

Instantly share code, notes, and snippets.

@TonicArtos
Created November 17, 2015 23:27
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 TonicArtos/c5df9906547655e3de94 to your computer and use it in GitHub Desktop.
Save TonicArtos/c5df9906547655e3de94 to your computer and use it in GitHub Desktop.
SuperSLiM Adapter - Not quite finished.
package com.tonicartos.superslim.adapter;
/**
*
*/
public interface Item {
int getType();
}
package com.tonicartos.superslim.adapter;
import com.tonicartos.superslim.BuildConfig;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
class ItemManager {
private final ArrayList<ItemNode> mItems;
private SectionGraphAdapter mSectionGraphAdapter;
private boolean mInMassRemoval;
public ItemManager(SectionGraphAdapter sectionGraphAdapter) {
mSectionGraphAdapter = sectionGraphAdapter;
mItems = new ArrayList<>();
}
public ItemNode get(int position) {
return mItems.get(position);
}
public void insert(int position, ItemNode item) {
mItems.add(position, item);
if (BuildConfig.DEBUG) {
// TODO: Change this check to BuildConfig.FLAVOUR == "test".
return;
}
mSectionGraphAdapter.notifyItemInserted(position);
}
public void insert(int start, List<ItemNode> items) {
for (int i = 0; i < items.size(); i++) {
mItems.add(start + i, items.get(i));
}
if (BuildConfig.DEBUG) {
return;
}
mSectionGraphAdapter.notifyItemRangeInserted(start, items.size());
}
public void move(int from, int to) {
to -= (from < to) ? 1 : 0;
mItems.add(to, mItems.remove(from));
if (BuildConfig.DEBUG) {
return;
}
mSectionGraphAdapter.notifyItemMoved(from, to);
}
public void remove(int position) {
mItems.remove(position);
if (BuildConfig.DEBUG) {
return;
}
mSectionGraphAdapter.notifyItemRemoved(position);
}
public void removeRange(int start, int range) {
for (int i = 0; i < range; i++) {
mItems.remove(start);
}
if (BuildConfig.DEBUG) {
return;
}
mSectionGraphAdapter.notifyItemRangeRemoved(start, range);
}
public void update(int position, ItemNode item) {
mItems.set(position, item);
if (BuildConfig.DEBUG) {
return;
}
mSectionGraphAdapter.notifyItemChanged(position);
}
int getItemCount() {
return mItems.size();
}
}
package com.tonicartos.superslim.adapter;
/**
*
*/
class ItemNode extends Node {
private final Item mItem;
ItemNode(Section parent, Item data) {
mItem = data;
setParent(parent);
}
@Override
public int getChildCount() {
return 0;
}
@Override
public int getTotalItems() {
return 1;
}
@Override
public boolean isItem() {
return true;
}
@Override
public boolean isSection() {
return false;
}
@Override
void insertItemsToAdapter() {
mItemManager.insert(getPositionInAdapter(), this);
}
@Override
void removeItemsFromAdapter() {
mItemManager.remove(getPositionInAdapter());
}
@Override
public String toString() {
return mItem.toString();
}
Item getItem() {
return mItem;
}
}
package com.tonicartos.superslim.adapter;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.List;
/**
*
*/
abstract class Node {
protected static final ItemManager DUMMY_MANAGER = new DummyItemManager();
@NonNull
protected ItemManager mItemManager = DUMMY_MANAGER;
@Nullable
protected Section mParent;
protected int mPositionInParent;
private int mPeersItemsBeforeThis;
public abstract int getChildCount();
@Nullable
public Section getParent() {
return mParent;
}
void setParent(@NonNull Section parent) {
mParent = parent;
}
public int getPositionInAdapter() {
if (mParent == null) {
return 0;
}
return mParent.getPositionInAdapter() + mPeersItemsBeforeThis;
}
public int getPositionInParent() {
return mPositionInParent;
}
public void setPositionInParent(int positionInParent) {
mPositionInParent = positionInParent;
}
public abstract int getTotalItems();
public abstract boolean isItem();
public abstract boolean isSection();
void changePeersItemsBeforeThis(int change) {
mPeersItemsBeforeThis += change;
}
void changePositionInParent(int change) {
mPositionInParent += change;
}
int getPeersItemsBeforeThis() {
return mPeersItemsBeforeThis;
}
void setPeersItemsBeforeThis(int itemsBefore) {
mPeersItemsBeforeThis = itemsBefore;
}
abstract void insertItemsToAdapter();
abstract void removeItemsFromAdapter();
void reset() {
mParent = null;
setItemManager(DUMMY_MANAGER);
}
void setItemManager(@NonNull ItemManager itemManager) {
mItemManager = itemManager;
}
public static class DummyItemManager extends ItemManager {
public DummyItemManager() {
super(null);
}
@Override
public ItemNode get(int position) {
return null;
}
@Override
public void insert(int position, ItemNode item) {
}
@Override
public void insert(int start, List<ItemNode> items) {
}
@Override
public void move(int from, int to) {
}
@Override
public void remove(int position) {
}
@Override
public void removeRange(int start, int range) {
}
@Override
public void update(int position, ItemNode item) {
}
@Override
int getItemCount() {
return 0;
}
}
}
package com.tonicartos.superslim.adapter;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
/**
*
*/
public class Section extends Node {
private ItemNode mHeader;
private ArrayList<Node> mChildren = new ArrayList<>();
private int mTotalItems;
@Nullable
private SectionConfiguration mConfiguration;
private SectionGraphAdapter.Registration mRegistration;
private boolean mIsCollapsed;
Section() {
}
Section(SectionGraphAdapter.Registration registration) {
mRegistration = registration;
}
public final void add(Section section) {
insert(mChildren.size(), section);
}
public final void add(@NonNull Item item) {
insert(mChildren.size(), item);
}
public void collapseChildren() {
int jumpHeader = mHeader == null ? 0 : 1;
int numItemsToRemove = getTotalItems() - jumpHeader;
mItemManager.removeRange(getPositionInAdapter() + jumpHeader, numItemsToRemove);
for (int i = 0, size = mChildren.size(); i < size; i++) {
mChildren.get(i).reset();
}
mTotalItems -= numItemsToRemove;
if (mParent != null) {
mParent.totalItemsChanged(mPositionInParent, -numItemsToRemove);
}
mIsCollapsed = true;
}
/**
* Deregisters this section and all descendant sections from the adapter.
*/
public void deregister() {
for (int i = 0, size = mChildren.size(); i < size; i++) {
if (mChildren.get(i).isSection()) {
((Section) mChildren.get(i)).deregister();
}
}
mRegistration.deregister();
}
public void expandChildren() {
int numItemsAdded = 0;
for (int i = 0, size = mChildren.size(); i < size; i++) {
Node child = mChildren.get(i);
initChild(i, child);
// Add actual item hierarchy to adapter and this section.
child.insertItemsToAdapter();
// Update the number of descendants for this section.
numItemsAdded += child.getTotalItems();
}
totalItemsChanged(numItemsAdded);
mIsCollapsed = false;
}
/**
* Warning! This method has an unchecked generic return. Return type will be one of Item or
* Section. To be safe you should check the item type first by calling {@link
* Section#isPositionItem(int)} or {@link Section#isPositionSection(int)}.
*/
public <T> T get(int position) {
Node node = mChildren.get(position);
if (node.isItem()) {
return (T) ((ItemNode) node).getItem();
}
return (T) node;
}
public int getAdapterPositionOfChild(int position) {
return mChildren.get(position).getPositionInAdapter();
}
@Override
public int getChildCount() {
return mChildren.size();
}
@Override
public int getTotalItems() {
return mTotalItems;
}
@Override
public boolean isItem() {
return false;
}
@Override
public boolean isSection() {
return true;
}
@Override
void insertItemsToAdapter() {
if (mHeader != null) {
mHeader.setItemManager(mItemManager);
mItemManager.insert(getPositionInAdapter(), mHeader);
}
if (!mIsCollapsed) {
for (int i = 0, size = mChildren.size(); i < size; i++) {
Node child = mChildren.get(i);
child.setItemManager(mItemManager);
child.insertItemsToAdapter();
}
}
}
@Override
void removeItemsFromAdapter() {
mItemManager.removeRange(getPositionInAdapter(), getTotalItems());
}
@Override
void setItemManager(@NonNull ItemManager itemManager) {
super.setItemManager(itemManager);
for (int i = 0, size = mChildren.size(); i < size; i++) {
mChildren.get(i).setItemManager(itemManager);
}
}
public ItemNode getHeader() {
return mHeader;
}
public void setHeader(@NonNull Item item) {
ItemNode header = new ItemNode(this, item);
header.setItemManager(mItemManager);
header.setPeersItemsBeforeThis(0);
if (mHeader != null) {
mItemManager.update(getPositionInAdapter(), header);
} else {
// Now have header.
mItemManager.insert(getPositionInAdapter(), header);
// Update children as we have inserted an item before them.
for (int i = 0, size = mChildren.size(); i < size; i++) {
Node child = mChildren.get(i);
child.changePeersItemsBeforeThis(1);
}
totalItemsChanged(1);
}
mHeader = header;
}
public final void insert(int position, Section section) {
insertChild(position, section);
}
public final void insert(int position, @NonNull Item item) {
insertChild(position, new ItemNode(this, item));
}
public boolean isCollapsed() {
return mIsCollapsed;
}
public boolean isPositionItem(int position) {
return mChildren.get(position).isItem();
}
public boolean isPositionSection(int position) {
return mChildren.get(position).isSection();
}
public final void remove(int position) {
final Node removed = mChildren.remove(position);
if (!mIsCollapsed) {
removed.removeItemsFromAdapter();
final int itemChange = -removed.getTotalItems();
// Update every child after the removed one.
for (int i = position + 1, size = mChildren.size(); i < size; i++) {
mChildren.get(i).changePeersItemsBeforeThis(itemChange);
mChildren.get(i).changePositionInParent(-1);
}
totalItemsChanged(itemChange);
}
removed.reset();
}
public void removeFromParent() {
if (mParent != null) {
mParent.remove(mPositionInParent);
}
}
public void removeHeader() {
if (mHeader != null) {
mHeader = null;
mItemManager.remove(getPositionInAdapter());
mTotalItems -= 1;
for (int i = 0, size = mChildren.size(); i < size; i++) {
Node child = mChildren.get(i);
child.setPeersItemsBeforeThis(child.getPeersItemsBeforeThis() - 1);
}
if (mParent != null) {
mParent.totalItemsChanged(mPositionInParent, -1);
}
}
}
public void setConfiguration(@NonNull SectionConfiguration configuration) {
mConfiguration = configuration;
}
public void toggleChildren() {
if (mIsCollapsed) {
expandChildren();
} else {
collapseChildren();
}
}
/**
* Update or replace an item.
*
* @param position Position of an item in section to perform update on. The update will fail if
* the position is that of a section.
* @param item Item to replace into the position.
* @return False if the update failed. This should only happen if the target position is a
* section and not an item.
*/
public final boolean update(int position, @NonNull Item item) {
// TODO: Test behaviour when view type changes. Might have to do a replace rather than
// update if old.getItem().getType() != item.getType().
if (mChildren.get(position).isItem()) {
ItemNode old = (ItemNode) mChildren.get(position);
ItemNode update = new ItemNode(this, item);
update.setParent(this);
update.setItemManager(mItemManager);
update.setPositionInParent(position);
update.setPeersItemsBeforeThis(old.getPeersItemsBeforeThis());
if (!mIsCollapsed) {
mItemManager.update(old.getPositionInAdapter(), update);
}
mChildren.set(position, update);
return true;
}
return false;
}
void totalItemsChanged(int childPosition, int change) {
// Update children after child that called in change.
for (int i = childPosition + 1, size = mChildren.size(); i < size; i++) {
mChildren.get(i).changePeersItemsBeforeThis(change);
}
totalItemsChanged(change);
}
private void initChild(int position, Node child) {
child.setItemManager(mItemManager);
child.setParent(this);
// Set child start position.
if (position > 0) {
Node prior = mChildren.get(position - 1);
child.setPeersItemsBeforeThis(
prior.getPeersItemsBeforeThis() + prior.getTotalItems());
} else {
child.setPeersItemsBeforeThis(mHeader == null ? 0 : 1);
}
// Set child position on child, this has to be tracked with inserts and removals but we
// already have to do that to keep the correct index into the adapter item array.
child.setPositionInParent(position);
}
private void insertChild(int position, Node child) {
if (position < 0) {
position = mChildren.size();
}
if (!mIsCollapsed) {
initChild(position, child);
// Add actual item hierarchy to adapter and this section.
child.insertItemsToAdapter();
// Update children after the inserted child.
final int numItemsAdded = child.getTotalItems();
for (int i = position, size = mChildren.size(); i < size; i++) {
mChildren.get(i).changePeersItemsBeforeThis(numItemsAdded);
mChildren.get(i).changePositionInParent(1);
}
totalItemsChanged(numItemsAdded);
}
mChildren.add(position, child);
}
private void totalItemsChanged(int numItemsAdded) {
mTotalItems += numItemsAdded;
if (mParent != null) {
mParent.totalItemsChanged(mPositionInParent, numItemsAdded);
}
}
}
package com.tonicartos.superslim.adapter;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
*
*/
public class SectionConfiguration {
/**
* Header is positioned at the top of the section content. Content starts below the
* header. Inline headers are always sticky. Use the embedded style if you want an
* inline header that is not sticky.
*/
public static final int HEADER_INLINE = 1;
/**
* Header is positioned at the top of the section content. Content starts below the
* header, but the header never becomes sticky. Embedded headers may not be overlays
* either.
*/
public static final int HEADER_EMBEDDED = 2;
/**
* Header is aligned to the start edge of the section. This is the left for LTR
* locales.
* <p/>
* Start aligned headers are always sticky.
*/
public static final int HEADER_START = 4;
/**
* Header is aligned to the end edge of the section. This is the right for LTR locales.
* <p/>
* End aligned headers are always sticky.
*/
public static final int HEADER_END = 8;
/**
* Overlay headers float above the content.
* <p/>
* Overlay headers are always sticky.
*/
public static final int HEADER_OVERLAY = 16;
public static final int MARGIN_AUTO = -1;
static final int CUSTOM_SLM = 0;
private static final int DEFAULT_MARGIN = MARGIN_AUTO;
private static final int DEFAULT_HEADER_STYLE = HEADER_INLINE;
private int mHeaderStyle;
private String mLabel;
private int mHeaderMarginStart;
private int mHeaderMarginEnd;
/**
* Create a new configuration for a custom slm.
*
* @param label A label assigned to a slm when it was added to the layout manager.
*/
public SectionConfiguration(String label) {
this(label, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_HEADER_STYLE);
}
/**
* Create a new configuration for a custom slm. The label identifies a custom slm added to
* the layout manager.
*
* @param label A label assigned to a slm when it was added to the layout
* manager.
* @param headerMarginStart A margin for the section in which the header may be placed if
* it is start aligned. Which edge is the start edge is determined
* by the language locale.
* @param headerMarginEnd A margin for the section in which the header may be placed if
* it is end aligned. Which edge is the end edge is determined by
* the language locale.
* @param headerStyle Header style for this section.
*/
public SectionConfiguration(String label, int headerMarginStart, int headerMarginEnd,
@HeaderStyle int headerStyle) {
this(headerMarginStart, headerMarginEnd, headerStyle);
mLabel = label;
}
SectionConfiguration() {
this(DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_HEADER_STYLE);
}
/**
* @param headerMarginStart A margin for the section in which the header may be placed if
* it is start aligned. Which edge is the start edge is determined
* by the language locale.
* @param headerMarginEnd A margin for the section in which the header may be placed if
* it is end aligned. Which edge is the end edge is determined by
* the language locale.
* @param headerStyle Header style for this section.
*/
SectionConfiguration(int headerMarginStart, int headerMarginEnd, @HeaderStyle int headerStyle) {
mHeaderMarginStart = headerMarginStart;
mHeaderMarginEnd = headerMarginEnd;
mHeaderStyle = headerStyle;
}
public int getHeaderMarginEnd() {
return mHeaderMarginEnd;
}
public void setHeaderMarginEnd(int headerMarginEnd) {
mHeaderMarginEnd = headerMarginEnd;
}
public int getHeaderMarginStart() {
return mHeaderMarginStart;
}
public void setHeaderMarginStart(int headerMarginStart) {
mHeaderMarginStart = headerMarginStart;
}
public int getHeaderStyle() {
return mHeaderStyle;
}
public void setHeaderStyle(int headerStyle) {
mHeaderStyle = headerStyle;
}
/**
* When custom slms are added to the layout manager they are assigned a label by the
* client. The value returned by this method is expected to match one of those labels.
*
* @return A slm label that may match a known one in the layout manager.
*/
String getCustomSlmLabel() {
return mLabel;
}
/**
* Overridden for internal subclasses which have a known slm kind.
*
* @return Kind of slm.
*/
int getSlmKind() {
return CUSTOM_SLM;
}
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {HEADER_INLINE, HEADER_EMBEDDED, HEADER_START, HEADER_END,
HEADER_OVERLAY})
public @interface HeaderStyle {
}
}
package com.tonicartos.superslim.adapter;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import java.util.HashMap;
public abstract class SectionGraphAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private final SectionGraphRoot mSectionGraph;
private final ItemManager mItemManager = new ItemManager(this);
private HashMap<String, Section> mSectionLookup;
public SectionGraphAdapter() {
mSectionGraph = new SectionGraphRoot(mItemManager);
init();
}
public void addSection(Section section) {
mSectionGraph.add(section);
}
public Section createSection(String id) {
return createSection(id, null, null);
}
public Section createSection(String id, @Nullable SectionConfiguration configuration,
@Nullable Item header) {
Section section = new Section(new Registration(id, mSectionLookup));
if (header != null) {
section.setHeader(header);
}
if (configuration != null) {
section.setConfiguration(configuration);
}
registerSection(id, section);
return section;
}
/**
* Deregisters the section from the adapter. This removes the reference that allows the section
* to be accessed directly without having to travers the section graph. Deregistration cascades
* to descended sections. To detach the section from the graph you can call {@link
* Section#removeFromParent()}.
*
* @param id Id by which the parent section of the subgraph is referred by.
* @return Root section of the subgraph.
*/
public Section deregisterSection(String id) {
Section section = mSectionLookup.get(id);
section.deregister();
return section;
}
public int getNumSections() {
return mSectionGraph.getChildCount();
}
public Section getSection(String id) {
return mSectionLookup.get(id);
}
public Section getSection(int position) {
return (Section) mSectionGraph.get(position);
}
public void insertSection(int position, Section section) {
mSectionGraph.insert(position, section);
}
@Override
public final void onBindViewHolder(VH holder, int position) {
ItemNode itemNode = mItemManager.get(position);
onBindViewHolder(holder, itemNode.getItem());
}
@Override
public int getItemViewType(int position) {
return mItemManager.get(position).getItem().getType();
}
@Override
public int getItemCount() {
return mItemManager.getItemCount();
}
public abstract void onBindViewHolder(VH holder, Item item);
/**
* Remove a section from the graph. This section will still be referenced by the adapter unless
* you call {@link SectionGraphAdapter#deregisterSection(String)} or {@link
* Section#deregister()}.
*/
public Section removeSection(int position) {
Section removed = mSectionGraph.get(position);
mSectionGraph.remove(position);
return removed;
}
void init() {
mSectionLookup = new HashMap<>();
}
void registerSection(String id, Section section) {
mSectionLookup.put(id, section);
}
/**
*
*/
static final class SectionGraphRoot extends Section {
SectionGraphRoot(ItemManager itemManager) {
mItemManager = itemManager;
}
}
static class Registration {
private final String mId;
private final HashMap<String, Section> mSectionLookup;
public Registration(String id, HashMap<String, Section> sectionLookup) {
mId = id;
mSectionLookup = sectionLookup;
}
public void deregister() {
mSectionLookup.remove(mId);
}
}
}
@au-phiware
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment