/** * Copyright 2016 Aidan Follestad * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package com.afollestad.sectionedrecyclerview; import android.support.annotation.IntRange; import android.support.annotation.Nullable; import android.support.v4.util.ArrayMap; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.view.ViewGroup; import java.util.List; /** * @author Aidan Follestad (afollestad) */ public abstract class SectionedRecyclerViewAdapter extends RecyclerView.Adapter { protected final static int VIEW_TYPE_HEADER = -2; protected final static int VIEW_TYPE_ITEM = -1; private final ArrayMap mHeaderLocationMap; private GridLayoutManager mLayoutManager; private ArrayMap mSpanMap; private boolean mShowHeadersForEmptySections; public SectionedRecyclerViewAdapter() { mHeaderLocationMap = new ArrayMap<>(); } public abstract int getSectionCount(); public abstract int getItemCount(int section); public abstract void onBindHeaderViewHolder(VH holder, int section); public abstract void onBindViewHolder(VH holder, int section, int relativePosition, int absolutePosition); public final boolean isHeader(int position) { return mHeaderLocationMap.get(position) != null; } /** * Instructs the list view adapter to whether show headers for empty sections or not. * * @param show flag indicating whether headers for empty sections ought to be shown. */ public final void shouldShowHeadersForEmptySections(boolean show) { mShowHeadersForEmptySections = show; } public final void setLayoutManager(@Nullable GridLayoutManager lm) { mLayoutManager = lm; if (lm == null) return; lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (isHeader(position)) return mLayoutManager.getSpanCount(); final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); final int absPos = position - (sectionAndPos[0] + 1); return getRowSpan(mLayoutManager.getSpanCount(), sectionAndPos[0], sectionAndPos[1], absPos); } }); } @SuppressWarnings("UnusedParameters") protected int getRowSpan(int fullSpanSize, int section, int relativePosition, int absolutePosition) { return 1; } // returns section along with offsetted position private int[] getSectionIndexAndRelativePosition(int itemPosition) { synchronized (mHeaderLocationMap) { Integer lastSectionIndex = -1; for (final Integer sectionIndex : mHeaderLocationMap.keySet()) { if (itemPosition > sectionIndex) { lastSectionIndex = sectionIndex; } else { break; } } return new int[]{mHeaderLocationMap.get(lastSectionIndex), itemPosition - lastSectionIndex - 1}; } } @Override public final int getItemCount() { int count = 0; mHeaderLocationMap.clear(); for (int s = 0; s < getSectionCount(); s++) { int itemCount = getItemCount(s); if (mShowHeadersForEmptySections || (itemCount > 0)) { mHeaderLocationMap.put(count, s); count += itemCount + 1; } } return count; } /** * @hide * @deprecated */ @Override @Deprecated public final int getItemViewType(int position) { if (isHeader(position)) { return getHeaderViewType(mHeaderLocationMap.get(position)); } else { final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); return getItemViewType(sectionAndPos[0], // offset section view positions sectionAndPos[1], position - (sectionAndPos[0] + 1)); } } @SuppressWarnings("UnusedParameters") @IntRange(from = 0, to = Integer.MAX_VALUE) public int getHeaderViewType(int section) { //noinspection ResourceType return VIEW_TYPE_HEADER; } @SuppressWarnings("UnusedParameters") @IntRange(from = 0, to = Integer.MAX_VALUE) public int getItemViewType(int section, int relativePosition, int absolutePosition) { //noinspection ResourceType return VIEW_TYPE_ITEM; } /** * @hide * @deprecated */ @Override @Deprecated public final void onBindViewHolder(VH holder, int position) { StaggeredGridLayoutManager.LayoutParams layoutParams = null; if (holder.itemView.getLayoutParams() instanceof GridLayoutManager.LayoutParams) layoutParams = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); else if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams(); if (isHeader(position)) { if (layoutParams != null) layoutParams.setFullSpan(true); onBindHeaderViewHolder(holder, mHeaderLocationMap.get(position)); } else { if (layoutParams != null) layoutParams.setFullSpan(false); final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); final int absPos = position - (sectionAndPos[0] + 1); onBindViewHolder(holder, sectionAndPos[0], // offset section view positions sectionAndPos[1], absPos); } if (layoutParams != null) holder.itemView.setLayoutParams(layoutParams); } /** * @hide * @deprecated */ @Deprecated @Override public final void onBindViewHolder(VH holder, int position, List payloads) { super.onBindViewHolder(holder, position, payloads); } }