Skip to content

5.x | Headers and Sections

Davide Steduto edited this page Jan 3, 2017 · 61 revisions

In this page


Section

A Section is composed by a Header item with IHeader interface and a Child item with ISectionable interface which internally holds the same instance of the header item. Read the tutorial of Item interfaces for more details about all item interfaces.

For simplicity, you can directly extend AbstractHeaderItem and AbstractSectionableItem respectively to quickly create a header item and a child item with header. Optionally you can extend/collapse a section (please refer at the end of this page):

public abstract class AbstractHeaderItem<VH extends FlexibleViewHolder>
		extends AbstractFlexibleItem<VH>
		implements IHeader<VH> {

	/**
	 * By default, header is hidden and not selectable
	 */
	public AbstractHeaderItem() {
		setHidden(true);
		setSelectable(false);
	}
}
public abstract class AbstractSectionableItem<VH extends RecyclerView.ViewHolder, T extends IHeader>
		extends AbstractFlexibleItem<VH>
		implements ISectionable<VH, T> {

	/**
	 * The header of this item
	 */
	protected T header;

	public AbstractSectionableItem(T header) {
		this.header = header;
	}

	@Override
	public T getHeader() {
		return header;
	}

	@Override
	public void setHeader(T header) {
		this.header = header;
	}
}

Initialization

There are 2 use cases to represent sections with headers:

1. Use case similar to Instagram or Contacts

Main items are defined as ISectionable that hold an IHeader item, useful to display 1 or more items with the same Header linked.
The adapter list should be initialized only with ISectionable items, while the Header item, not added to the list, must be assigned to the ISectionable items of the same group, using the constructor or the method setHeader().

Header items can be shown all together with a unique command showAllHeaders() OR at startup by calling setDisplayHeadersAtStartUp(true) which internally calls showAllHeaders(). Doing this, the hidden property is changed to false and the header items are inserted at the top of each ISectionable item, wherever the header is still hidden.

Note:

  • By default, headers are NOT shown at startup.
  • As explained in the Item interfaces, it is important to well Override the equals method.

2. Use case to extend/collapse a section

Header item is defined as IExpandable combined with IHeader, containing sub items of type ISectionable. For simplicity, you can directly extend from AbstractExpandableHeaderItem. Here, you should add only the expandable/header items to the Adapter list. To initially expand the section at startup, you need to call expandItemsAtStartUp() in the Activity/Fragment creation phase.

Note:

  • Headers items can be hidden, but be aware that sub items need the own parent (Header) currently shown.

Sticky headers

To make a header sticky, you must follow these 5 steps:

At design time

1. Always wrap the RecyclerView widget with a FrameLayout as following:

<!-- This FrameLayout is still mandatory ONLY IF sticky header will be enabled. This
     layout will help to receive the inflated layout at runtime and to display the
     Refresh circle AND the FastScroll on the top of sticky_header_layout. -->
<FrameLayout
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<android.support.v7.widget.RecyclerView
		android:id="@+id/recycler_view"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		tools:listitem="@layout/recycler_adapter_item"/>

	<!-- NOT NEEDED ANYMORE!! This layout is generated at runtime when
		 sticky headers are enabled. So you can safely remove it. -->
	<!--<include layout="@layout/sticky_header_layout"/>-->

</FrameLayout>

Note: You can still customize your sticky_header_layout by calling:

setStickyHeaders(true, ViewGroup stickyContainer).

2. Implement IHeader interface for the item that will be sticky.

3. You need to pass true to the super() constructor of the Header ViewHolder only. Since itemView is declared final by Android, this parameter allows to insert a FrameLayout holding the real content. This is necessary to avoid App crashing during scrolling (etc.) because the LayoutManager needs the itemView with a contentView filled. The technique adopted actually moves the contentView to the FrameLayout (@layout/sticky_header_layout) changing the parent view!

At runtime

4. Display headers with setDisplayHeadersAtStartUp(true).

5. Call setStickyHeaders(true), headers stay sticky if they are currently shown.

Note: You should call getFlexibleAdapterPosition() instead of getAdapterPosition() only in Header View Holders. For 2 reasons:

  • getAdapterPosition() is declared final and we cannot override it;
  • When scrolling down the RecyclerView, the sticky ViewHolders are created out of the LayoutManager and if clicked in this particular situation, NO_POSITION is returned!

Transparency and Elevation

It is super easy to add the transparency and elevation to the header item, the values are taken from the header item layout:

<RelativeLayout	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:background="#aa7e57c2" <--- transparent background
	android:elevation="5dp">       <--- elevation for header item + sticky header
	...
</RelativeLayout>

But if elevation is not configured in the layout or is Zero, then you have 1 new method to obtain the following effect: header item flat + sticky header elevated:

setStickyHeaderElevation(float stickyElevation);

