Skip to content

Instantly share code, notes, and snippets.

@MFlisar
Created April 12, 2016 08:02
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 MFlisar/2d2c92bb78e4a379ea252c273e3de1a2 to your computer and use it in GitHub Desktop.
Save MFlisar/2d2c92bb78e4a379ea252c273e3de1a2 to your computer and use it in GitHub Desktop.
package com.turingtechnologies.materialscrollbar;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import java.util.ArrayList;
/**
* Created by flisar on 11.04.2016.
*/
public class FastScrollerUtil
{
private static final String TAG = FastScrollerUtil.class.getSimpleName();
private static boolean DEBUG = false;
public static void enableDebugging(boolean debug)
{
DEBUG = debug;
}
public interface IHeaderAdapter
{
boolean isHeader(int index);
int getItemCount();
void initScrollManager(int span);
int getHeaderHeight();
int getRowHeight();
}
// ---------------------
// IHeaderAdapter - public functions
// --------------------
public static void initHeaderScroller(RecyclerView rv)
{
RecyclerView.Adapter adapter = rv.getAdapter();
if (adapter instanceof IHeaderAdapter)
{
((IHeaderAdapter) adapter).initScrollManager(getSpanSize(rv));
initSpanSizeLookup(rv, (IHeaderAdapter)adapter);
}
}
// ---------------------
// IHeaderAdapter - helper functions
// --------------------
private static Integer getSpanSize(RecyclerView rv)
{
final RecyclerView.LayoutManager lm = rv.getLayoutManager();
if (lm != null && lm instanceof GridLayoutManager)
return ((GridLayoutManager)lm).getSpanCount();
return null;
}
private static boolean initSpanSizeLookup(final RecyclerView rv, final IHeaderAdapter adapter)
{
final RecyclerView.LayoutManager lm = rv.getLayoutManager();
if (lm != null && lm instanceof GridLayoutManager)
{
((GridLayoutManager)lm).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (rv == null)
return 1;
return adapter.isHeader(position) ? ((GridLayoutManager) lm).getSpanCount() : 1;
}
});
return true;
}
return false;
}
// ---------------------
// IHeaderAdapter - helper classes
// --------------------
public static class HeaderData
{
private int headerIndex; // index of header in the list of ALL items (not only of headers!)
private int items; // items underneath the header
public HeaderData(int headerIndex, int items)
{
this.items = items;
this.headerIndex = headerIndex;
}
public boolean containsIndex(int index)
{
return index >= headerIndex && index <= headerIndex + items;
}
public boolean containsRow(int rowsBeforeThisHeader, int span, int row)
{
return row >= rowsBeforeThisHeader + 1 && row <= rowsBeforeThisHeader + getRows(span);
}
public int getFirstItemIndex(int rowsBeforeThisHeader, int span, int row)
{
int relativeRowIndex = row - rowsBeforeThisHeader - 1;
if (relativeRowIndex == 0)
return headerIndex;
return headerIndex + relativeRowIndex * span - (span - 1);
}
public int getRows(int span)
{
return 1 + (int)Math.ceil((float)items / (float)span);
}
public int getRelativeRowIndex(int span, int index)
{
if (index - headerIndex == 0)
return 0;
return (int)Math.ceil((float)(index - headerIndex) / (float)span);
}
}
public static class HeaderScrollManager
{
private int mSpan;
private int mCount;
private int mRows;
private ArrayList<HeaderData> mHeaderData;
private int mHeaderHeight;
private int mRowHeight;
private int mTotalHeight;
public HeaderScrollManager(int span)
{
mSpan = span;
}
public int getSpan()
{
return mSpan;
}
public <T extends RecyclerView.Adapter & IHeaderAdapter> void calcData(T adapter)
{
mHeaderData = new ArrayList<>();
// 1) How many items do we have (inkl. headers) + init local variables
mCount = adapter.getItemCount();
mHeaderHeight = adapter.getHeaderHeight();
mRowHeight = adapter.getRowHeight();
// 2) How many rows do we have? + fill map with index of header and items under this header + calc rows
mRows = 0;
int itemsAddedToHeaders = 0;
for (int i = 1; i < mCount; i++)
{
if (adapter.isHeader(i) || i == mCount - 1)
{
// current header group end found => save the data
int itemsUnderneathHeader = i - itemsAddedToHeaders;
if (i != mCount - 1)
itemsUnderneathHeader -= 1; // remove next header item from count
HeaderData headerData = new HeaderData(itemsAddedToHeaders, itemsUnderneathHeader);
mHeaderData.add(headerData);
// increase itemsHandled, so that the next header knows how many items are before it
itemsAddedToHeaders += 1; // add header item
itemsAddedToHeaders += itemsUnderneathHeader; // add items of this header
// adjust row count
mRows += headerData.getRows(mSpan);
}
}
// 3) calc total height
mTotalHeight = mHeaderData.size() * mHeaderHeight;
for (int i = 0; i < mHeaderData.size(); i++)
mTotalHeight += (mHeaderData.get(i).getRows(mSpan) - 1) * mRowHeight;
if (DEBUG)
{
for (int i = 0; i < mHeaderData.size(); i++)
Log.d(TAG, "Header data " + i + ": headerIndex=" + mHeaderData.get(i).headerIndex + " | items=" + mHeaderData.get(i).items + " | rows=" + mHeaderData.get(i).getRows(mSpan));
for (int i = 0; i <= mCount; i++)
{
float progress = (float) i / (float) mCount;
getItemIndexForScroll(progress);
}
for (int i = 0; i < mCount; i++)
getDepthForItem(i);
}
}
public int getTotalDepth()
{
return mTotalHeight;
}
public int getDepthForItem(int index)
{
// 1) calculate row of this index
int row = 0;
int headersAbove = 0;
for (int i = 0; i < mHeaderData.size(); i++)
{
HeaderData headerData = mHeaderData.get(i);
headersAbove++;
if (headerData.containsIndex(index))
{
int relRow = headerData.getRelativeRowIndex(mSpan, index);
row += relRow + 1;
// remove header itself from headersAbove count if row is a header
if (relRow == 0)
headersAbove--;
// L.d(this, "index => Header Data Index: " + index + " => " + i);
break;
}
else
row += headerData.getRows(mSpan);
}
int totalOffset = headersAbove * mHeaderHeight + (row - headersAbove - 1) * mRowHeight;
if (DEBUG)
Log.d(TAG, "index => row=" + row + " of " + mRows + ", headersAbove=" + headersAbove + " (totalOffset=" + totalOffset + ")");
return totalOffset;
}
public int getItemIndexForScroll(float scrollBarPos)
{
if (scrollBarPos < 0)
scrollBarPos = 0;
else if (scrollBarPos > 1)
scrollBarPos = 1;
// 1) calculate row that corresponds to scrollBarPos
int rowIndex = Math.round((float)(mRows - 1) * scrollBarPos);
int row = rowIndex + 1;
// 2) find header that contains this row
int rows = 0;
for (int i = 0; i < mHeaderData.size(); i++)
{
HeaderData headerData = mHeaderData.get(i);
if (headerData.containsRow(rows, mSpan, row))
{
int index = headerData.getFirstItemIndex(rows, mSpan, row);
if (DEBUG)
Log.d(TAG, "scrollBarPos=" + scrollBarPos + " => row " + row + " of " + mRows + " (item " + (index + 1) + " of " + mCount + ")");
return index;
}
rows += headerData.getRows(mSpan);
}
throw new RuntimeException("Could not find index for scroll position!");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment