FileDocCategorySizeDatePackage
StaggeredGridView.javaAPI DocAndroid 5.1 API56933Thu Mar 12 22:22:48 GMT 2015com.android.ex.widget

StaggeredGridView

public class StaggeredGridView extends android.view.ViewGroup
ListView and GridView just not complex enough? Try StaggeredGridView!

StaggeredGridView presents a multi-column grid with consistent column sizes but varying row sizes between the columns. Each successive item from a {@link android.widget.ListAdapter ListAdapter} will be arranged from top to bottom, left to right. The largest vertical gap is always filled first.

Item views may span multiple columns as specified by their {@link LayoutParams}. The attribute android:layout_span may be used when inflating item views from xml.

This class is still under development and is not fully functional yet.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private android.widget.ListAdapter
mAdapter
public static final int
COLUMN_COUNT_AUTO
private int
mColCountSetting
private int
mColCount
private int
mMinColWidth
private int
mItemMargin
private int[]
mItemTops
private int[]
mItemBottoms
private boolean
mFastChildLayout
private boolean
mPopulating
private boolean
mForcePopulateOnLayout
private boolean
mInLayout
private int
mRestoreOffset
private final RecycleBin
mRecycler
private final AdapterDataSetObserver
mObserver
private boolean
mDataChanged
private int
mOldItemCount
private int
mItemCount
private boolean
mHasStableIds
private int
mFirstPosition
private int
mTouchSlop
private int
mMaximumVelocity
private int
mFlingVelocity
private float
mLastTouchY
private float
mTouchRemainderY
private int
mActivePointerId
private static final int
TOUCH_MODE_IDLE
private static final int
TOUCH_MODE_DRAGGING
private static final int
TOUCH_MODE_FLINGING
private int
mTouchMode
private final android.view.VelocityTracker
mVelocityTracker
private final ScrollerCompat
mScroller
private final EdgeEffectCompat
mTopEdge
private final EdgeEffectCompat
mBottomEdge
private final android.support.v4.util.SparseArrayCompat
mLayoutRecords
Constructors Summary
public StaggeredGridView(android.content.Context context)


       
        this(context, null);
    
public StaggeredGridView(android.content.Context context, android.util.AttributeSet attrs)

        this(context, attrs, 0);
    
public StaggeredGridView(android.content.Context context, android.util.AttributeSet attrs, int defStyle)

        super(context, attrs, defStyle);

        final ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
        mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
        mFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mScroller = ScrollerCompat.from(context);

        mTopEdge = new EdgeEffectCompat(context);
        mBottomEdge = new EdgeEffectCompat(context);
        setWillNotDraw(false);
        setClipToPadding(false);
    
Methods Summary
public voidbeginFastChildLayout()

        mFastChildLayout = true;
    
protected booleancheckLayoutParams(ViewGroup.LayoutParams lp)

        return lp instanceof LayoutParams;
    
private voidclearAllState()
Clear all state because the grid will be used for a completely different set of data.

        // Clear all layout records and views
        mLayoutRecords.clear();
        removeAllViews();

        // Reset to the top of the grid
        resetStateForGridTop();

        // Clear recycler because there could be different view types now
        mRecycler.clear();
    
public voidcomputeScroll()

        if (mScroller.computeScrollOffset()) {
            final int y = mScroller.getCurrY();
            final int dy = (int) (y - mLastTouchY);
            mLastTouchY = y;
            final boolean stopped = !trackMotionScroll(dy, false);

            if (!stopped && !mScroller.isFinished()) {
                ViewCompat.postInvalidateOnAnimation(this);
            } else {
                if (stopped) {
                    final int overScrollMode = ViewCompat.getOverScrollMode(this);
                    if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) {
                        final EdgeEffectCompat edge;
                        if (dy > 0) {
                            edge = mTopEdge;
                        } else {
                            edge = mBottomEdge;
                        }
                        edge.onAbsorb(Math.abs((int) mScroller.getCurrVelocity()));
                        ViewCompat.postInvalidateOnAnimation(this);
                    }
                    mScroller.abortAnimation();
                }
                mTouchMode = TOUCH_MODE_IDLE;
            }
        }
    
private final booleancontentFits()

        if (mFirstPosition != 0 || getChildCount() != mItemCount) {
            return false;
        }

        int topmost = Integer.MAX_VALUE;
        int bottommost = Integer.MIN_VALUE;
        for (int i = 0; i < mColCount; i++) {
            if (mItemTops[i] < topmost) {
                topmost = mItemTops[i];
            }
            if (mItemBottoms[i] > bottommost) {
                bottommost = mItemBottoms[i];
            }
        }

        return topmost >= getPaddingTop() && bottommost <= getHeight() - getPaddingBottom();
    
public voiddraw(android.graphics.Canvas canvas)

        super.draw(canvas);

        if (mTopEdge != null) {
            boolean needsInvalidate = false;
            if (!mTopEdge.isFinished()) {
                mTopEdge.draw(canvas);
                needsInvalidate = true;
            }
            if (!mBottomEdge.isFinished()) {
                final int restoreCount = canvas.save();
                final int width = getWidth();
                canvas.translate(-width, getHeight());
                canvas.rotate(180, width, 0);
                mBottomEdge.draw(canvas);
                canvas.restoreToCount(restoreCount);
                needsInvalidate = true;
            }

            if (needsInvalidate) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    
private voiddumpItemPositions()

        final int childCount = getChildCount();
        Log.d(TAG, "dumpItemPositions:");
        Log.d(TAG, " => Tops:");
        for (int i = 0; i < mColCount; i++) {
            Log.d(TAG, "  => " + mItemTops[i]);
            boolean found = false;
            for (int j = 0; j < childCount; j++) {
                final View child = getChildAt(j);
                if (mItemTops[i] == child.getTop() - mItemMargin) {
                    found = true;
                }
            }
            if (!found) {
                Log.d(TAG, "!!! No top item found for column " + i + " value " + mItemTops[i]);
            }
        }
        Log.d(TAG, " => Bottoms:");
        for (int i = 0; i < mColCount; i++) {
            Log.d(TAG, "  => " + mItemBottoms[i]);
            boolean found = false;
            for (int j = 0; j < childCount; j++) {
                final View child = getChildAt(j);
                if (mItemBottoms[i] == child.getBottom()) {
                    found = true;
                }
            }
            if (!found) {
                Log.d(TAG, "!!! No bottom item found for column " + i + " value " + mItemBottoms[i]);
            }
        }
    
public voidendFastChildLayout()

        mFastChildLayout = false;
        populate();
    
final intfillDown(int fromPosition, int overhang)
Should be called with mPopulating set to true

param
fromPosition Position to start filling from
param
overhang the number of extra pixels to fill beyond the current bottom edge
return
the max overhang beyond the end of the view of any added items at the bottom

        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int itemMargin = mItemMargin;
        final int colWidth =
                (getWidth() - paddingLeft - paddingRight - itemMargin * (mColCount - 1)) / mColCount;
        final int gridBottom = getHeight() - getPaddingBottom();
        final int fillTo = gridBottom + overhang;
        int nextCol = getNextColumnDown();
        int position = fromPosition;

        while (nextCol >= 0 && mItemBottoms[nextCol] < fillTo && position < mItemCount) {
            final View child = obtainView(position, null);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (child.getParent() != this) {
                if (mInLayout) {
                    addViewInLayout(child, -1, lp);
                } else {
                    addView(child);
                }
            }

            final int span = Math.min(mColCount, lp.span);
            final int widthSize = colWidth * span + itemMargin * (span - 1);
            final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);

            LayoutRecord rec;
            if (span > 1) {
                rec = getNextRecordDown(position, span);
                nextCol = rec.column;
            } else {
                rec = mLayoutRecords.get(position);
            }

            boolean invalidateAfter = false;
            if (rec == null) {
                rec = new LayoutRecord();
                mLayoutRecords.put(position, rec);
                rec.column = nextCol;
                rec.span = span;
            } else if (span != rec.span) {
                rec.span = span;
                rec.column = nextCol;
                invalidateAfter = true;
            } else {
                nextCol = rec.column;
            }

            if (mHasStableIds) {
                final long id = mAdapter.getItemId(position);
                rec.id = id;
                lp.id = id;
            }

            lp.column = nextCol;

            final int heightSpec;
            if (lp.height == LayoutParams.WRAP_CONTENT) {
                heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            } else {
                heightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
            }
            child.measure(widthSpec, heightSpec);

            final int childHeight = child.getMeasuredHeight();
            if (invalidateAfter || (childHeight != rec.height && rec.height > 0)) {
                invalidateLayoutRecordsAfterPosition(position);
            }
            rec.height = childHeight;

            final int startFrom;
            if (span > 1) {
                int lowest = mItemBottoms[nextCol];
                for (int i = nextCol + 1; i < nextCol + span; i++) {
                    final int bottom = mItemBottoms[i];
                    if (bottom > lowest) {
                        lowest = bottom;
                    }
                }
                startFrom = lowest;
            } else {
                startFrom = mItemBottoms[nextCol];
            }
            final int childTop = startFrom + itemMargin;
            final int childBottom = childTop + childHeight;
            final int childLeft = paddingLeft + nextCol * (colWidth + itemMargin);
            final int childRight = childLeft + child.getMeasuredWidth();
            child.layout(childLeft, childTop, childRight, childBottom);

            for (int i = nextCol; i < nextCol + span; i++) {
                mItemBottoms[i] = childBottom + rec.getMarginBelow(i - nextCol);
            }

            nextCol = getNextColumnDown();
            position++;
        }

        int lowestView = 0;
        for (int i = 0; i < mColCount; i++) {
            if (mItemBottoms[i] > lowestView) {
                lowestView = mItemBottoms[i];
            }
        }
        return lowestView - gridBottom;
    
final intfillUp(int fromPosition, int overhang)
Should be called with mPopulating set to true

param
fromPosition Position to start filling from
param
overhang the number of extra pixels to fill beyond the current top edge
return
the max overhang beyond the beginning of the view of any added items at the top

        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int itemMargin = mItemMargin;
        final int colWidth =
                (getWidth() - paddingLeft - paddingRight - itemMargin * (mColCount - 1)) / mColCount;
        final int gridTop = getPaddingTop();
        final int fillTo = gridTop - overhang;
        int nextCol = getNextColumnUp();
        int position = fromPosition;

        while (nextCol >= 0 && mItemTops[nextCol] > fillTo && position >= 0) {
            final View child = obtainView(position, null);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (child.getParent() != this) {
                if (mInLayout) {
                    addViewInLayout(child, 0, lp);
                } else {
                    addView(child, 0);
                }
            }

            final int span = Math.min(mColCount, lp.span);
            final int widthSize = colWidth * span + itemMargin * (span - 1);
            final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);

            LayoutRecord rec;
            if (span > 1) {
                rec = getNextRecordUp(position, span);
                nextCol = rec.column;
            } else {
                rec = mLayoutRecords.get(position);
            }

            boolean invalidateBefore = false;
            if (rec == null) {
                rec = new LayoutRecord();
                mLayoutRecords.put(position, rec);
                rec.column = nextCol;
                rec.span = span;
            } else if (span != rec.span) {
                rec.span = span;
                rec.column = nextCol;
                invalidateBefore = true;
            } else {
                nextCol = rec.column;
            }

            if (mHasStableIds) {
                final long id = mAdapter.getItemId(position);
                rec.id = id;
                lp.id = id;
            }

            lp.column = nextCol;

            final int heightSpec;
            if (lp.height == LayoutParams.WRAP_CONTENT) {
                heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            } else {
                heightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
            }
            child.measure(widthSpec, heightSpec);

            final int childHeight = child.getMeasuredHeight();
            if (invalidateBefore || (childHeight != rec.height && rec.height > 0)) {
                invalidateLayoutRecordsBeforePosition(position);
            }
            rec.height = childHeight;

            final int startFrom;
            if (span > 1) {
                int highest = mItemTops[nextCol];
                for (int i = nextCol + 1; i < nextCol + span; i++) {
                    final int top = mItemTops[i];
                    if (top < highest) {
                        highest = top;
                    }
                }
                startFrom = highest;
            } else {
                startFrom = mItemTops[nextCol];
            }
            final int childBottom = startFrom;
            final int childTop = childBottom - childHeight;
            final int childLeft = paddingLeft + nextCol * (colWidth + itemMargin);
            final int childRight = childLeft + child.getMeasuredWidth();
            child.layout(childLeft, childTop, childRight, childBottom);

            for (int i = nextCol; i < nextCol + span; i++) {
                mItemTops[i] = childTop - rec.getMarginAbove(i - nextCol) - itemMargin;
            }

            nextCol = getNextColumnUp();
            mFirstPosition = position--;
        }

        int highestView = getHeight();
        for (int i = 0; i < mColCount; i++) {
            if (mItemTops[i] < highestView) {
                highestView = mItemTops[i];
            }
        }
        return gridTop - highestView;
    
protected com.android.ex.widget.StaggeredGridView$LayoutParamsgenerateDefaultLayoutParams()

        return new LayoutParams(LayoutParams.WRAP_CONTENT);
    
protected com.android.ex.widget.StaggeredGridView$LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams lp)

        return new LayoutParams(lp);
    
public ViewGroup.LayoutParamsgenerateLayoutParams(android.util.AttributeSet attrs)

        return new LayoutParams(getContext(), attrs);
    
public android.widget.ListAdaptergetAdapter()

        return mAdapter;
    
public intgetColumnCount()

        return mColCount;
    
public intgetFirstPosition()
Return the first adapter position with a view currently attached as a child view of this grid.

return
the adapter position represented by the view at getChildAt(0).

        return mFirstPosition;
    
final intgetNextColumnDown()

return
column that the next view filling downwards should occupy. This is the top-most position available.

        int result = -1;
        int topMost = Integer.MAX_VALUE;

        final int colCount = mColCount;
        for (int i = 0; i < colCount; i++) {
            final int bottom = mItemBottoms[i];
            if (bottom < topMost) {
                topMost = bottom;
                result = i;
            }
        }
        return result;
    
final intgetNextColumnUp()

return
column that the next view filling upwards should occupy. This is the bottom-most position available for a single-column item.

        int result = -1;
        int bottomMost = Integer.MIN_VALUE;

        final int colCount = mColCount;
        for (int i = colCount - 1; i >= 0; i--) {
            final int top = mItemTops[i];
            if (top > bottomMost) {
                bottomMost = top;
                result = i;
            }
        }
        return result;
    
final com.android.ex.widget.StaggeredGridView$LayoutRecordgetNextRecordDown(int position, int span)

        LayoutRecord rec = mLayoutRecords.get(position);
        if (rec == null) {
            rec = new LayoutRecord();
            rec.span = span;
            mLayoutRecords.put(position, rec);
        } else if (rec.span != span) {
            throw new IllegalStateException("Invalid LayoutRecord! Record had span=" + rec.span +
                    " but caller requested span=" + span + " for position=" + position);
        }
        int targetCol = -1;
        int topMost = Integer.MAX_VALUE;

        final int colCount = mColCount;
        for (int i = 0; i <= colCount - span; i++) {
            int bottom = Integer.MIN_VALUE;
            for (int j = i; j < i + span; j++) {
                final int singleBottom = mItemBottoms[j];
                if (singleBottom > bottom) {
                    bottom = singleBottom;
                }
            }
            if (bottom < topMost) {
                topMost = bottom;
                targetCol = i;
            }
        }

        rec.column = targetCol;

        for (int i = 0; i < span; i++) {
            rec.setMarginAbove(i, topMost - mItemBottoms[i + targetCol]);
        }

        return rec;
    
final com.android.ex.widget.StaggeredGridView$LayoutRecordgetNextRecordUp(int position, int span)
Return a LayoutRecord for the given position

param
position
param
span
return

        LayoutRecord rec = mLayoutRecords.get(position);
        if (rec == null) {
            rec = new LayoutRecord();
            rec.span = span;
            mLayoutRecords.put(position, rec);
        } else if (rec.span != span) {
            throw new IllegalStateException("Invalid LayoutRecord! Record had span=" + rec.span +
                    " but caller requested span=" + span + " for position=" + position);
        }
        int targetCol = -1;
        int bottomMost = Integer.MIN_VALUE;

        final int colCount = mColCount;
        for (int i = colCount - span; i >= 0; i--) {
            int top = Integer.MAX_VALUE;
            for (int j = i; j < i + span; j++) {
                final int singleTop = mItemTops[j];
                if (singleTop < top) {
                    top = singleTop;
                }
            }
            if (top > bottomMost) {
                bottomMost = top;
                targetCol = i;
            }
        }

        rec.column = targetCol;

        for (int i = 0; i < span; i++) {
            rec.setMarginBelow(i, mItemTops[i + targetCol] - bottomMost);
        }

        return rec;
    
final voidinvalidateLayoutRecordsAfterPosition(int position)

        int beginAt = mLayoutRecords.size() - 1;
        while (beginAt >= 0 && mLayoutRecords.keyAt(beginAt) > position) {
            beginAt--;
        }
        beginAt++;
        mLayoutRecords.removeAtRange(beginAt + 1, mLayoutRecords.size() - beginAt);
    
final voidinvalidateLayoutRecordsBeforePosition(int position)

        int endAt = 0;
        while (endAt < mLayoutRecords.size() && mLayoutRecords.keyAt(endAt) < position) {
            endAt++;
        }
        mLayoutRecords.removeAtRange(0, endAt);
    
final voidlayoutChildren(boolean queryAdapter)
Measure and layout all currently visible children.

param
queryAdapter true to requery the adapter for view data

        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int itemMargin = mItemMargin;
        final int colWidth =
                (getWidth() - paddingLeft - paddingRight - itemMargin * (mColCount - 1)) / mColCount;
        int rebuildLayoutRecordsBefore = -1;
        int rebuildLayoutRecordsAfter = -1;

        Arrays.fill(mItemBottoms, Integer.MIN_VALUE);

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final int col = lp.column;
            final int position = mFirstPosition + i;
            final boolean needsLayout = queryAdapter || child.isLayoutRequested();

            if (queryAdapter) {
                View newView = obtainView(position, child);
                if (newView != child) {
                    removeViewAt(i);
                    addView(newView, i);
                    child = newView;
                }
                lp = (LayoutParams) child.getLayoutParams(); // Might have changed
            }

            final int span = Math.min(mColCount, lp.span);
            final int widthSize = colWidth * span + itemMargin * (span - 1);

            if (needsLayout) {
                final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);

                final int heightSpec;
                if (lp.height == LayoutParams.WRAP_CONTENT) {
                    heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
                } else {
                    heightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
                }

                child.measure(widthSpec, heightSpec);
            }

            int childTop = mItemBottoms[col] > Integer.MIN_VALUE ?
                    mItemBottoms[col] + mItemMargin : child.getTop();
            if (span > 1) {
                int lowest = childTop;
                for (int j = col + 1; j < col + span; j++) {
                    final int bottom = mItemBottoms[j] + mItemMargin;
                    if (bottom > lowest) {
                        lowest = bottom;
                    }
                }
                childTop = lowest;
            }
            final int childHeight = child.getMeasuredHeight();
            final int childBottom = childTop + childHeight;
            final int childLeft = paddingLeft + col * (colWidth + itemMargin);
            final int childRight = childLeft + child.getMeasuredWidth();
            child.layout(childLeft, childTop, childRight, childBottom);

            for (int j = col; j < col + span; j++) {
                mItemBottoms[j] = childBottom;
            }

            final LayoutRecord rec = mLayoutRecords.get(position);
            if (rec != null && rec.height != childHeight) {
                // Invalidate our layout records for everything before this.
                rec.height = childHeight;
                rebuildLayoutRecordsBefore = position;
            }

            if (rec != null && rec.span != span) {
                // Invalidate our layout records for everything after this.
                rec.span = span;
                rebuildLayoutRecordsAfter = position;
            }
        }

        // Update mItemBottoms for any empty columns
        for (int i = 0; i < mColCount; i++) {
            if (mItemBottoms[i] == Integer.MIN_VALUE) {
                mItemBottoms[i] = mItemTops[i];
            }
        }

        if (rebuildLayoutRecordsBefore >= 0 || rebuildLayoutRecordsAfter >= 0) {
            if (rebuildLayoutRecordsBefore >= 0) {
                invalidateLayoutRecordsBeforePosition(rebuildLayoutRecordsBefore);
            }
            if (rebuildLayoutRecordsAfter >= 0) {
                invalidateLayoutRecordsAfterPosition(rebuildLayoutRecordsAfter);
            }
            for (int i = 0; i < childCount; i++) {
                final int position = mFirstPosition + i;
                final View child = getChildAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                LayoutRecord rec = mLayoutRecords.get(position);
                if (rec == null) {
                    rec = new LayoutRecord();
                    mLayoutRecords.put(position, rec);
                }
                rec.column = lp.column;
                rec.height = child.getHeight();
                rec.id = lp.id;
                rec.span = Math.min(mColCount, lp.span);
            }
        }
    
final android.view.ViewobtainView(int position, android.view.View optScrap)
Obtain a populated view from the adapter. If optScrap is non-null and is not reused it will be placed in the recycle bin.

param
position position to get view for
param
optScrap Optional scrap view; will be reused if possible
return
A new view, a recycled view from mRecycler, or optScrap

        View view = mRecycler.getTransientStateView(position);
        if (view != null) {
            return view;
        }

        // Reuse optScrap if it's of the right type (and not null)
        final int optType = optScrap != null ?
                ((LayoutParams) optScrap.getLayoutParams()).viewType : -1;
        final int positionViewType = mAdapter.getItemViewType(position);
        final View scrap = optType == positionViewType ?
                optScrap : mRecycler.getScrapView(positionViewType);

        view = mAdapter.getView(position, scrap, this);

        if (view != scrap && scrap != null) {
            // The adapter didn't use it; put it back.
            mRecycler.addScrap(scrap);
        }

        ViewGroup.LayoutParams lp = view.getLayoutParams();

        if (view.getParent() != this) {
            if (lp == null) {
                lp = generateDefaultLayoutParams();
            } else if (!checkLayoutParams(lp)) {
                lp = generateLayoutParams(lp);
            }
        }

        final LayoutParams sglp = (LayoutParams) lp;
        sglp.position = position;
        sglp.viewType = positionViewType;

        return view;
    
final voidoffsetChildren(int offset)

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            child.layout(child.getLeft(), child.getTop() + offset,
                    child.getRight(), child.getBottom() + offset);
        }

        final int colCount = mColCount;
        for (int i = 0; i < colCount; i++) {
            mItemTops[i] += offset;
            mItemBottoms[i] += offset;
        }
    
public booleanonInterceptTouchEvent(android.view.MotionEvent ev)

        mVelocityTracker.addMovement(ev);
        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mVelocityTracker.clear();
                mScroller.abortAnimation();
                mLastTouchY = ev.getY();
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mTouchRemainderY = 0;
                if (mTouchMode == TOUCH_MODE_FLINGING) {
                    // Catch!
                    mTouchMode = TOUCH_MODE_DRAGGING;
                    return true;
                }
                break;

            case MotionEvent.ACTION_MOVE: {
                final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (index < 0) {
                    Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
                            mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
                            "event stream?");
                    return false;
                }
                final float y = MotionEventCompat.getY(ev, index);
                final float dy = y - mLastTouchY + mTouchRemainderY;
                final int deltaY = (int) dy;
                mTouchRemainderY = dy - deltaY;

                if (Math.abs(dy) > mTouchSlop) {
                    mTouchMode = TOUCH_MODE_DRAGGING;
                    return true;
                }
            }
        }

        return false;
    
protected voidonLayout(boolean changed, int l, int t, int r, int b)

        mInLayout = true;
        populate();
        mInLayout = false;
        mForcePopulateOnLayout = false;

        final int width = r - l;
        final int height = b - t;
        mTopEdge.setSize(width, height);
        mBottomEdge.setSize(width, height);
    
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY) {
            Log.e(TAG, "onMeasure: must have an exact width or match_parent! " +
                    "Using fallback spec of EXACTLY " + widthSize);
            widthMode = MeasureSpec.EXACTLY;
        }
        if (heightMode != MeasureSpec.EXACTLY) {
            Log.e(TAG, "onMeasure: must have an exact height or match_parent! " +
                    "Using fallback spec of EXACTLY " + heightSize);
            heightMode = MeasureSpec.EXACTLY;
        }

        setMeasuredDimension(widthSize, heightSize);

        if (mColCountSetting == COLUMN_COUNT_AUTO) {
            final int colCount = widthSize / mMinColWidth;
            if (colCount != mColCount) {
                mColCount = colCount;
                mForcePopulateOnLayout = true;
            }
        }
    
public voidonRestoreInstanceState(android.os.Parcelable state)

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        mDataChanged = true;
        mFirstPosition = ss.position;
        mRestoreOffset = ss.topOffset;
        requestLayout();
    
public android.os.ParcelableonSaveInstanceState()

        final Parcelable superState = super.onSaveInstanceState();
        final SavedState ss = new SavedState(superState);
        final int position = mFirstPosition;
        ss.position = position;
        if (position >= 0 && mAdapter != null && position < mAdapter.getCount()) {
            ss.firstId = mAdapter.getItemId(position);
        }
        if (getChildCount() > 0) {
            ss.topOffset = getChildAt(0).getTop() - mItemMargin - getPaddingTop();
        }
        return ss;
    
public booleanonTouchEvent(android.view.MotionEvent ev)

        mVelocityTracker.addMovement(ev);
        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mVelocityTracker.clear();
                mScroller.abortAnimation();
                mLastTouchY = ev.getY();
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mTouchRemainderY = 0;
                break;

            case MotionEvent.ACTION_MOVE: {
                final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (index < 0) {
                    Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
                            mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
                            "event stream?");
                    return false;
                }
                final float y = MotionEventCompat.getY(ev, index);
                final float dy = y - mLastTouchY + mTouchRemainderY;
                final int deltaY = (int) dy;
                mTouchRemainderY = dy - deltaY;

                if (Math.abs(dy) > mTouchSlop) {
                    mTouchMode = TOUCH_MODE_DRAGGING;
                }

                if (mTouchMode == TOUCH_MODE_DRAGGING) {
                    mLastTouchY = y;

                    if (!trackMotionScroll(deltaY, true)) {
                        // Break fling velocity if we impacted an edge.
                        mVelocityTracker.clear();
                    }
                }
            } break;

            case MotionEvent.ACTION_CANCEL:
                mTouchMode = TOUCH_MODE_IDLE;
                break;

            case MotionEvent.ACTION_UP: {
                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                final float velocity = VelocityTrackerCompat.getYVelocity(mVelocityTracker,
                        mActivePointerId);
                if (Math.abs(velocity) > mFlingVelocity) { // TODO
                    mTouchMode = TOUCH_MODE_FLINGING;
                    mScroller.fling(0, 0, 0, (int) velocity, 0, 0,
                            Integer.MIN_VALUE, Integer.MAX_VALUE);
                    mLastTouchY = 0;
                    ViewCompat.postInvalidateOnAnimation(this);
                } else {
                    mTouchMode = TOUCH_MODE_IDLE;
                }

            } break;
        }
        return true;
    