Bonus from my researches:

  • Transparent Sticky Header with elevation at the same time
  • Surgery precision on header swapping :-)
  • Double background not visible anymore on header swapping, if transparency is set.
  • Fade animation on sticky header container, when sticky is enabled or disabled.

Adding/removing a section

Following the use case 1:

  • To add new section:
    • You can call addSection(header). An empty section (the header) will be added to the top of the list;
    • You can call addSection(header, comparator*). To add the section in the middle of the list with your custom sorting rules.
  • To remove a section:
    • You can delete the header item with removeItem(getGlobalPositionOf(header));.
    • You will have to handle yourself the items not belonging anymore to that header. By default, items keep the link of header, but you can change the default behavior by calling setUnlinkAllItemsOnRemoveHeaders(true).

Following the use case 2:

  • Add the new IExpandable+IHeader item to the Adapter list by simply calling addSection() or addItem(position, item). This section remains collapsed until you call expand(the new position), when you do, be sure to have the expanded property equals to false. Optionally, this property can remain true, but you have to call expand(the new position, init = true): read carefully the JavaDoc.
  • Deleting the IExpandable item, will automatically collapse the expandable and removed from the adapter list together with the sub-items too!

Adding/removing any item in a section

Always keep in mind that, the Adapter list must be manually synchronized with your Database list, meaning that the new item already exists in the Database list (if not, you have to add it first), then you call the Adapter methods to reflect the change and animate it for the user.

To add sub items:

Following the use case 1:

You can call addItemToSection(sectionable, header, comparator*) and to simplify the process calculatePositionFor(item, comparator*) has been introduced, this method accepts a custom Comparator object to extract the precise position where to add the new item in the Adapter list, then call addItem(position, item).

Following the use case 2:

Simply call addSubItem(parentPosition, subPosition, subItem). Section must be already expanded, otherwise it doesn't have any effect, if not, add true as 4th parameter it will be automatically expanded. Example:

public void addChild(AbstractExpandableHeaderItem header) {
    ChildItem childItem = ...
    //Add the child item to the end of the section
    //Note: you don't need the next line, if childItem is already part of the sublist
    header.addSubItem(childItem);//last position
    //Synchronize the Adapter,
    mAdapter.addSubItem(mAdapter.getGlobalPositionOf(header),
            header.getSubItemPosition(childItem),
            childItem);
}

To remove any items, simply call removeItem(the adapter global position). If you have the Undo functionality enabled, you then delete the item from the Database when timeout is over (See UndoHelper class).

* = The Comparator object should be customized to support ALL the types of items the Adapter is displaying or a ClassCastException will be raised. If the Comparator is null the returned position will be 0. See the demo App for a real example.

Dragging with auto-linkage

We can drag an ISectionable item with a header inside or the IHeader item as well. Auto-linkage works in 6 ways:

  • Header item is dragged up.
  • Header item is dragged down.
  • The upper item is dragged below the header item.
  • The underlying item is dragged above the header.
  • The underlying item is dragged lower.
  • A lower item is dragged up and becomes a new underlying item.

The ISectionable item is updated with the following behaviors:

  • Item moved out of any section has a null header.
  • Item moved into another section updates the header.
  • Item moved inside the same section maintains the header.

The header item is notified about the change and if the underlying item is another header, then the section becomes empty and header remains unlinked.

Expand/Collapse a section

Using AbstractExpandableHeaderItem (in the library), an implementation of IExpandable and IHeader interfaces, it is possible to make a section expandable/collapsible by configuring the item signature as following (3 examples). This special item can be collapsed by clicking on it, also when the header of the section (aka parent) is sticky!

Example 1:

public class ExpandableHeaderItem
		extends AbstractExpandableHeaderItem<ExpandableHeaderItem.ExpandableHeaderViewHolder, SubItem> {

	public ExpandableHeaderItem() {
		super();//Call super to auto-configure the section status as shown, expanded, not selectable
	}
}

Example 2:

public class ExpandableHeaderItem
		extends AbstractFlexibleItem<ExpandableHeaderItem.ExpandableHeaderViewHolder>
		implements IExpandable<ExpandableHeaderItem.ExpandableHeaderViewHolder, SubItem>,
		IHeader<ExpandableHeaderItem.ExpandableHeaderViewHolder> {

	public ExpandableHeaderItem() {
		//Configure the section status: shown and/or expanded
		setHidden(false);
		setExpanded(true);
		//NOT selectable, otherwise (if you have) the ActionMode will be activated on long click!
		setSelectable(false);
	}
}

Example 3:

public class ExpandableHeaderItem
		extends AbstractExpandableItem<ExpandableHeaderItem.ExpandableHeaderViewHolder, SubItem>,
		implements IHeader<ExpandableHeaderItem.ExpandableHeaderViewHolder> {

	public ExpandableHeaderItem() {
		setHidden(false);
		setExpanded(true);
		setSelectable(false);
	}
}
Clone this wiki locally