private voidpopulate()

        if (getWidth() == 0 || getHeight() == 0) {
            return;
        }

        if (mColCount == COLUMN_COUNT_AUTO) {
            final int colCount = getWidth() / mMinColWidth;
            if (colCount != mColCount) {
                mColCount = colCount;
            }
        }

        final int colCount = mColCount;
        if (mItemTops == null || mItemTops.length != colCount) {
            mItemTops = new int[colCount];
            mItemBottoms = new int[colCount];
            final int top = getPaddingTop();
            final int offset = top + Math.min(mRestoreOffset, 0);
            Arrays.fill(mItemTops, offset);
            Arrays.fill(mItemBottoms, offset);
            mLayoutRecords.clear();
            if (mInLayout) {
                removeAllViewsInLayout();
            } else {
                removeAllViews();
            }
            mRestoreOffset = 0;
        }

        mPopulating = true;
        layoutChildren(mDataChanged);
        fillDown(mFirstPosition + getChildCount(), 0);
        fillUp(mFirstPosition - 1, 0);
        mPopulating = false;
        mDataChanged = false;
    
private voidrecycleAllViews()

        for (int i = 0; i < getChildCount(); i++) {
            mRecycler.addScrap(getChildAt(i));
        }

        if (mInLayout) {
            removeAllViewsInLayout();
        } else {
            removeAllViews();
        }
    
private voidrecycleOffscreenViews()
Important: this method will leave offscreen views attached if they are required to maintain the invariant that child view with index i is always the view corresponding to position mFirstPosition + i.

        final int height = getHeight();
        final int clearAbove = -mItemMargin;
        final int clearBelow = height + mItemMargin;
        for (int i = getChildCount() - 1; i >= 0; i--) {
            final View child = getChildAt(i);
            if (child.getTop() <= clearBelow)  {
                // There may be other offscreen views, but we need to maintain
                // the invariant documented above.
                break;
            }

            if (mInLayout) {
                removeViewsInLayout(i, 1);
            } else {
                removeViewAt(i);
            }

            mRecycler.addScrap(child);
        }

        while (getChildCount() > 0) {
            final View child = getChildAt(0);
            if (child.getBottom() >= clearAbove) {
                // There may be other offscreen views, but we need to maintain
                // the invariant documented above.
                break;
            }

            if (mInLayout) {
                removeViewsInLayout(0, 1);
            } else {
                removeViewAt(0);
            }

            mRecycler.addScrap(child);
            mFirstPosition++;
        }

        final int childCount = getChildCount();
        if (childCount > 0) {
            // Repair the top and bottom column boundaries from the views we still have
            Arrays.fill(mItemTops, Integer.MAX_VALUE);
            Arrays.fill(mItemBottoms, Integer.MIN_VALUE);

            for (int i = 0; i < childCount; i++){
                final View child = getChildAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final int top = child.getTop() - mItemMargin;
                final int bottom = child.getBottom();
                final LayoutRecord rec = mLayoutRecords.get(mFirstPosition + i);

                final int colEnd = lp.column + Math.min(mColCount, lp.span);
                for (int col = lp.column; col < colEnd; col++) {
                    final int colTop = top - rec.getMarginAbove(col - lp.column);
                    final int colBottom = bottom + rec.getMarginBelow(col - lp.column);
                    if (colTop < mItemTops[col]) {
                        mItemTops[col] = colTop;
                    }
                    if (colBottom > mItemBottoms[col]) {
                        mItemBottoms[col] = colBottom;
                    }
                }
            }

            for (int col = 0; col < mColCount; col++) {
                if (mItemTops[col] == Integer.MAX_VALUE) {
                    // If one was untouched, both were.
                    mItemTops[col] = 0;
                    mItemBottoms[col] = 0;
                }
            }
        }
    
public voidrequestLayout()

        if (!mPopulating && !mFastChildLayout) {
            super.requestLayout();
        }
    
private voidresetStateForGridTop()
Reset all internal state to be at the top of the grid.

        // Reset mItemTops and mItemBottoms
        final int colCount = mColCount;
        if (mItemTops == null || mItemTops.length != colCount) {
            mItemTops = new int[colCount];
            mItemBottoms = new int[colCount];
        }
        final int top = getPaddingTop();
        Arrays.fill(mItemTops, top);
        Arrays.fill(mItemBottoms, top);

        // Reset the first visible position in the grid to be item 0
        mFirstPosition = 0;
        mRestoreOffset = 0;
    
public voidsetAdapter(android.widget.ListAdapter adapter)

        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mObserver);
        }
        // TODO: If the new adapter says that there are stable IDs, remove certain layout records
        // and onscreen views if they have changed instead of removing all of the state here.
        clearAllState();
        mAdapter = adapter;
        mDataChanged = true;
        mOldItemCount = mItemCount = adapter != null ? adapter.getCount() : 0;
        if (adapter != null) {
            adapter.registerDataSetObserver(mObserver);
            mRecycler.setViewTypeCount(adapter.getViewTypeCount());
            mHasStableIds = adapter.hasStableIds();
        } else {
            mHasStableIds = false;
        }
        populate();
    
public voidsetColumnCount(int colCount)
Set a fixed number of columns for this grid. Space will be divided evenly among all columns, respecting the item margin between columns. The default is 2. (If it were 1, perhaps you should be using a {@link android.widget.ListView ListView}.)

param
colCount Number of columns to display.
see
#setMinColumnWidth(int)

        if (colCount < 1 && colCount != COLUMN_COUNT_AUTO) {
            throw new IllegalArgumentException("Column count must be at least 1 - received " +
                    colCount);
        }
        final boolean needsPopulate = colCount != mColCount;
        mColCount = mColCountSetting = colCount;
        if (needsPopulate) {
            populate();
        }
    
public voidsetItemMargin(int marginPixels)
Set the margin between items in pixels. This margin is applied both vertically and horizontally.

param
marginPixels Spacing between items in pixels

        final boolean needsPopulate = marginPixels != mItemMargin;
        mItemMargin = marginPixels;
        if (needsPopulate) {
            populate();
        }
    
public voidsetMinColumnWidth(int minColWidth)
Set a minimum column width for

param
minColWidth

        mMinColWidth = minColWidth;
        setColumnCount(COLUMN_COUNT_AUTO);
    
public voidsetSelectionToTop()
Scroll the list so the first visible position in the grid is the first item in the adapter.

        // Clear out the views (but don't clear out the layout records or recycler because the data
        // has not changed)
        removeAllViews();

        // Reset to top of grid
        resetStateForGridTop();

        // Start populating again
        populate();
    
private booleantrackMotionScroll(int deltaY, boolean allowOverScroll)

param
deltaY Pixels that content should move by
return
true if the movement completed, false if it was stopped prematurely.

        final boolean contentFits = contentFits();
        final int allowOverhang = Math.abs(deltaY);

        final int overScrolledBy;
        final int movedBy;
        if (!contentFits) {
            final int overhang;
            final boolean up;
            mPopulating = true;
            if (deltaY > 0) {
                overhang = fillUp(mFirstPosition - 1, allowOverhang);
                up = true;
            } else {
                overhang = fillDown(mFirstPosition + getChildCount(), allowOverhang) + mItemMargin;
                up = false;
            }
            movedBy = Math.min(overhang, allowOverhang);
            offsetChildren(up ? movedBy : -movedBy);
            recycleOffscreenViews();
            mPopulating = false;
            overScrolledBy = allowOverhang - overhang;
        } else {
            overScrolledBy = allowOverhang;
            movedBy = 0;
        }

        if (allowOverScroll) {
            final int overScrollMode = ViewCompat.getOverScrollMode(this);

            if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
                    (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits)) {

                if (overScrolledBy > 0) {
                    EdgeEffectCompat edge = deltaY > 0 ? mTopEdge : mBottomEdge;
                    edge.onPull((float) Math.abs(deltaY) / getHeight());
                    ViewCompat.postInvalidateOnAnimation(this);
                }
            }
        }

        return deltaY == 0 || movedBy != 0;