• Tutorials
  • Tips & Tricks
  • Applications
  • News

Android Trainee

  • Tutorials
  • Tips & Tricks
  • Applications
  • News
Home  /  Tutorials  /  Android Staggered Grid & List View.
14 July 2015

Android Staggered Grid & List View.

Written by admin@androidtrainee
Tutorials Android Listview, GridView, listview, Staggered Gridview, Staggered Listview Leave a Comment

Android Staggered Grid & List View.

 

Create Android Staggered Lib Project.

 

—>  AndroidManifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.grid"
android:versionCode="1"
android:versionName="1.0.3">

<uses-sdk android:minSdkVersion="11"/>

<application />

</manifest>

 

values folder

 

attrs.xml

 


<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="StaggeredGridView">
<attr name="column_count" format="integer" />
<attr name="column_count_portrait" format="integer" />
<attr name="column_count_landscape" format="integer" />
<attr name="item_margin" format="dimension" />
<attr name="grid_paddingLeft" format="dimension" />
<attr name="grid_paddingRight" format="dimension" />
<attr name="grid_paddingTop" format="dimension" />
<attr name="grid_paddingBottom" format="dimension" />
</declare-styleable>

</resources>

ClassLoaderSavedState.java

package com.android.grid;

import android.os.Parcel;
import android.os.Parcelable;



public abstract class ClassLoaderSavedState implements Parcelable {
public static final ClassLoaderSavedState EMPTY_STATE = new ClassLoaderSavedState() {};

private Parcelable mSuperState = EMPTY_STATE;
private ClassLoader mClassLoader;

private ClassLoaderSavedState() {
mSuperState = null;
mClassLoader = null;
}


protected ClassLoaderSavedState(Parcelable superState, ClassLoader classLoader) {
mClassLoader = classLoader;
if (superState == null) {
throw new IllegalArgumentException("superState must not be null");
}
else {
mSuperState = superState != EMPTY_STATE ? superState : null;
}
}


protected ClassLoaderSavedState(Parcel source) {
// ETSY : we're using the passed super class loader unlike AbsSavedState
Parcelable superState = source.readParcelable(mClassLoader);
mSuperState = superState != null ? superState : EMPTY_STATE;
}

final public Parcelable getSuperState() {
return mSuperState;
}

public int describeContents() {
return 0;
}

public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mSuperState, flags);
}

public static final Parcelable.Creator<ClassLoaderSavedState> CREATOR
= new Parcelable.Creator<ClassLoaderSavedState>() {

public ClassLoaderSavedState createFromParcel(Parcel in) {
Parcelable superState = in.readParcelable(null);
if (superState != null) {
throw new IllegalStateException("superState must be null");
}
return EMPTY_STATE;
}

public ClassLoaderSavedState[] newArray(int size) {
return new ClassLoaderSavedState[size];
}
};
}

ExtendableListView.java

 


package com.android.grid;

import android.annotation.SuppressLint;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.*;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.Scroller;
import java.util.ArrayList;


public abstract class ExtendableListView extends AbsListView {

private static final String TAG = "ExtendableListView";

private static final boolean DBG = false;

private static final int TOUCH_MODE_IDLE = 0;
private static final int TOUCH_MODE_SCROLLING = 1;
private static final int TOUCH_MODE_FLINGING = 2;
private static final int TOUCH_MODE_DOWN = 3;
private static final int TOUCH_MODE_TAP = 4;
private static final int TOUCH_MODE_DONE_WAITING = 5;

private static final int INVALID_POINTER = -1;

// Layout using our default existing state
private static final int LAYOUT_NORMAL = 0;
// Layout from the first item down
private static final int LAYOUT_FORCE_TOP = 1;
// Layout from the saved instance state data
private static final int LAYOUT_SYNC = 2;

private int mLayoutMode;

private int mTouchMode;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;

// Rectangle used for hit testing children
// private Rect mTouchFrame;
// TODO : ItemClick support from AdapterView

// For managing scrolling
private VelocityTracker mVelocityTracker = null;

private int mTouchSlop;
private int mMaximumVelocity;
private int mFlingVelocity;


private boolean mInLayout;

ListAdapter mAdapter;

private int mMotionY;
private int mMotionX;
private int mMotionCorrection;
private int mMotionPosition;

private int mLastY;

private int mActivePointerId = INVALID_POINTER;

protected int mFirstPosition;

// are we attached to a window - we shouldn't handle any touch events if we're not!
private boolean mIsAttached;


private boolean mBlockLayoutRequests = false;

// has our data changed - and should we react to it
private boolean mDataChanged;
private int mItemCount;
private int mOldItemCount;

final boolean[] mIsScrap = new boolean[1];

private RecycleBin mRecycleBin;

private AdapterDataSetObserver mObserver;
private int mWidthMeasureSpec;
private FlingRunnable mFlingRunnable;

protected boolean mClipToPadding;
private PerformClick mPerformClick;

private Runnable mPendingCheckForTap;
private CheckForLongPress mPendingCheckForLongPress;

private class CheckForLongPress extends WindowRunnnable implements Runnable {
public void run() {
final int motionPosition = mMotionPosition;
final View child = getChildAt(motionPosition);
if (child != null) {
final int longPressPosition = mMotionPosition;
final long longPressId = mAdapter.getItemId(mMotionPosition + mFirstPosition);

boolean handled = false;
if (sameWindow() && !mDataChanged) {
handled = performLongPress(child, longPressPosition + mFirstPosition, longPressId);
}
if (handled) {
mTouchMode = TOUCH_MODE_IDLE;
setPressed(false);
child.setPressed(false);
} else {
mTouchMode = TOUCH_MODE_DONE_WAITING;
}

}
}
}


public class FixedViewInfo {

public View view;

public Object data;

public boolean isSelectable;
}

private ArrayList<FixedViewInfo> mHeaderViewInfos;
private ArrayList<FixedViewInfo> mFooterViewInfos;


public ExtendableListView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);

// setting up to be a scrollable view group
setWillNotDraw(false);
setClipToPadding(false);
setFocusableInTouchMode(false);

final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
mFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();

mRecycleBin = new RecycleBin();
mObserver = new AdapterDataSetObserver();

mHeaderViewInfos = new ArrayList<FixedViewInfo>();
mFooterViewInfos = new ArrayList<FixedViewInfo>();

// start our layout mode drawing from the top
mLayoutMode = LAYOUT_NORMAL;
}




@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();

if (mAdapter != null) {
// Data may have changed while we were detached. Refresh.
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
}
mIsAttached = true;
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();

// Detach any view left in the scrap heap
mRecycleBin.clear();

if (mFlingRunnable != null) {
removeCallbacks(mFlingRunnable);
}

mIsAttached = false;
}

@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
// TODO : handle focus and its impact on selection - if we add item selection support
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
// TODO : handle focus and its impact on selection - if we add item selection support
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
onSizeChanged(w, h);
}

protected void onSizeChanged(int w, int h) {
if (getChildCount() > 0) {
stopFlingRunnable();
mRecycleBin.clear();
mDataChanged = true;
rememberSyncState();
}
}



@Override
public ListAdapter getAdapter() {
return mAdapter;
}

@Override
public void setAdapter(final ListAdapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mObserver);
}

// use a wrapper list adapter if we have a header or footer
if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
}
else {
mAdapter = adapter;
}

mDataChanged = true;
mItemCount = mAdapter != null ? mAdapter.getCount() : 0;

if (mAdapter != null) {
mAdapter.registerDataSetObserver(mObserver);
mRecycleBin.setViewTypeCount(mAdapter.getViewTypeCount());
}

requestLayout();
}

@Override
public int getCount() {
return mItemCount;
}


@Override
public View getSelectedView() {
if (DBG) Log.e(TAG, "getSelectedView() is not supported in ExtendableListView yet");
return null;
}

@Override
public void setSelection(final int position) {
if (position >= 0) {
mLayoutMode = LAYOUT_SYNC;
mSpecificTop = getListPaddingTop();

mFirstPosition = 0;
if (mNeedSync) {
mSyncPosition = position;
mSyncRowId = mAdapter.getItemId(position);
}
requestLayout();
}
}


public void addHeaderView(View v, Object data, boolean isSelectable) {

if (mAdapter != null && !(mAdapter instanceof HeaderViewListAdapter)) {
throw new IllegalStateException(
"Cannot add header view to list -- setAdapter has already been called.");
}

FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);


if (mAdapter != null && mObserver != null) {
mObserver.onChanged();
}
}


public void addHeaderView(View v) {
addHeaderView(v, null, true);
}

public int getHeaderViewsCount() {
return mHeaderViewInfos.size();
}

boolean removeHeaderView(View v) {
if (mHeaderViewInfos.size() > 0) {
boolean result = false;
if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
if (mObserver != null) {
mObserver.onChanged();
}
result = true;
}
removeFixedViewInfo(v, mHeaderViewInfos);
return result;
}
return false;
}

private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
int len = where.size();
for (int i = 0; i < len; ++i) {
FixedViewInfo info = where.get(i);
if (info.view == v) {
where.remove(i);
break;
}
}
}


public void addFooterView(View v, Object data, boolean isSelectable) {



FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mFooterViewInfos.add(info);

// in the case of re-adding a footer view, or adding one later on,
// we need to notify the observer
if (mAdapter != null && mObserver != null) {
mObserver.onChanged();
}
}


public void addFooterView(View v) {
addFooterView(v, null, true);
}

public int getFooterViewsCount() {
return mFooterViewInfos.size();
}


public boolean removeFooterView(View v) {
if (mFooterViewInfos.size() > 0) {
boolean result = false;
if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
if (mObserver != null) {
mObserver.onChanged();
}
result = true;
}
removeFixedViewInfo(v, mFooterViewInfos);
return result;
}
return false;
}


@Override
public void setClipToPadding(final boolean clipToPadding) {
super.setClipToPadding(clipToPadding);
mClipToPadding = clipToPadding;
}


@Override
public void requestLayout() {
if (!mBlockLayoutRequests && !mInLayout) {
super.requestLayout();
}
}


@Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
// super.onLayout(changed, l, t, r, b); - skipping base AbsListView implementation on purpose
// haven't set an adapter yet? get to it
if (mAdapter == null) {
return;
}

if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycleBin.markChildrenDirty();
}

// TODO get the height of the view??
mInLayout = true;
layoutChildren();
mInLayout = false;
}


@Override
protected void layoutChildren() {
if (mBlockLayoutRequests) return;
mBlockLayoutRequests = true;

try {
super.layoutChildren();
invalidate();

if (mAdapter == null) {
clearState();
invokeOnItemScrollListener();
return;
}

int childrenTop = getListPaddingTop();

int childCount = getChildCount();
View oldFirst = null;

// our last state so we keep our position
if (mLayoutMode == LAYOUT_NORMAL) {
oldFirst = getChildAt(0);
}

boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}

// safety check!
// Handle the empty set by removing all views that are visible
// and calling it a day
if (mItemCount == 0) {
clearState();
invokeOnItemScrollListener();
return;
}
else if (mItemCount != mAdapter.getCount()) {
throw new IllegalStateException("The content of the adapter has changed but "
+ "ExtendableListView did not receive a notification. Make sure the content of "
+ "your adapter is not modified from a background thread, but only "
+ "from the UI thread. [in ExtendableListView(" + getId() + ", " + getClass()
+ ") with Adapter(" + mAdapter.getClass() + ")]");
}

// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycleBin;

if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition + i);
}
}
else {
recycleBin.fillActiveViews(childCount, firstPosition);
}

// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();

switch (mLayoutMode) {
case LAYOUT_FORCE_TOP: {
mFirstPosition = 0;
resetToTop();
adjustViewsUpOrDown();
fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
}
case LAYOUT_SYNC: {
fillSpecific(mSyncPosition, mSpecificTop);
break;
}
case LAYOUT_NORMAL:
default: {
if (childCount == 0) {
fillFromTop(childrenTop);
}
else if (mFirstPosition < mItemCount) {
fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
}
else {
fillSpecific(0, childrenTop);
}
break;
}
}

// Flush any cached views that did not get reused above
recycleBin.scrapActiveViews();
mDataChanged = false;
mNeedSync = false;
mLayoutMode = LAYOUT_NORMAL;
invokeOnItemScrollListener();
} finally {
mBlockLayoutRequests = false;
}
}


@Override
protected void handleDataChanged() {
super.handleDataChanged();

final int count = mItemCount;

if (count > 0 && mNeedSync) {
mNeedSync = false;
mSyncState = null;

mLayoutMode = LAYOUT_SYNC;
mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
return;
}

mLayoutMode = LAYOUT_FORCE_TOP;
mNeedSync = false;
mSyncState = null;

// TODO : add selection handling here
}

public void resetToTop() {
// TO override
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}


@Override
public boolean onTouchEvent(MotionEvent event) {


if (!isEnabled()) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return isClickable() || isLongClickable();
}

initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(event);

if (!hasChildren()) return false;

boolean handled;
final int action = event.getAction() & MotionEventCompat.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
handled = onTouchDown(event);
break;

case MotionEvent.ACTION_MOVE:
handled = onTouchMove(event);
break;

case MotionEvent.ACTION_CANCEL:
handled = onTouchCancel(event);
break;

case MotionEvent.ACTION_POINTER_UP:
handled = onTouchPointerUp(event);
break;

case MotionEvent.ACTION_UP:
handled = onTouchUp(event);
break;

default:
handled = false;
break;
}

notifyTouchMode();

return handled;
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();

if (!mIsAttached) {

return false;
}


switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
int touchMode = mTouchMode;



final int x = (int) ev.getX();
final int y = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);

int motionPosition = findMotionRow(y);
if (touchMode != TOUCH_MODE_FLINGING && motionPosition >= 0) {
// User clicked on an actual view (and was not stopping a fling).
// Remember where the motion event started
mMotionX = x;
mMotionY = y;
mMotionPosition = motionPosition;
mTouchMode = TOUCH_MODE_DOWN;
}
mLastY = Integer.MIN_VALUE;
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
if (touchMode == TOUCH_MODE_FLINGING) {
return true;
}
break;
}

case MotionEvent.ACTION_MOVE: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
pointerIndex = 0;
mActivePointerId = ev.getPointerId(pointerIndex);
}
final int y = (int) ev.getY(pointerIndex);
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
if (startScrollIfNeeded(y)) {
return true;
}
break;
}
break;
}

case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
mTouchMode = TOUCH_MODE_IDLE;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
break;
}

case MotionEvent.ACTION_POINTER_UP: {
onSecondaryPointerUp(ev);
break;
}
}

return false;
}

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
recycleVelocityTracker();
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}

final class CheckForTap implements Runnable {
public void run() {
if (mTouchMode == TOUCH_MODE_DOWN) {
mTouchMode = TOUCH_MODE_TAP;
final View child = getChildAt(mMotionPosition);
if (child != null && !child.hasFocusable()) {
mLayoutMode = LAYOUT_NORMAL;

if (!mDataChanged) {
layoutChildren();
child.setPressed(true);
setPressed(true);

final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
final boolean longClickable = isLongClickable();

if (longClickable) {
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress, longPressTimeout);
} else {
mTouchMode = TOUCH_MODE_DONE_WAITING;
}
} else {
mTouchMode = TOUCH_MODE_DONE_WAITING;
}
}
}
}
}

private boolean onTouchDown(final MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
int motionPosition = pointToPosition(x, y);

mVelocityTracker.clear();
mActivePointerId = MotionEventCompat.getPointerId(event, 0);



if ((mTouchMode != TOUCH_MODE_FLINGING) &&
!mDataChanged &&
motionPosition >= 0 &&
getAdapter().isEnabled(motionPosition)) {
// is it a tap or a scroll .. we don't know yet!
mTouchMode = TOUCH_MODE_DOWN;

if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

if (event.getEdgeFlags() != 0 && motionPosition < 0) {

return false;
}
}
else if (mTouchMode == TOUCH_MODE_FLINGING) {
mTouchMode = TOUCH_MODE_SCROLLING;
mMotionCorrection = 0;
motionPosition = findMotionRow(y);
}

mMotionX = x;
mMotionY = y;
mMotionPosition = motionPosition;
mLastY = Integer.MIN_VALUE;

return true;
}

private boolean onTouchMove(final MotionEvent event) {
final int index = MotionEventCompat.findPointerIndex(event, mActivePointerId);
if (index < 0) {
Log.e(TAG, "onTouchMove could not find pointer with id " +
mActivePointerId + " - did ExtendableListView receive an inconsistent " +
"event stream?");
return false;
}
final int y = (int) MotionEventCompat.getY(event, index);

// our data's changed so we need to do a layout before moving any further
if (mDataChanged) {
layoutChildren();
}

switch (mTouchMode) {
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:

startScrollIfNeeded(y);
break;
case TOUCH_MODE_SCROLLING:

scrollIfNeeded(y);
break;
}

return true;
}


private boolean onTouchCancel(final MotionEvent event) {
mTouchMode = TOUCH_MODE_IDLE;
setPressed(false);
invalidate(); // redraw selector
final Handler handler = getHandler();

if (handler != null) {
handler.removeCallbacks(mPendingCheckForLongPress);
}

recycleVelocityTracker();
mActivePointerId = INVALID_POINTER;
return true;
}

private boolean onTouchUp(final MotionEvent event) {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
return onTouchUpTap(event);

case TOUCH_MODE_SCROLLING:
return onTouchUpScrolling(event);
}

setPressed(false);
invalidate(); // redraw selector

final Handler handler = getHandler();
if (handler != null) {
handler.removeCallbacks(mPendingCheckForLongPress);
}

recycleVelocityTracker();

mActivePointerId = INVALID_POINTER;
return true;
}

private boolean onTouchUpScrolling(final MotionEvent event) {
if (hasChildren()) {
// 2 - Are we at the top or bottom?
int top = getFirstChildTop();
int bottom = getLastChildBottom();
final boolean atEdge = mFirstPosition == 0 &&
top >= getListPaddingTop() &&
mFirstPosition + getChildCount() < mItemCount &&
bottom <= getHeight() - getListPaddingBottom();

if (!atEdge) {
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final float velocity = mVelocityTracker.getYVelocity(mActivePointerId);

if (Math.abs(velocity) > mFlingVelocity) {
startFlingRunnable(velocity);
mTouchMode = TOUCH_MODE_FLINGING;
mMotionY = 0;
invalidate();
return true;
}
}
}

stopFlingRunnable();
recycleVelocityTracker();
mTouchMode = TOUCH_MODE_IDLE;
return true;
}

private boolean onTouchUpTap(final MotionEvent event) {
final int motionPosition = mMotionPosition;
if (motionPosition >= 0) {
final View child = getChildAt(motionPosition);
if (child != null && !child.hasFocusable()) {
if (mTouchMode != TOUCH_MODE_DOWN) {
child.setPressed(false);
}

if (mPerformClick == null) {
invalidate();
mPerformClick = new PerformClick();
}

final PerformClick performClick = mPerformClick;
performClick.mClickMotionPosition = motionPosition;
performClick.rememberWindowAttachCount();

//            mResurrectToPosition = motionPosition;

if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
final Handler handler = getHandler();
if (handler != null) {
handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
mPendingCheckForTap : mPendingCheckForLongPress);
}
mLayoutMode = LAYOUT_NORMAL;
if (!mDataChanged && motionPosition >= 0 && mAdapter.isEnabled(motionPosition)) {
mTouchMode = TOUCH_MODE_TAP;
layoutChildren();
child.setPressed(true);
setPressed(true);
postDelayed(new Runnable() {
public void run() {
child.setPressed(false);
setPressed(false);
if (!mDataChanged) {
post(performClick);
}
mTouchMode = TOUCH_MODE_IDLE;
}
}, ViewConfiguration.getPressedStateDuration());
} else {
mTouchMode = TOUCH_MODE_IDLE;
}
return true;
} else if (!mDataChanged && motionPosition >= 0 && mAdapter.isEnabled(motionPosition)) {
post(performClick);
}
}
}
mTouchMode = TOUCH_MODE_IDLE;

return true;
}

private boolean onTouchPointerUp(final MotionEvent event) {
onSecondaryPointerUp(event);
final int x = mMotionX;
final int y = mMotionY;
final int motionPosition = pointToPosition(x, y);
if (motionPosition >= 0) {
mMotionPosition = motionPosition;
}
mLastY = y;
return true;
}

private void onSecondaryPointerUp(MotionEvent event) {
final int pointerIndex = (event.getAction() &
MotionEventCompat.ACTION_POINTER_INDEX_MASK) >>
MotionEventCompat.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mMotionX = (int) event.getX(newPointerIndex);
mMotionY = (int) event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
recycleVelocityTracker();
}
}


private boolean startScrollIfNeeded(final int y) {
final int deltaY = y - mMotionY;
final int distance = Math.abs(deltaY);
// TODO : Overscroll?
// final boolean overscroll = mScrollY != 0;
final boolean overscroll = false;
if (overscroll || distance > mTouchSlop) {
if (overscroll) {
mMotionCorrection = 0;
}
else {
mTouchMode = TOUCH_MODE_SCROLLING;
mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
}

final Handler handler = getHandler();
if (handler != null) {
handler.removeCallbacks(mPendingCheckForLongPress);
}
setPressed(false);
View motionView = getChildAt(mMotionPosition - mFirstPosition);
if (motionView != null) {
motionView.setPressed(false);
}
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}

scrollIfNeeded(y);
return true;
}
return false;
}

private void scrollIfNeeded(final int y) {
if (DBG) Log.d(TAG, "scrollIfNeeded y: " + y);
final int rawDeltaY = y - mMotionY;
final int deltaY = rawDeltaY - mMotionCorrection;
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;

if (mTouchMode == TOUCH_MODE_SCROLLING) {
if (DBG) Log.d(TAG, "scrollIfNeeded TOUCH_MODE_SCROLLING");
if (y != mLastY) {
// stop our parent
if (Math.abs(rawDeltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}

final int motionIndex;
if (mMotionPosition >= 0) {
motionIndex = mMotionPosition - mFirstPosition;
}
else {

motionIndex = getChildCount() / 2;
}

// No need to do all this work if we're not going to move anyway
boolean atEdge = false;
if (incrementalDeltaY != 0) {
atEdge = moveTheChildren(deltaY, incrementalDeltaY);
}

// Check to see if we have bumped into the scroll limit
View motionView = this.getChildAt(motionIndex);
if (motionView != null) {
if (atEdge) {
// TODO : edge effect & overscroll
}
mMotionY = y;
}
mLastY = y;
}

}
// TODO : ELSE SUPPORT OVERSCROLL!
}

private int findMotionRow(int y) {
int childCount = getChildCount();
if (childCount > 0) {
// always from the top
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
if (y <= v.getBottom()) {
return mFirstPosition + i;
}
}
}
return INVALID_POSITION;
}


private boolean moveTheChildren(int deltaY, int incrementalDeltaY) {
if (DBG) Log.d(TAG, "moveTheChildren deltaY: " + deltaY + "incrementalDeltaY: " + incrementalDeltaY);
// there's nothing to move!
if (!hasChildren()) return true;

final int firstTop = getHighestChildTop();
final int lastBottom = getLowestChildBottom();


int effectivePaddingTop = 0;
int effectivePaddingBottom = 0;
if (mClipToPadding) {
effectivePaddingTop = getListPaddingTop();
effectivePaddingBottom = getListPaddingBottom();
}

final int gridHeight = getHeight();
final int spaceAbove = effectivePaddingTop - getFirstChildTop();
final int end = gridHeight - effectivePaddingBottom;
final int spaceBelow = getLastChildBottom() - end;

final int height = gridHeight - getListPaddingBottom() - getListPaddingTop();

if (incrementalDeltaY < 0) {
incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
}
else {
incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
}

final int firstPosition = mFirstPosition;

int maxTop = getListPaddingTop();
int maxBottom = gridHeight - getListPaddingBottom();
int childCount = getChildCount();

final boolean cannotScrollDown = (firstPosition == 0 &&
firstTop >= maxTop && incrementalDeltaY >= 0);
final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
lastBottom <= maxBottom && incrementalDeltaY <= 0);

if (DBG) {
Log.d(TAG, "moveTheChildren " +
" firstTop " + firstTop +
" maxTop " + maxTop +
" incrementalDeltaY " + incrementalDeltaY);
Log.d(TAG, "moveTheChildren " +
" lastBottom " + lastBottom +
" maxBottom " + maxBottom +
" incrementalDeltaY " + incrementalDeltaY);
}

if (cannotScrollDown) {
if (DBG) Log.d(TAG, "moveTheChildren cannotScrollDown " + cannotScrollDown);
return incrementalDeltaY != 0;
}

if (cannotScrollUp) {
if (DBG) Log.d(TAG, "moveTheChildren cannotScrollUp " + cannotScrollUp);
return incrementalDeltaY != 0;
}

final boolean isDown = incrementalDeltaY < 0;

final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();

int start = 0;
int count = 0;

if (isDown) {
int top = -incrementalDeltaY;
if (mClipToPadding) {
top += getListPaddingTop();
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top) {
break;
}
else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
mRecycleBin.addScrapView(child, position);
}
}
}
}
else {
int bottom = gridHeight - incrementalDeltaY;
if (mClipToPadding) {
bottom -= getListPaddingBottom();
}
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= bottom) {
break;
}
else {
start = i;
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
mRecycleBin.addScrapView(child, position);
}
}
}
}

mBlockLayoutRequests = true;

if (count > 0) {
if (DBG) Log.d(TAG, "scrap - detachViewsFromParent start:" + start + " count:" + count);
detachViewsFromParent(start, count);
mRecycleBin.removeSkippedScrap();
onChildrenDetached(start, count);
}


if (!awakenScrollBars()) {
invalidate();
}

offsetChildrenTopAndBottom(incrementalDeltaY);

if (isDown) {
mFirstPosition += count;
}

final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(isDown);
}

// TODO : touch mode selector handling
mBlockLayoutRequests = false;
invokeOnItemScrollListener();

return false;
}

protected void onChildrenDetached(final int start, final int count) {

}


protected void fillGap(boolean down) {
final int count = getChildCount();
if (down) {
// fill down from the top of the position below our last
int position = mFirstPosition + count;
final int startOffset = getChildTop(position);
fillDown(position, startOffset);
}
else {
// fill up from the bottom of the position above our first.
int position = mFirstPosition - 1;
final int startOffset = getChildBottom(position);
fillUp(position, startOffset);
}
adjustViewsAfterFillGap(down);
}

protected void adjustViewsAfterFillGap(boolean down) {
if (down) {
correctTooHigh(getChildCount());
}
else {
correctTooLow(getChildCount());
}
}

private View fillDown(int pos, int nextTop) {
if (DBG) Log.d(TAG, "fillDown - pos:" + pos + " nextTop:" + nextTop);

View selectedView = null;

int end = getHeight();
if (mClipToPadding) {
end -= getListPaddingBottom();
}

while ((nextTop < end || hasSpaceDown()) && pos < mItemCount) {
// TODO : add selection support
makeAndAddView(pos, nextTop, true, false);
pos++;
nextTop = getNextChildDownsTop(pos); // = child.getBottom();
}

return selectedView;
}


protected boolean hasSpaceDown() {
return false;
}

private View fillUp(int pos, int nextBottom) {
if (DBG) Log.d(TAG, "fillUp - position:" + pos + " nextBottom:" + nextBottom);
View selectedView = null;

int end = mClipToPadding ? getListPaddingTop() : 0;

while ((nextBottom > end || hasSpaceUp()) && pos >= 0) {
// TODO : add selection support
makeAndAddView(pos, nextBottom, false, false);
pos--;
nextBottom = getNextChildUpsBottom(pos);
if (DBG) Log.d(TAG, "fillUp next - position:" + pos + " nextBottom:" + nextBottom);
}

mFirstPosition = pos + 1;
return selectedView;
}


protected boolean hasSpaceUp() {
return false;
}


private View fillFromTop(int nextTop) {
mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
if (mFirstPosition < 0) {
mFirstPosition = 0;
}
return fillDown(mFirstPosition, nextTop);
}


private View fillSpecific(int position, int top) {
boolean tempIsSelected = false; // ain't no body got time for that @ Etsy
View temp = makeAndAddView(position, top, true, tempIsSelected);
// Possibly changed again in fillUp if we add rows above this one.
mFirstPosition = position;

View above;
View below;

int nextBottom = getNextChildUpsBottom(position - 1);
int nextTop = getNextChildDownsTop(position + 1);

above = fillUp(position - 1, nextBottom);
// This will correct for the top of the first view not touching the top of the list
adjustViewsUpOrDown();
below = fillDown(position + 1, nextTop);
int childCount = getChildCount();
if (childCount > 0) {
correctTooHigh(childCount);
}

if (tempIsSelected) {
return temp;
}
else if (above != null) {
return above;
}
else {
return below;
}
}


private View makeAndAddView(int position, int y, boolean flowDown, boolean selected) {
View child;

onChildCreated(position, flowDown);

if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycleBin.getActiveView(position);
if (child != null) {

// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flowDown, selected, true);
return child;
}
}

// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flowDown, selected, mIsScrap[0]);

return child;
}


private void setupChild(View child, int position, int y, boolean flowDown,
boolean selected, boolean recycled) {
final boolean isSelected = false; // TODO : selected && shouldShowSelector();
final boolean updateChildSelected = isSelected != child.isSelected();
final int mode = mTouchMode;
final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLLING &&
mMotionPosition == position;
final boolean updateChildPressed = isPressed != child.isPressed();
final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();

int itemViewType = mAdapter.getItemViewType(position);

LayoutParams layoutParams;
if (itemViewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
layoutParams = generateWrapperLayoutParams(child);
}
else {
layoutParams = generateChildLayoutParams(child);
}

layoutParams.viewType = itemViewType;
layoutParams.position = position;

if (recycled || (layoutParams.recycledHeaderFooter &&
layoutParams.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
if (DBG) Log.d(TAG, "setupChild attachViewToParent position:" + position);
attachViewToParent(child, flowDown ? -1 : 0, layoutParams);
}
else {
if (DBG) Log.d(TAG, "setupChild addViewInLayout position:" + position);
if (layoutParams.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
layoutParams.recycledHeaderFooter = true;
}
addViewInLayout(child, flowDown ? -1 : 0, layoutParams, true);
}

if (updateChildSelected) {
child.setSelected(isSelected);
}

if (updateChildPressed) {
child.setPressed(isPressed);
}

if (needToMeasure) {
if (DBG) Log.d(TAG, "setupChild onMeasureChild position:" + position);
onMeasureChild(child, layoutParams);
}
else {
if (DBG) Log.d(TAG, "setupChild cleanupLayoutState position:" + position);
cleanupLayoutState(child);
}

final int w = child.getMeasuredWidth();
final int h = child.getMeasuredHeight();
final int childTop = flowDown ? y : y - h;

if (DBG) {
Log.d(TAG, "setupChild position:" + position + " h:" + h + " w:" + w);
}

final int childrenLeft = getChildLeft(position);

if (needToMeasure) {
final int childRight = childrenLeft + w;
final int childBottom = childTop + h;
onLayoutChild(child, position, flowDown, childrenLeft, childTop, childRight, childBottom);
}
else {
onOffsetChild(child, position, flowDown, childrenLeft, childTop);
}

}

protected LayoutParams generateChildLayoutParams(final View child) {
return generateWrapperLayoutParams(child);
}

protected LayoutParams generateWrapperLayoutParams(final View child) {
LayoutParams layoutParams = null;

final ViewGroup.LayoutParams childParams = child.getLayoutParams();
if (childParams != null) {
if (childParams instanceof LayoutParams) {
layoutParams = (LayoutParams) childParams;
}
else {
layoutParams = new LayoutParams(childParams);
}
}
if (layoutParams == null) {
layoutParams = generateDefaultLayoutParams();
}

return layoutParams;
}



protected void onMeasureChild(final View child, final LayoutParams layoutParams) {
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
getListPaddingLeft() + getListPaddingRight(), layoutParams.width);
int lpHeight = layoutParams.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
}
else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}

protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}

protected LayoutParams generateHeaderFooterLayoutParams(final View child) {
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}


private View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;

scrapView = mRecycleBin.getScrapView(position);

View child;
if (scrapView != null) {
if (DBG) Log.d(TAG, "getView from scrap position:" + position);
child = mAdapter.getView(position, scrapView, this);

if (child != scrapView) {
mRecycleBin.addScrapView(scrapView, position);
}
else {
isScrap[0] = true;
}
}
else {
if (DBG) Log.d(TAG, "getView position:" + position);
child = mAdapter.getView(position, null, this);
}

return child;
}



private void correctTooHigh(int childCount) {
// First see if the last item is visible. If it is not, it is OK for the
// top of the list to be pushed up.
int lastPosition = mFirstPosition + childCount - 1;
if (lastPosition == mItemCount - 1 && childCount > 0) {

// ... and its bottom edge
final int lastBottom = getLowestChildBottom();

// This is bottom of our drawable area
final int end = (getBottom() - getTop()) - getListPaddingBottom();

// This is how far the bottom edge of the last view is from the bottom of the
// drawable area
int bottomOffset = end - lastBottom;

final int firstTop = getHighestChildTop();

// Make sure we are 1) Too high, and 2) Either there are more rows above the
// first row or the first row is scrolled off the top of the drawable area
if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < getListPaddingTop())) {
if (mFirstPosition == 0) {
// Don't pull the top too far down
bottomOffset = Math.min(bottomOffset, getListPaddingTop() - firstTop);
}
// Move everything down
offsetChildrenTopAndBottom(bottomOffset);
if (mFirstPosition > 0) {
// Fill the gap that was opened above mFirstPosition with more rows, if
// possible
int previousPosition = mFirstPosition - 1;
fillUp(previousPosition, getNextChildUpsBottom(previousPosition));
// Close up the remaining gap
adjustViewsUpOrDown();
}

}
}
}


private void correctTooLow(int childCount) {
// First see if the first item is visible. If it is not, it is OK for the
// bottom of the list to be pushed down.
if (mFirstPosition == 0 && childCount > 0) {

// ... and its top edge
final int firstTop = getHighestChildTop();

// This is top of our drawable area
final int start = getListPaddingTop();

// This is bottom of our drawable area
final int end = (getTop() - getBottom()) - getListPaddingBottom();

// This is how far the top edge of the first view is from the top of the
// drawable area
int topOffset = firstTop - start;
final int lastBottom = getLowestChildBottom();

int lastPosition = mFirstPosition + childCount - 1;

// Make sure we are 1) Too low, and 2) Either there are more rows below the
// last row or the last row is scrolled off the bottom of the drawable area
if (topOffset > 0) {
if (lastPosition < mItemCount - 1 || lastBottom > end) {
if (lastPosition == mItemCount - 1) {
// Don't pull the bottom too far up
topOffset = Math.min(topOffset, lastBottom - end);
}
// Move everything up
offsetChildrenTopAndBottom(-topOffset);
if (lastPosition < mItemCount - 1) {
// Fill the gap that was opened below the last position with more rows, if
// possible
int nextPosition = lastPosition + 1;
fillDown(nextPosition, getNextChildDownsTop(nextPosition));
// Close up the remaining gap
adjustViewsUpOrDown();
}
}
else if (lastPosition == mItemCount - 1) {
adjustViewsUpOrDown();
}
}
}
}


private void adjustViewsUpOrDown() {
final int childCount = getChildCount();
int delta;

if (childCount > 0) {
// Uh-oh -- we came up short. Slide all views up to make them
// align with the top
delta = getHighestChildTop() - getListPaddingTop();
if (delta < 0) {
// We only are looking to see if we are too low, not too high
delta = 0;
}

if (delta != 0) {
offsetChildrenTopAndBottom(-delta);
}
}
}


protected void onChildCreated(final int position, final boolean flowDown) {

}


protected void onLayoutChild(final View child, final int position,
final boolean flowDown, final int childrenLeft, final int childTop,
final int childRight, final int childBottom) {
child.layout(childrenLeft, childTop, childRight, childBottom);
}


protected void onOffsetChild(final View child, final int position,
final boolean flowDown, final int childrenLeft, final int childTop) {
child.offsetLeftAndRight(childrenLeft - child.getLeft());
child.offsetTopAndBottom(childTop - child.getTop());
}


protected int getChildLeft(final int position) {
return getListPaddingLeft();
}


protected int getChildTop(final int position) {
int count = getChildCount();
int paddingTop = 0;
if (mClipToPadding) {
paddingTop = getListPaddingTop();
}
return count > 0 ? getChildAt(count - 1).getBottom() : paddingTop;
}


protected int getChildBottom(final int position) {
int count = getChildCount();
int paddingBottom = 0;
if (mClipToPadding) {
paddingBottom = getListPaddingBottom();
}
return count > 0 ? getChildAt(0).getTop() : getHeight() - paddingBottom;
}

protected int getNextChildDownsTop(final int position) {
final int count = getChildCount();
return count > 0 ? getChildAt(count - 1).getBottom() : 0;
}

protected int getNextChildUpsBottom(final int position) {
final int count = getChildCount();
if (count == 0) {
return 0;
}
return count > 0 ? getChildAt(0).getTop() : 0;
}

protected int getFirstChildTop() {
return hasChildren() ? getChildAt(0).getTop() : 0;
}

protected int getHighestChildTop() {
return hasChildren() ? getChildAt(0).getTop() : 0;
}

protected int getLastChildBottom() {
return hasChildren() ? getChildAt(getChildCount() - 1).getBottom() : 0;
}

protected int getLowestChildBottom() {
return hasChildren() ? getChildAt(getChildCount() - 1).getBottom() : 0;
}

protected boolean hasChildren() {
return getChildCount() > 0;
}

protected void offsetChildrenTopAndBottom(int offset) {
if (DBG) Log.d(TAG, "offsetChildrenTopAndBottom: " + offset);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View v = getChildAt(i);
v.offsetTopAndBottom(offset);
}
}

@Override
public int getFirstVisiblePosition() {
return Math.max(0, mFirstPosition - getHeaderViewsCount());
}

@Override
public int getLastVisiblePosition() {
return Math.min(mFirstPosition + getChildCount() - 1, mAdapter != null ? mAdapter.getCount() - 1 : 0);
}



private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
else {
mVelocityTracker.clear();
}
}

private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}

private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}

private void startFlingRunnable(final float velocity) {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
mFlingRunnable.start((int) -velocity);
}

private void stopFlingRunnable() {
if (mFlingRunnable != null) {
mFlingRunnable.endFling();
}
}


private class FlingRunnable implements Runnable {

private final Scroller mScroller;


private int mLastFlingY;

FlingRunnable() {
mScroller = new Scroller(getContext());
}

void start(int initialVelocity) {
int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
mLastFlingY = initialY;
mScroller.forceFinished(true);
mScroller.fling(0, initialY, 0, initialVelocity, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
mTouchMode = TOUCH_MODE_FLINGING;
postOnAnimate(this);
}

void startScroll(int distance, int duration) {
int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
mLastFlingY = initialY;
mScroller.startScroll(0, initialY, 0, distance, duration);
mTouchMode = TOUCH_MODE_FLINGING;
postOnAnimate(this);
}

private void endFling() {
mLastFlingY = 0;
mTouchMode = TOUCH_MODE_IDLE;

reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
removeCallbacks(this);

mScroller.forceFinished(true);
}

public void run() {
switch (mTouchMode) {
default:
return;

case TOUCH_MODE_FLINGING: {
if (mItemCount == 0 || getChildCount() == 0) {
endFling();
return;
}

final Scroller scroller = mScroller;
boolean more = scroller.computeScrollOffset();
final int y = scroller.getCurrY();


int delta = mLastFlingY - y;

// Pretend that each frame of a fling scroll is a touch scroll
if (delta > 0) {
// List is moving towards the top. Use first view as mMotionPosition
mMotionPosition = mFirstPosition;
// Don't fling more than 1 screen
delta = Math.min(getHeight() - getPaddingBottom() - getPaddingTop() - 1, delta);
}
else {
// List is moving towards the bottom. Use last view as mMotionPosition
int offsetToLast = getChildCount() - 1;
mMotionPosition = mFirstPosition + offsetToLast;

// Don't fling more than 1 screen
delta = Math.max(-(getHeight() - getPaddingBottom() - getPaddingTop() - 1), delta);
}

final boolean atEnd = moveTheChildren(delta, delta);

if (more && !atEnd) {
invalidate();
mLastFlingY = y;
postOnAnimate(this);
}
else {
endFling();
}
break;
}
}
}

}

private void postOnAnimate(Runnable runnable) {
ViewCompat.postOnAnimation(this, runnable);
}


public void notifyTouchMode() {
// only tell the scroll listener about some things we want it to know
switch (mTouchMode) {
case TOUCH_MODE_SCROLLING:
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
break;
case TOUCH_MODE_FLINGING:
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
break;
case TOUCH_MODE_IDLE:
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
break;
}
}

private OnScrollListener mOnScrollListener;

public void setOnScrollListener(OnScrollListener scrollListener) {
super.setOnScrollListener(scrollListener);
mOnScrollListener = scrollListener;
}

void reportScrollStateChange(int newState) {
if (newState != mScrollState) {
mScrollState = newState;
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(this, newState);
}
}
}

void invokeOnItemScrollListener() {
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
}
}


@SuppressLint("WrongCall") private void updateEmptyStatus() {
boolean empty = getAdapter() == null || getAdapter().isEmpty();
if (isInFilterMode()) {
empty = false;
}

View emptyView = getEmptyView();
if (empty) {
if (emptyView != null) {
emptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
}
else {
// If the caller just removed our empty view, make sure the list view is visible
setVisibility(View.VISIBLE);
}

// We are now GONE, so pending layouts will not be dispatched.
// Force one here to make sure that the state of the list matches
// the state of the adapter.
if (mDataChanged) {
this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
}
}
else {
if (emptyView != null) {
emptyView.setVisibility(View.GONE);
}
setVisibility(View.VISIBLE);
}
}

// //////////////////////////////////////////////////////////////////////////////////////////
// ADAPTER OBSERVER
//

class AdapterDataSetObserver extends DataSetObserver {

private Parcelable mInstanceState = null;

@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();

mRecycleBin.clearTransientStateViews();

// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (ExtendableListView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
ExtendableListView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
}
else {
rememberSyncState();
}

updateEmptyStatus();
requestLayout();
}

@Override
public void onInvalidated() {
mDataChanged = true;

if (ExtendableListView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = ExtendableListView.this.onSaveInstanceState();
}

// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mNeedSync = false;

updateEmptyStatus();
requestLayout();
}

public void clearSavedState() {
mInstanceState = null;
}
}



public static class LayoutParams extends AbsListView.LayoutParams {

boolean recycledHeaderFooter;

// Position of the view in the data
int position;

// adapter ID the view represents fetched from the adapter if it's stable
long itemId = -1;

// adapter view type
int viewType;

public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}

public LayoutParams(int w, int h) {
super(w, h);
}

public LayoutParams(int w, int h, int viewType) {
super(w, h);
this.viewType = viewType;
}

public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}

}


class RecycleBin {

private int mFirstActivePosition;


private View[] mActiveViews = new View[0];

private ArrayList<View>[] mScrapViews;

private int mViewTypeCount;

private ArrayList<View> mCurrentScrap;

private ArrayList<View> mSkippedScrap;

private SparseArrayCompat<View> mTransientStateViews;

public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}

public void markChildrenDirty() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
scrap.get(i).forceLayout();
}
}
else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
final int scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
scrap.get(j).forceLayout();
}
}
}
if (mTransientStateViews != null) {
final int count = mTransientStateViews.size();
for (int i = 0; i < count; i++) {
mTransientStateViews.valueAt(i).forceLayout();
}
}
}

public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
}


void clear() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
}
}
else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
final int scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
}
}
}
if (mTransientStateViews != null) {
mTransientStateViews.clear();
}
}


void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;

final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't put header or footer views into the scrap heap
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
//        However, we will NOT place them into scrap views.
activeViews[i] = child;
}
}
}


View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >= 0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}

View getTransientStateView(int position) {
if (mTransientStateViews == null) {
return null;
}
final int index = mTransientStateViews.indexOfKey(position);
if (index < 0) {
return null;
}
final View result = mTransientStateViews.valueAt(index);
mTransientStateViews.removeAt(index);
return result;
}


void clearTransientStateViews() {
if (mTransientStateViews != null) {
mTransientStateViews.clear();
}
}


View getScrapView(int position) {
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
}
else {
int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
}
return null;
}


void addScrapView(View scrap, int position) {
if (DBG) Log.d(TAG, "addScrapView position = " + position);

LayoutParams lp = (LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}

lp.position = position;

// Don't put header or footer views or views that should be ignored
// into the scrap heap
int viewType = lp.viewType;
final boolean scrapHasTransientState = ViewCompat.hasTransientState(scrap);
if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<View>();
}
mSkippedScrap.add(scrap);
}
if (scrapHasTransientState) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArrayCompat<View>();
}
mTransientStateViews.put(position, scrap);
}
return;
}

if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
}
else {
mScrapViews[viewType].add(scrap);
}
}


void removeSkippedScrap() {
if (mSkippedScrap == null) {
return;
}
final int count = mSkippedScrap.size();
for (int i = 0; i < count; i++) {
removeDetachedView(mSkippedScrap.get(i), false);
}
mSkippedScrap.clear();
}


void scrapActiveViews() {
final View[] activeViews = mActiveViews;
final boolean multipleScraps = mViewTypeCount > 1;

ArrayList<View> scrapViews = mCurrentScrap;
final int count = activeViews.length;
for (int i = count - 1; i >= 0; i--) {
final View victim = activeViews[i];
if (victim != null) {
final LayoutParams lp = (LayoutParams) victim.getLayoutParams();
activeViews[i] = null;

final boolean scrapHasTransientState = ViewCompat.hasTransientState(victim);
int viewType = lp.viewType;

if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
// Do not move views that should be ignored
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
removeDetachedView(victim, false);
}
if (scrapHasTransientState) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArrayCompat<View>();
}
mTransientStateViews.put(mFirstActivePosition + i, victim);
}
continue;
}

if (multipleScraps) {
scrapViews = mScrapViews[viewType];
}
lp.position = mFirstActivePosition + i;
scrapViews.add(victim);
}
}

pruneScrapViews();
}


private void pruneScrapViews() {
final int maxViews = mActiveViews.length;
final int viewTypeCount = mViewTypeCount;
final ArrayList<View>[] scrapViews = mScrapViews;
for (int i = 0; i < viewTypeCount; ++i) {
final ArrayList<View> scrapPile = scrapViews[i];
int size = scrapPile.size();
final int extras = size - maxViews;
size--;
for (int j = 0; j < extras; j++) {
removeDetachedView(scrapPile.remove(size--), false);
}
}

if (mTransientStateViews != null) {
for (int i = 0; i < mTransientStateViews.size(); i++) {
final View v = mTransientStateViews.valueAt(i);
if (!ViewCompat.hasTransientState(v)) {
mTransientStateViews.removeAt(i);
i--;
}
}
}
}


void setCacheColorHint(int color) {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
scrap.get(i).setDrawingCacheBackgroundColor(color);
}
}
else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
final int scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
scrap.get(j).setDrawingCacheBackgroundColor(color);
}
}
}
// Just in case this is called during a layout pass
final View[] activeViews = mActiveViews;
final int count = activeViews.length;
for (int i = 0; i < count; ++i) {
final View victim = activeViews[i];
if (victim != null) {
victim.setDrawingCacheBackgroundColor(color);
}
}
}
}

static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position.
for (int i = 0; i < size; i++) {
View view = scrapViews.get(i);
if (((LayoutParams) view.getLayoutParams()).position == position) {
scrapViews.remove(i);
return view;
}
}
return scrapViews.remove(size - 1);
}
else {
return null;
}
}


protected int mSyncPosition;


protected int mSpecificTop;


long mSyncRowId = INVALID_ROW_ID;


long mSyncHeight;


boolean mNeedSync = false;


private ListSavedState mSyncState;



void rememberSyncState() {
if (getChildCount() > 0) {
mNeedSync = true;
mSyncHeight = getHeight();
// Sync the based on the offset of the first view
View v = getChildAt(0);
ListAdapter adapter = getAdapter();
if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
mSyncRowId = adapter.getItemId(mFirstPosition);
}
else {
mSyncRowId = NO_ID;
}
if (v != null) {
mSpecificTop = v.getTop();
}
mSyncPosition = mFirstPosition;
}
}

private void clearState() {
// cleanup headers and footers before removing the views
clearRecycledState(mHeaderViewInfos);
clearRecycledState(mFooterViewInfos);

removeAllViewsInLayout();
mFirstPosition = 0;
mDataChanged = false;
mRecycleBin.clear();
mNeedSync = false;
mSyncState = null;
mLayoutMode = LAYOUT_NORMAL;
invalidate();
}

private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
if (infos == null) return;
for (FixedViewInfo info : infos) {
final View child = info.view;
final ViewGroup.LayoutParams p = child.getLayoutParams();

if (p instanceof LayoutParams) {
((LayoutParams) p).recycledHeaderFooter = false;
}
}
}

public static class ListSavedState extends ClassLoaderSavedState {
protected long selectedId;
protected long firstId;
protected int viewTop;
protected int position;
protected int height;


public ListSavedState(Parcelable superState) {
super(superState, AbsListView.class.getClassLoader());
}


public ListSavedState(Parcel in) {
super(in);
selectedId = in.readLong();
firstId = in.readLong();
viewTop = in.readInt();
position = in.readInt();
height = in.readInt();
}

@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeLong(selectedId);
out.writeLong(firstId);
out.writeInt(viewTop);
out.writeInt(position);
out.writeInt(height);
}

@Override
public String toString() {
return "ExtendableListView.ListSavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " selectedId=" + selectedId
+ " firstId=" + firstId
+ " viewTop=" + viewTop
+ " position=" + position
+ " height=" + height + "}";
}

public static final Creator<ListSavedState> CREATOR
= new Creator<ListSavedState>() {
public ListSavedState createFromParcel(Parcel in) {
return new ListSavedState(in);
}

public ListSavedState[] newArray(int size) {
return new ListSavedState[size];
}
};
}


@Override
public Parcelable onSaveInstanceState() {

Parcelable superState = super.onSaveInstanceState();
ListSavedState ss = new ListSavedState(superState);

if (mSyncState != null) {
// Just keep what we last restored.
ss.selectedId = mSyncState.selectedId;
ss.firstId = mSyncState.firstId;
ss.viewTop = mSyncState.viewTop;
ss.position = mSyncState.position;
ss.height = mSyncState.height;
return ss;
}

boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
ss.selectedId = getSelectedItemId();
ss.height = getHeight();

// TODO : sync selection when we handle it
if (haveChildren && mFirstPosition > 0) {

View v = getChildAt(0);
ss.viewTop = v.getTop();
int firstPos = mFirstPosition;
if (firstPos >= mItemCount) {
firstPos = mItemCount - 1;
}
ss.position = firstPos;
ss.firstId = mAdapter.getItemId(firstPos);
}
else {
ss.viewTop = 0;
ss.firstId = INVALID_POSITION;
ss.position = 0;
}

return ss;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
ListSavedState ss = (ListSavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mDataChanged = true;

mSyncHeight = ss.height;

if (ss.firstId >= 0) {
mNeedSync = true;
mSyncState = ss;
mSyncRowId = ss.firstId;
mSyncPosition = ss.position;
mSpecificTop = ss.viewTop;
}
requestLayout();
}

private class PerformClick extends WindowRunnnable implements Runnable {
int mClickMotionPosition;

public void run() {
if (mDataChanged) return;

final ListAdapter adapter = mAdapter;
final int motionPosition = mClickMotionPosition;
if (adapter != null && mItemCount > 0 &&
motionPosition != INVALID_POSITION &&
motionPosition < adapter.getCount() && sameWindow()) {
final View view = getChildAt(motionPosition); // a fix by @pboos

if (view != null) {
final int clickPosition = motionPosition + mFirstPosition;
performItemClick(view, clickPosition, adapter.getItemId(clickPosition));
}
}
}
}

private boolean performLongPress(final View child,
final int longPressPosition, final long longPressId) {
boolean handled = false;

OnItemLongClickListener onItemLongClickListener = getOnItemLongClickListener();
if (onItemLongClickListener != null) {
handled = onItemLongClickListener.onItemLongClick(ExtendableListView.this, child,
longPressPosition, longPressId);
}

if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}


private class WindowRunnnable {
private int mOriginalAttachCount;

public void rememberWindowAttachCount() {
mOriginalAttachCount = getWindowAttachCount();
}

public boolean sameWindow() {
return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
}
}
}

 

HeaderViewListAdapter.java

 


package com.android.grid;

import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ListAdapter;
import android.widget.WrapperListAdapter;

import java.util.ArrayList;


public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {

private final ListAdapter mAdapter;

// These two ArrayList are assumed to NOT be null.
// They are indeed created when declared in ListView and then shared.
ArrayList<StaggeredGridView.FixedViewInfo> mHeaderViewInfos;
ArrayList<StaggeredGridView.FixedViewInfo> mFooterViewInfos;

// Used as a placeholder in case the provided info views are indeed null.
// Currently only used by some CTS tests, which may be removed.
static final ArrayList<StaggeredGridView.FixedViewInfo> EMPTY_INFO_LIST =
new ArrayList<StaggeredGridView.FixedViewInfo>();

boolean mAreAllFixedViewsSelectable;

private final boolean mIsFilterable;

public HeaderViewListAdapter(ArrayList<StaggeredGridView.FixedViewInfo> headerViewInfos,
ArrayList<StaggeredGridView.FixedViewInfo> footerViewInfos,
ListAdapter adapter) {
mAdapter = adapter;
mIsFilterable = adapter instanceof Filterable;

if (headerViewInfos == null) {
mHeaderViewInfos = EMPTY_INFO_LIST;
} else {
mHeaderViewInfos = headerViewInfos;
}

if (footerViewInfos == null) {
mFooterViewInfos = EMPTY_INFO_LIST;
} else {
mFooterViewInfos = footerViewInfos;
}

mAreAllFixedViewsSelectable =
areAllListInfosSelectable(mHeaderViewInfos)
&& areAllListInfosSelectable(mFooterViewInfos);
}

public int getHeadersCount() {
return mHeaderViewInfos.size();
}

public int getFootersCount() {
return mFooterViewInfos.size();
}

public boolean isEmpty() {
return mAdapter == null || mAdapter.isEmpty();
}

private boolean areAllListInfosSelectable(ArrayList<StaggeredGridView.FixedViewInfo> infos) {
if (infos != null) {
for (StaggeredGridView.FixedViewInfo info : infos) {
if (!info.isSelectable) {
return false;
}
}
}
return true;
}

public boolean removeHeader(View v) {
for (int i = 0; i < mHeaderViewInfos.size(); i++) {
StaggeredGridView.FixedViewInfo info = mHeaderViewInfos.get(i);
if (info.view == v) {
mHeaderViewInfos.remove(i);

mAreAllFixedViewsSelectable =
areAllListInfosSelectable(mHeaderViewInfos)
&& areAllListInfosSelectable(mFooterViewInfos);

return true;
}
}

return false;
}

public boolean removeFooter(View v) {
for (int i = 0; i < mFooterViewInfos.size(); i++) {
StaggeredGridView.FixedViewInfo info = mFooterViewInfos.get(i);
if (info.view == v) {
mFooterViewInfos.remove(i);

mAreAllFixedViewsSelectable =
areAllListInfosSelectable(mHeaderViewInfos)
&& areAllListInfosSelectable(mFooterViewInfos);

return true;
}
}

return false;
}

public int getCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getCount();
} else {
return getFootersCount() + getHeadersCount();
}
}

public boolean areAllItemsEnabled() {
if (mAdapter != null) {
return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
} else {
return true;
}
}

public boolean isEnabled(int position) {
// Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).isSelectable;
}

// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.isEnabled(adjPosition);
}
}

// Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;
}

public Object getItem(int position) {
// Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).data;
}

// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItem(adjPosition);
}
}

// Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).data;
}

public long getItemId(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemId(adjPosition);
}
}
return -1;
}

public boolean hasStableIds() {
if (mAdapter != null) {
return mAdapter.hasStableIds();
}
return false;
}

public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).view;
}

// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
}

// Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).view;
}

public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}

return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}

public int getViewTypeCount() {
if (mAdapter != null) {
return mAdapter.getViewTypeCount();
}
return 1;
}

public void registerDataSetObserver(DataSetObserver observer) {
if (mAdapter != null) {
mAdapter.registerDataSetObserver(observer);
}
}

public void unregisterDataSetObserver(DataSetObserver observer) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(observer);
}
}

public Filter getFilter() {
if (mIsFilterable) {
return ((Filterable) mAdapter).getFilter();
}
return null;
}

public ListAdapter getWrappedAdapter() {
return mAdapter;
}
}

 

StaggeredGridView.java

 


package com.android.grid;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;

import java.util.Arrays;


public class StaggeredGridView extends ExtendableListView {

private static final String TAG = "StaggeredGridView";
private static final boolean DBG = false;

private static final int DEFAULT_COLUMNS_PORTRAIT = 2;
private static final int DEFAULT_COLUMNS_LANDSCAPE = 3;

private int mColumnCount;
private int mItemMargin;
private int mColumnWidth;
private boolean mNeedSync;

private int mColumnCountPortrait = DEFAULT_COLUMNS_PORTRAIT;
private int mColumnCountLandscape = DEFAULT_COLUMNS_LANDSCAPE;


private SparseArray<GridItemRecord> mPositionData;
private int mGridPaddingLeft;
private int mGridPaddingRight;
private int mGridPaddingTop;
private int mGridPaddingBottom;


static class GridItemRecord implements Parcelable {
int column;
double heightRatio;
boolean isHeaderFooter;

GridItemRecord() { }


private GridItemRecord(Parcel in) {
column = in.readInt();
heightRatio = in.readDouble();
isHeaderFooter = in.readByte() == 1;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(column);
out.writeDouble(heightRatio);
out.writeByte((byte) (isHeaderFooter ? 1 : 0));
}

@Override
public String toString() {
return "GridItemRecord.ListSavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " column:" + column
+ " heightRatio:" + heightRatio
+ " isHeaderFooter:" + isHeaderFooter
+ "}";
}

public static final Parcelable.Creator<GridItemRecord> CREATOR
= new Parcelable.Creator<GridItemRecord>() {
public GridItemRecord createFromParcel(Parcel in) {
return new GridItemRecord(in);
}

public GridItemRecord[] newArray(int size) {
return new GridItemRecord[size];
}
};
}


private int[] mColumnTops;


private int[] mColumnBottoms;


private int[] mColumnLefts;


private int mDistanceToTop;

public StaggeredGridView(final Context context) {
this(context, null);
}

public StaggeredGridView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}

public StaggeredGridView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);

if (attrs != null) {
// get the number of columns in portrait and landscape
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StaggeredGridView, defStyle, 0);

mColumnCount = typedArray.getInteger(
R.styleable.StaggeredGridView_column_count, 0);

if (mColumnCount > 0) {
mColumnCountPortrait = mColumnCount;
mColumnCountLandscape = mColumnCount;
}
else {
mColumnCountPortrait = typedArray.getInteger(
R.styleable.StaggeredGridView_column_count_portrait,
DEFAULT_COLUMNS_PORTRAIT);
mColumnCountLandscape = typedArray.getInteger(
R.styleable.StaggeredGridView_column_count_landscape,
DEFAULT_COLUMNS_LANDSCAPE);
}

mItemMargin = typedArray.getDimensionPixelSize(
R.styleable.StaggeredGridView_item_margin, 0);
mGridPaddingLeft = typedArray.getDimensionPixelSize(
R.styleable.StaggeredGridView_grid_paddingLeft, 0);
mGridPaddingRight = typedArray.getDimensionPixelSize(
R.styleable.StaggeredGridView_grid_paddingRight, 0);
mGridPaddingTop = typedArray.getDimensionPixelSize(
R.styleable.StaggeredGridView_grid_paddingTop, 0);
mGridPaddingBottom = typedArray.getDimensionPixelSize(
R.styleable.StaggeredGridView_grid_paddingBottom, 0);

typedArray.recycle();
}

mColumnCount = 0; // determined onMeasure
// Creating these empty arrays to avoid saving null states
mColumnTops = new int[0];
mColumnBottoms = new int[0];
mColumnLefts = new int[0];
mPositionData = new SparseArray<GridItemRecord>();
}

// //////////////////////////////////////////////////////////////////////////////////////////
// PROPERTIES
//

// Grid padding is applied to the list item rows but not the header and footer
public int getRowPaddingLeft() {
return getListPaddingLeft() + mGridPaddingLeft;
}

public int getRowPaddingRight() {
return getListPaddingRight() + mGridPaddingRight;
}

public int getRowPaddingTop() {
return getListPaddingTop() + mGridPaddingTop;
}

public int getRowPaddingBottom() {
return getListPaddingBottom() + mGridPaddingBottom;
}

public void setGridPadding(int left, int top, int right, int bottom) {
mGridPaddingLeft = left;
mGridPaddingTop = top;
mGridPaddingRight = right;
mGridPaddingBottom = bottom;
}

public void setColumnCountPortrait(int columnCountPortrait) {
mColumnCountPortrait = columnCountPortrait;
onSizeChanged(getWidth(), getHeight());
requestLayoutChildren();
}

public void setColumnCountLandscape(int columnCountLandscape) {
mColumnCountLandscape = columnCountLandscape;
onSizeChanged(getWidth(), getHeight());
requestLayoutChildren();
}

public void setColumnCount(int columnCount) {
mColumnCountPortrait = columnCount;
mColumnCountLandscape = columnCount;
// mColumnCount set onSizeChanged();
onSizeChanged(getWidth(), getHeight());
requestLayoutChildren();
}

// //////////////////////////////////////////////////////////////////////////////////////////
// MEASUREMENT
//
private boolean isLandscape() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}

@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

if (mColumnCount <= 0) {
boolean isLandscape = isLandscape();
mColumnCount = isLandscape ? mColumnCountLandscape : mColumnCountPortrait;
}

// our column width is the width of the listview
// minus it's padding
// minus the total items margin
// divided by the number of columns
mColumnWidth = calculateColumnWidth(getMeasuredWidth());

if (mColumnTops == null || mColumnTops.length != mColumnCount) {
mColumnTops = new int[mColumnCount];
initColumnTops();
}
if (mColumnBottoms == null || mColumnBottoms.length != mColumnCount) {
mColumnBottoms = new int[mColumnCount];
initColumnBottoms();
}
if (mColumnLefts == null || mColumnLefts.length != mColumnCount) {
mColumnLefts = new int[mColumnCount];
initColumnLefts();
}
}

@Override
protected void onMeasureChild(final View child, final LayoutParams layoutParams) {
final int viewType = layoutParams.viewType;
final int position = layoutParams.position;

if (viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||
viewType == ITEM_VIEW_TYPE_IGNORE) {
// for headers and weird ignored views
super.onMeasureChild(child, layoutParams);
}
else {
if (DBG) Log.d(TAG, "onMeasureChild BEFORE position:" + position +
" h:" + getMeasuredHeight());
// measure it to the width of our column.
int childWidthSpec = MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY);
int childHeightSpec;
if (layoutParams.height > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
}
else {
childHeightSpec = MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}

final int childHeight = getChildHeight(child);
setPositionHeightRatio(position, childHeight);

if (DBG) Log.d(TAG, "onMeasureChild AFTER position:" + position +
" h:" + childHeight);
}

public int getColumnWidth() {
return mColumnWidth;
}

public void resetToTop() {
if (mColumnCount > 0) {

if (mColumnTops == null) {
mColumnTops = new int[mColumnCount];
}
if (mColumnBottoms == null) {
mColumnBottoms = new int[mColumnCount];
}
initColumnTopsAndBottoms();

mPositionData.clear();
mNeedSync = false;
mDistanceToTop = 0;
setSelection(0);
}
}

// //////////////////////////////////////////////////////////////////////////////////////////
// POSITIONING
//

@Override
protected void onChildCreated(final int position, final boolean flowDown) {
super.onChildCreated(position, flowDown);
if (!isHeaderOrFooter(position)) {
// do we already have a column for this position?
final int column = getChildColumn(position, flowDown);
setPositionColumn(position, column);
if (DBG) Log.d(TAG, "onChildCreated position:" + position +
" is in column:" + column);
}
else {
setPositionIsHeaderFooter(position);
}
}

private void requestLayoutChildren() {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View v = getChildAt(i);
if (v != null) v.requestLayout();
}
}

@Override
protected void layoutChildren() {
preLayoutChildren();
super.layoutChildren();
}

private void preLayoutChildren() {
// on a major re-layout reset for our next layout pass
if (!mNeedSync) {
Arrays.fill(mColumnBottoms, 0);
}
else {
mNeedSync = false;
}
// copy the tops into the bottom
// since we're going to redo a layout pass that will draw down from
// the top
System.arraycopy(mColumnTops, 0, mColumnBottoms, 0, mColumnCount);
}

// NOTE : Views will either be layout out via onLayoutChild
// OR
// Views will be offset if they are active but offscreen so that we can recycle!
// Both onLayoutChild() and onOffsetChild are called after we measure our view
// see ExtensibleListView.setupChild();

@Override
protected void onLayoutChild(final View child,
final int position,
final boolean flowDown,
final int childrenLeft, final int childTop,
final int childRight, final int childBottom) {
if (isHeaderOrFooter(position)) {
layoutGridHeaderFooter(child, position, flowDown, childrenLeft, childTop, childRight, childBottom);
}
else {
layoutGridChild(child, position, flowDown, childrenLeft, childRight);
}
}

private void layoutGridHeaderFooter(final View child, final int position, final boolean flowDown, final int childrenLeft, final int childTop, final int childRight, final int childBottom) {
// offset the top and bottom of all our columns
// if it's the footer we want it below the lowest child bottom
int gridChildTop;
int gridChildBottom;

if (flowDown) {
gridChildTop = getLowestPositionedBottom();
gridChildBottom = gridChildTop + getChildHeight(child);
}
else {
gridChildBottom = getHighestPositionedTop();
gridChildTop = gridChildBottom - getChildHeight(child);
}

for (int i = 0; i < mColumnCount; i++) {
updateColumnTopIfNeeded(i, gridChildTop);
updateColumnBottomIfNeeded(i, gridChildBottom);
}

super.onLayoutChild(child, position, flowDown,
childrenLeft, gridChildTop, childRight, gridChildBottom);
}

private void layoutGridChild(final View child, final int position,
final boolean flowDown,
final int childrenLeft, final int childRight) {
// stash the bottom and the top if it's higher positioned
int column = getPositionColumn(position);

int gridChildTop;
int gridChildBottom;

int childTopMargin = getChildTopMargin(position);
int childBottomMargin = getChildBottomMargin();
int verticalMargins = childTopMargin + childBottomMargin;

if (flowDown) {
gridChildTop = mColumnBottoms[column]; // the next items top is the last items bottom
gridChildBottom = gridChildTop + (getChildHeight(child) + verticalMargins);
}
else {
gridChildBottom = mColumnTops[column]; // the bottom of the next column up is our top
gridChildTop = gridChildBottom - (getChildHeight(child) + verticalMargins);
}

if (DBG) Log.d(TAG, "onLayoutChild position:" + position +
" column:" + column +
" gridChildTop:" + gridChildTop +
" gridChildBottom:" + gridChildBottom);

// we also know the column of this view so let's stash it in the
// view's layout params
GridLayoutParams layoutParams = (GridLayoutParams) child.getLayoutParams();
layoutParams.column = column;

updateColumnBottomIfNeeded(column, gridChildBottom);
updateColumnTopIfNeeded(column, gridChildTop);

// subtract the margins before layout
gridChildTop += childTopMargin;
gridChildBottom -= childBottomMargin;

child.layout(childrenLeft, gridChildTop, childRight, gridChildBottom);
}

@Override
protected void onOffsetChild(final View child, final int position,
final boolean flowDown, final int childrenLeft, final int childTop) {
// if the child is recycled and is just offset
// we still want to add its deets into our store
if (isHeaderOrFooter(position)) {

offsetGridHeaderFooter(child, position, flowDown, childrenLeft, childTop);
}
else {
offsetGridChild(child, position, flowDown, childrenLeft, childTop);
}
}

private void offsetGridHeaderFooter(final View child, final int position, final boolean flowDown, final int childrenLeft, final int childTop) {
// offset the top and bottom of all our columns
// if it's the footer we want it below the lowest child bottom
int gridChildTop;
int gridChildBottom;

if (flowDown) {
gridChildTop = getLowestPositionedBottom();
gridChildBottom = gridChildTop + getChildHeight(child);
}
else {
gridChildBottom = getHighestPositionedTop();
gridChildTop = gridChildBottom - getChildHeight(child);
}

for (int i = 0; i < mColumnCount; i++) {
updateColumnTopIfNeeded(i, gridChildTop);
updateColumnBottomIfNeeded(i, gridChildBottom);
}

super.onOffsetChild(child, position, flowDown, childrenLeft, gridChildTop);
}

private void offsetGridChild(final View child, final int position, final boolean flowDown, final int childrenLeft, final int childTop) {
// stash the bottom and the top if it's higher positioned
int column = getPositionColumn(position);

int gridChildTop;
int gridChildBottom;

int childTopMargin = getChildTopMargin(position);
int childBottomMargin = getChildBottomMargin();
int verticalMargins = childTopMargin + childBottomMargin;

if (flowDown) {
gridChildTop = mColumnBottoms[column]; // the next items top is the last items bottom
gridChildBottom = gridChildTop + (getChildHeight(child) + verticalMargins);
}
else {
gridChildBottom = mColumnTops[column]; // the bottom of the next column up is our top
gridChildTop = gridChildBottom - (getChildHeight(child) + verticalMargins);
}

if (DBG) Log.d(TAG, "onOffsetChild position:" + position +
" column:" + column +
" childTop:" + childTop +
" gridChildTop:" + gridChildTop +
" gridChildBottom:" + gridChildBottom);

// we also know the column of this view so let's stash it in the
// view's layout params
GridLayoutParams layoutParams = (GridLayoutParams) child.getLayoutParams();
layoutParams.column = column;

updateColumnBottomIfNeeded(column, gridChildBottom);
updateColumnTopIfNeeded(column, gridChildTop);

super.onOffsetChild(child, position, flowDown, childrenLeft, gridChildTop + childTopMargin);
}

private int getChildHeight(final View child) {
return child.getMeasuredHeight();
}

private int getChildTopMargin(final int position) {
boolean isFirstRow = position < (getHeaderViewsCount() + mColumnCount);
return isFirstRow ? mItemMargin : 0;
}

private int getChildBottomMargin() {
return mItemMargin;
}

@Override
protected LayoutParams generateChildLayoutParams(final View child) {
GridLayoutParams layoutParams = null;

final ViewGroup.LayoutParams childParams = child.getLayoutParams();
if (childParams != null) {
if (childParams instanceof GridLayoutParams) {
layoutParams = (GridLayoutParams) childParams;
}
else {
layoutParams = new GridLayoutParams(childParams);
}
}
if (layoutParams == null) {
layoutParams = new GridLayoutParams(
mColumnWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
}

return layoutParams;
}

private void updateColumnTopIfNeeded(int column, int childTop) {
if (childTop < mColumnTops[column]) {
mColumnTops[column] = childTop;
}
}

private void updateColumnBottomIfNeeded(int column, int childBottom) {
if (childBottom > mColumnBottoms[column]) {
mColumnBottoms[column] = childBottom;
}
}

@Override
protected int getChildLeft(final int position) {
if (isHeaderOrFooter(position)) {
return super.getChildLeft(position);
}
else {
final int column = getPositionColumn(position);
return mColumnLefts[column];
}
}

@Override
protected int getChildTop(final int position) {
if (isHeaderOrFooter(position)) {
return super.getChildTop(position);
}
else {
final int column = getPositionColumn(position);
if (column == -1) {
return getHighestPositionedBottom();
}
return mColumnBottoms[column];
}
}


@Override
protected int getNextChildDownsTop(final int position) {
if (isHeaderOrFooter(position)) {
return super.getNextChildDownsTop(position);
}
else {
return getHighestPositionedBottom();
}
}

@Override
protected int getChildBottom(final int position) {
if (isHeaderOrFooter(position)) {
return super.getChildBottom(position);
}
else {
final int column = getPositionColumn(position);
if (column == -1) {
return getLowestPositionedTop();
}
return mColumnTops[column];
}
}


@Override
protected int getNextChildUpsBottom(final int position) {
if (isHeaderOrFooter(position)) {
return super.getNextChildUpsBottom(position);
}
else {
return getLowestPositionedTop();
}
}

@Override
protected int getLastChildBottom() {
final int lastPosition = mFirstPosition + (getChildCount() - 1);
if (isHeaderOrFooter(lastPosition)) {
return super.getLastChildBottom();
}
return getHighestPositionedBottom();
}

@Override
protected int getFirstChildTop() {
if (isHeaderOrFooter(mFirstPosition)) {
return super.getFirstChildTop();
}
return getLowestPositionedTop();
}

@Override
protected int getHighestChildTop() {
if (isHeaderOrFooter(mFirstPosition)) {
return super.getHighestChildTop();
}
return getHighestPositionedTop();
}

@Override
protected int getLowestChildBottom() {
final int lastPosition = mFirstPosition + (getChildCount() - 1);
if (isHeaderOrFooter(lastPosition)) {
return super.getLowestChildBottom();
}
return getLowestPositionedBottom();
}

@Override
protected void offsetChildrenTopAndBottom(final int offset) {
super.offsetChildrenTopAndBottom(offset);
offsetAllColumnsTopAndBottom(offset);
offsetDistanceToTop(offset);
}

protected void offsetChildrenTopAndBottom(final int offset, final int column) {
if (DBG) Log.d(TAG, "offsetChildrenTopAndBottom: " + offset + " column:" + column);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View v = getChildAt(i);
if (v != null &&
v.getLayoutParams() != null &&
v.getLayoutParams() instanceof GridLayoutParams) {
GridLayoutParams lp = (GridLayoutParams) v.getLayoutParams();
if (lp.column == column) {
v.offsetTopAndBottom(offset);
}
}
}
offsetColumnTopAndBottom(offset, column);
}

private void offsetDistanceToTop(final int offset) {
mDistanceToTop += offset;
if (DBG) Log.d(TAG, "offset mDistanceToTop:" + mDistanceToTop);
}

public int getDistanceToTop() {
return mDistanceToTop;
}

private void offsetAllColumnsTopAndBottom(final int offset) {
if (offset != 0) {
for (int i = 0; i < mColumnCount; i++) {
offsetColumnTopAndBottom(offset, i);
}
}
}

private void offsetColumnTopAndBottom(final int offset, final int column) {
if (offset != 0) {
mColumnTops[column] += offset;
mColumnBottoms[column] += offset;
}
}

@Override
protected void adjustViewsAfterFillGap(final boolean down) {
super.adjustViewsAfterFillGap(down);
// fix vertical gaps when hitting the top after a rotate
// only when scrolling back up!
if (!down) {
alignTops();
}
}

private void alignTops() {
if (mFirstPosition == getHeaderViewsCount()) {
// we're showing all the views before the header views
int[] nonHeaderTops = getHighestNonHeaderTops();
// we should now have our non header tops
// align them
boolean isAligned = true;
int highestColumn = -1;
int highestTop = Integer.MAX_VALUE;
for (int i = 0; i < nonHeaderTops.length; i++) {
// are they all aligned
if (isAligned && i > 0 && nonHeaderTops[i] != highestTop) {
isAligned = false; // not all the tops are aligned
}
// what's the highest
if (nonHeaderTops[i] < highestTop) {
highestTop = nonHeaderTops[i];
highestColumn = i;
}
}

// skip the rest.
if (isAligned) return;

// we've got the highest column - lets align the others
for (int i = 0; i < nonHeaderTops.length; i++) {
if (i != highestColumn) {
// there's a gap in this column
int offset = highestTop - nonHeaderTops[i];
offsetChildrenTopAndBottom(offset, i);
}
}
invalidate();
}
}

private int[] getHighestNonHeaderTops() {
int[] nonHeaderTops = new int[mColumnCount];
int childCount = getChildCount();
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child != null &&
child.getLayoutParams() != null &&
child.getLayoutParams() instanceof GridLayoutParams) {
// is this child's top the highest non
GridLayoutParams lp = (GridLayoutParams) child.getLayoutParams();
// is it a child that isn't a header
if (lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
child.getTop() < nonHeaderTops[lp.column]) {
nonHeaderTops[lp.column] = child.getTop();
}
}
}
}
return nonHeaderTops;
}

@Override
protected void onChildrenDetached(final int start, final int count) {
super.onChildrenDetached(start, count);
// go through our remaining views and sync the top and bottom stash.

// Repair the top and bottom column boundaries from the views we still have
Arrays.fill(mColumnTops, Integer.MAX_VALUE);
Arrays.fill(mColumnBottoms, 0);

for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child != null) {
final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
if (childParams.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
childParams instanceof GridLayoutParams) {
GridLayoutParams layoutParams = (GridLayoutParams) childParams;
int column = layoutParams.column;
int position = layoutParams.position;
final int childTop = child.getTop();
if (childTop < mColumnTops[column]) {
mColumnTops[column] = childTop - getChildTopMargin(position);
}
final int childBottom = child.getBottom();
if (childBottom > mColumnBottoms[column]) {
mColumnBottoms[column] = childBottom + getChildBottomMargin();
}
}
else {
// the header and footer here
final int childTop = child.getTop();
final int childBottom = child.getBottom();

for (int col = 0; col < mColumnCount; col++) {
if (childTop < mColumnTops[col]) {
mColumnTops[col] = childTop;
}
if (childBottom > mColumnBottoms[col]) {
mColumnBottoms[col] = childBottom;
}
}

}
}
}
}

@Override
protected boolean hasSpaceUp() {
int end = mClipToPadding ? getRowPaddingTop() : 0;
return getLowestPositionedTop() > end;
}

// //////////////////////////////////////////////////////////////////////////////////////////
// SYNCING ACROSS ROTATION
//

@Override
protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
onSizeChanged(w, h);
}

@Override
protected void onSizeChanged(int w, int h) {
super.onSizeChanged(w, h);
boolean isLandscape = isLandscape();
int newColumnCount = isLandscape ? mColumnCountLandscape : mColumnCountPortrait;
if (mColumnCount != newColumnCount) {
mColumnCount = newColumnCount;

mColumnWidth = calculateColumnWidth(w);

mColumnTops = new int[mColumnCount];
mColumnBottoms = new int[mColumnCount];
mColumnLefts = new int[mColumnCount];

mDistanceToTop = 0;

// rebuild the columns
initColumnTopsAndBottoms();
initColumnLefts();

// if we have data
if (getCount() > 0 && mPositionData.size() > 0) {
onColumnSync();
}

requestLayout();
}
}

private int calculateColumnWidth(final int gridWidth) {
final int listPadding = getRowPaddingLeft() + getRowPaddingRight();
return (gridWidth - listPadding - mItemMargin * (mColumnCount + 1)) / mColumnCount;
}

private int calculateColumnLeft(final int colIndex) {
return getRowPaddingLeft() + mItemMargin + ((mItemMargin + mColumnWidth) * colIndex);
}

/***
* Our mColumnTops and mColumnBottoms need to be re-built up to the
* mSyncPosition - the following layout request will then
* layout the that position and then fillUp and fillDown appropriately.
*/
private void onColumnSync() {
// re-calc tops for new column count!
int syncPosition = Math.min(mSyncPosition, getCount() - 1);

SparseArray<Double> positionHeightRatios = new SparseArray<Double>(syncPosition);
for (int pos = 0; pos < syncPosition; pos++) {
// check for weirdness
final GridItemRecord rec = mPositionData.get(pos);
if (rec == null) break;

Log.d(TAG, "onColumnSync:" + pos + " ratio:" + rec.heightRatio);
positionHeightRatios.append(pos, rec.heightRatio);
}

mPositionData.clear();

// re-calc our relative position while at the same time
// rebuilding our GridItemRecord collection

if (DBG) Log.d(TAG, "onColumnSync column width:" + mColumnWidth);

for (int pos = 0; pos < syncPosition; pos++) {
//Check for weirdness again
final Double heightRatio = positionHeightRatios.get(pos);
if(heightRatio == null){
break;
}

final GridItemRecord rec = getOrCreateRecord(pos);
final int height = (int) (mColumnWidth * heightRatio);
rec.heightRatio = heightRatio;

int top;
int bottom;
// check for headers
if (isHeaderOrFooter(pos)) {
// the next top is the bottom for that column
top = getLowestPositionedBottom();
bottom = top + height;

for (int i = 0; i < mColumnCount; i++) {
mColumnTops[i] = top;
mColumnBottoms[i] = bottom;
}
}
else {
// what's the next column down ?
final int column = getHighestPositionedBottomColumn();
// the next top is the bottom for that column
top = mColumnBottoms[column];
bottom = top + height + getChildTopMargin(pos) + getChildBottomMargin();

mColumnTops[column] = top;
mColumnBottoms[column] = bottom;

rec.column = column;
}


if (DBG) Log.d(TAG, "onColumnSync position:" + pos +
" top:" + top +
" bottom:" + bottom +
" height:" + height +
" heightRatio:" + heightRatio);
}

// our sync position will be displayed in this column
final int syncColumn = getHighestPositionedBottomColumn();
setPositionColumn(syncPosition, syncColumn);

// we want to offset from height of the sync position
// minus the offset
int syncToBottom = mColumnBottoms[syncColumn];
int offset = -syncToBottom + mSpecificTop;
// offset all columns by
offsetAllColumnsTopAndBottom(offset);

// sync the distance to top
mDistanceToTop = -syncToBottom;

// stash our bottoms in our tops - though these will be copied back to the bottoms
System.arraycopy(mColumnBottoms, 0, mColumnTops, 0, mColumnCount);
}


// //////////////////////////////////////////////////////////////////////////////////////////
// GridItemRecord UTILS
//

private void setPositionColumn(final int position, final int column) {
GridItemRecord rec = getOrCreateRecord(position);
rec.column = column;
}

private void setPositionHeightRatio(final int position, final int height) {
GridItemRecord rec = getOrCreateRecord(position);
rec.heightRatio = (double)  height / (double) mColumnWidth;
if (DBG) Log.d(TAG, "position:" + position +
" width:" + mColumnWidth +
" height:" + height +
" heightRatio:" + rec.heightRatio);
}

private void setPositionIsHeaderFooter(final int position) {
GridItemRecord rec = getOrCreateRecord(position);
rec.isHeaderFooter = true;
}

private GridItemRecord getOrCreateRecord(final int position) {
GridItemRecord rec = mPositionData.get(position, null);
if (rec == null) {
rec = new GridItemRecord();
mPositionData.append(position, rec);
}
return rec;
}

private int getPositionColumn(final int position) {
GridItemRecord rec = mPositionData.get(position, null);
return rec != null ? rec.column : -1;
}


// //////////////////////////////////////////////////////////////////////////////////////////
// HELPERS
//

private boolean isHeaderOrFooter(final int position) {
final int viewType = mAdapter.getItemViewType(position);
return viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}

private int getChildColumn(final int position, final boolean flowDown) {

// do we already have a column for this child position?
int column = getPositionColumn(position);
// we don't have the column or it no longer fits in our grid
final int columnCount = mColumnCount;
if (column < 0 || column >= columnCount) {
// if we're going down -
// get the highest positioned (lowest value)
// column bottom
if (flowDown) {
column = getHighestPositionedBottomColumn();
}
else {
column = getLowestPositionedTopColumn();

}
}
return column;
}

private void initColumnTopsAndBottoms() {
initColumnTops();
initColumnBottoms();
}

private void initColumnTops() {
Arrays.fill(mColumnTops, getPaddingTop() + mGridPaddingTop);
}

private void initColumnBottoms() {
Arrays.fill(mColumnBottoms, getPaddingTop() + mGridPaddingTop);
}

private void initColumnLefts() {
for (int i = 0; i < mColumnCount; i++) {
mColumnLefts[i] = calculateColumnLeft(i);
}
}


// //////////////////////////////////////////////////////////////////////////////////////////
// BOTTOM
//

private int getHighestPositionedBottom() {
final int column = getHighestPositionedBottomColumn();
return mColumnBottoms[column];
}

private int getHighestPositionedBottomColumn() {
int columnFound = 0;
int highestPositionedBottom = Integer.MAX_VALUE;
// the highest positioned bottom is the one with the lowest value :D
for (int i = 0; i < mColumnCount; i++) {
int bottom = mColumnBottoms[i];
if (bottom < highestPositionedBottom) {
highestPositionedBottom = bottom;
columnFound = i;
}
}
return columnFound;
}

private int getLowestPositionedBottom() {
final int column = getLowestPositionedBottomColumn();
return mColumnBottoms[column];
}

private int getLowestPositionedBottomColumn() {
int columnFound = 0;
int lowestPositionedBottom = Integer.MIN_VALUE;
// the lowest positioned bottom is the one with the highest value :D
for (int i = 0; i < mColumnCount; i++) {
int bottom = mColumnBottoms[i];
if (bottom > lowestPositionedBottom) {
lowestPositionedBottom = bottom;
columnFound = i;
}
}
return columnFound;
}

// //////////////////////////////////////////////////////////////////////////////////////////
// TOP
//

private int getLowestPositionedTop() {
final int column = getLowestPositionedTopColumn();
return mColumnTops[column];
}

private int getLowestPositionedTopColumn() {
int columnFound = 0;
// we'll go backwards through since the right most
// will likely be the lowest positioned Top
int lowestPositionedTop = Integer.MIN_VALUE;
// the lowest positioned top is the one with the highest value :D
for (int i = 0; i < mColumnCount; i++) {
int top = mColumnTops[i];
if (top > lowestPositionedTop) {
lowestPositionedTop = top;
columnFound = i;
}
}
return columnFound;
}

private int getHighestPositionedTop() {
final int column = getHighestPositionedTopColumn();
return mColumnTops[column];
}

private int getHighestPositionedTopColumn() {
int columnFound = 0;
int highestPositionedTop = Integer.MAX_VALUE;
// the highest positioned top is the one with the lowest value :D
for (int i = 0; i < mColumnCount; i++) {
int top = mColumnTops[i];
if (top < highestPositionedTop) {
highestPositionedTop = top;
columnFound = i;
}
}
return columnFound;
}

// //////////////////////////////////////////////////////////////////////////////////////////
// LAYOUT PARAMS
//

/**
* Extended LayoutParams to column position and anything else we may been for the grid
*/
public static class GridLayoutParams extends LayoutParams {

// The column the view is displayed in
int column;

public GridLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
enforceStaggeredLayout();
}

public GridLayoutParams(int w, int h) {
super(w, h);
enforceStaggeredLayout();
}

public GridLayoutParams(int w, int h, int viewType) {
super(w, h);
enforceStaggeredLayout();
}

public GridLayoutParams(ViewGroup.LayoutParams source) {
super(source);
enforceStaggeredLayout();
}

/**
* Here we're making sure that all grid view items
* are width MATCH_PARENT and height WRAP_CONTENT.
* That's what this grid is designed for
*/
private void enforceStaggeredLayout() {
if (width != MATCH_PARENT) {
width = MATCH_PARENT;
}
if (height == MATCH_PARENT) {
height = WRAP_CONTENT;
}
}
}

// //////////////////////////////////////////////////////////////////////////////////////////
// SAVED STATE


public static class GridListSavedState extends ListSavedState {
int columnCount;
int[] columnTops;
SparseArray positionData;

public GridListSavedState(Parcelable superState) {
super(superState);
}

/**
* Constructor called from {@link #CREATOR}
*/
public GridListSavedState(Parcel in) {
super(in);
columnCount = in.readInt();
columnTops = new int[columnCount >= 0 ? columnCount : 0];
in.readIntArray(columnTops);
positionData = in.readSparseArray(GridItemRecord.class.getClassLoader());
}

@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(columnCount);
out.writeIntArray(columnTops);
out.writeSparseArray(positionData);
}

@Override
public String toString() {
return "StaggeredGridView.GridListSavedState{"
+ Integer.toHexString(System.identityHashCode(this)) + "}";
}

public static final Creator<GridListSavedState> CREATOR
= new Creator<GridListSavedState>() {
public GridListSavedState createFromParcel(Parcel in) {
return new GridListSavedState(in);
}

public GridListSavedState[] newArray(int size) {
return new GridListSavedState[size];
}
};
}


@Override
public Parcelable onSaveInstanceState() {
ListSavedState listState = (ListSavedState) super.onSaveInstanceState();
GridListSavedState ss = new GridListSavedState(listState.getSuperState());

// from the list state
ss.selectedId = listState.selectedId;
ss.firstId = listState.firstId;
ss.viewTop = listState.viewTop;
ss.position = listState.position;
ss.height = listState.height;

// our state

boolean haveChildren = getChildCount() > 0 && getCount() > 0;

if (haveChildren && mFirstPosition > 0) {
ss.columnCount = mColumnCount;
ss.columnTops = mColumnTops;
ss.positionData = mPositionData;
}
else {
ss.columnCount = mColumnCount >= 0 ? mColumnCount : 0;
ss.columnTops = new int[ss.columnCount];
ss.positionData = new SparseArray<Object>();
}

return ss;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
GridListSavedState ss = (GridListSavedState) state;
mColumnCount = ss.columnCount;
mColumnTops = ss.columnTops;
mColumnBottoms = new int[mColumnCount];
mPositionData = ss.positionData;
mNeedSync = true;
super.onRestoreInstanceState(ss);
}
}

DynamicHeightImageView.java


package com.android.grid.util;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;


public class DynamicHeightImageView extends ImageView {

private double mHeightRatio;

public DynamicHeightImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public DynamicHeightImageView(Context context) {
super(context);
}

public void setHeightRatio(double ratio) {
if (ratio != mHeightRatio) {
mHeightRatio = ratio;
requestLayout();
}
}

public double getHeightRatio() {
return mHeightRatio;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mHeightRatio > 0.0) {
// set the image views size
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) (width * mHeightRatio);
setMeasuredDimension(width, height);
}
else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

DynamicHeightTextView.java


package com.android.grid.util;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;


public class DynamicHeightTextView extends TextView {

private double mHeightRatio;

public DynamicHeightTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public DynamicHeightTextView(Context context) {
super(context);
}

public void setHeightRatio(double ratio) {
if (ratio != mHeightRatio) {
mHeightRatio = ratio;
requestLayout();
}
}

public double getHeightRatio() {
return mHeightRatio;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mHeightRatio > 0.0) {
// set the image views size
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) (width * mHeightRatio);
setMeasuredDimension(width, height);
}
else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

 

—–> Create Android Staggered Grid Project And Link Above Project AS Lib Project.

 

—>  AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.sample"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="22" />

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity">

<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

<activity android:name=".StaggeredGridActivity"/>
<activity android:name=".StaggeredGridActivityFragment"/>
<activity android:name=".StaggeredGridEmptyViewActivity" />
<activity android:name=".ListViewActivity"/>

</application>

</manifest>

 

In Values Folder

colors.xml

 


<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="orange">#ffffdc7e</color>
<color name="grey">#ffd3d3d3</color>
<color name="green">#ff8dd304</color>
<color name="blue">#ff82e0ff</color>
<color name="yellow">#fffffbae</color>
<color name="red">#fff10800</color>
<color name="list_item_pressed">#1A000000</color>
</resources>

 

 

integers.xml

 


<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="grid_column_count">2</integer>
</resources>

 

IN Layout Folder

 

activity_list_view.xml

 


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="@drawable/list_item_selector"
android:drawSelectorOnTop="true" />

</FrameLayout>

activity_main.xml

 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:id="@+id/btn_sgv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:layout_margin="16dp"
android:text="Staggered Grid View" />

<Button
android:id="@+id/btn_sgv_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:layout_margin="16dp"
android:text="Staggered Grid View in Fragment" />

<Button
android:id="@+id/btn_sgv_empty_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:layout_margin="16dp"
android:text="Staggered Grid View with Empty View" />

<Button
android:id="@+id/btn_listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:layout_margin="16dp"
android:text="List View" />

</LinearLayout>

activity_sgv_empy_view.xml

 


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.android.grid.StaggeredGridView
android:id="@+id/grid_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:item_margin="8dp"
app:column_count="@integer/grid_column_count" />

<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textAppearance="?android:textAppearanceMedium"
android:text="Loading data..."/>

</FrameLayout>

activity_sgv.xml


<com.android.grid.StaggeredGridView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/grid_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:item_margin="8dp"
app:column_count="@integer/grid_column_count" />

 

list_item_header_footer.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/red">

<TextView
android:id="@+id/txt_title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_weight="1"
android:gravity="center" />

</LinearLayout>

 

list_item_sample.xml

 


<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:id="@+id/panel_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">

<com.android.grid.util.DynamicHeightTextView
android:id="@+id/txt_line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"/>

<Button
android:id="@+id/btn_go"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"
android:text="Go" />

</FrameLayout>

IN drawable Folder

list_item_selector.xml


<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

<!-- shapes defined for Android 2.3 drawable issues -->
<item android:state_pressed="true" >
<shape android:shape="rectangle">
<solid android:color="@color/list_item_pressed" />
</shape>
</item>

<!-- default -->
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
</shape>
</item>

</selector>

ListViewActivity.java


 

package com.android.sample;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.*;

import java.util.List;

public class ListViewActivity extends Activity implements AdapterView.OnItemClickListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);

setTitle("ListView");

final ListView listView = (ListView) findViewById(R.id.list_view);

LayoutInflater layoutInflater = getLayoutInflater();

View header = layoutInflater.inflate(R.layout.list_item_header_footer, null);
View footer = layoutInflater.inflate(R.layout.list_item_header_footer, null);
TextView txtHeaderTitle = (TextView) header.findViewById(R.id.txt_title);
TextView txtFooterTitle =  (TextView) footer.findViewById(R.id.txt_title);
txtHeaderTitle.setText("THE HEADER!");
txtFooterTitle.setText("THE FOOTER!");

listView.addHeaderView(header);
listView.addFooterView(footer);

final SampleAdapter adapter = new SampleAdapter(this, R.id.txt_line1);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);

final List<String> sampleData = SampleData.generateSampleData();
for (String data : sampleData) {
adapter.add(data);
}
}

@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Toast.makeText(this, "Item Clicked: " + position, Toast.LENGTH_SHORT).show();
}
}

MainActivity.java


package com.android.sample;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity implements View.OnClickListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("SGV Sample");
setContentView(R.layout.activity_main);

findViewById(R.id.btn_sgv).setOnClickListener(this);
findViewById(R.id.btn_sgv_fragment).setOnClickListener(this);
findViewById(R.id.btn_sgv_empty_view).setOnClickListener(this);
findViewById(R.id.btn_listview).setOnClickListener(this);
}


@Override
public void onClick(final View v) {
if (v.getId() == R.id.btn_sgv) {
startActivity(new Intent(this, StaggeredGridActivity.class));
}
else if (v.getId() == R.id.btn_sgv_fragment) {
startActivity(new Intent(this, StaggeredGridActivityFragment.class));
}
else if (v.getId() == R.id.btn_sgv_empty_view) {
startActivity(new Intent(this, StaggeredGridEmptyViewActivity.class));
}
else if (v.getId() == R.id.btn_listview) {
startActivity(new Intent(this, ListViewActivity.class));
}
}
}

SampleAdapter.java


package com.android.sample;


import java.util.ArrayList;
import java.util.Random;

import com.android.grid.util.DynamicHeightTextView;

import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Toast;





public class SampleAdapter extends ArrayAdapter<String> {

private static final String TAG = "SampleAdapter";

static class ViewHolder {
DynamicHeightTextView txtLineOne;
Button btnGo;
}

private final LayoutInflater mLayoutInflater;
private final Random mRandom;
private final ArrayList<Integer> mBackgroundColors;

private static final SparseArray<Double> sPositionHeightRatios = new SparseArray<Double>();

public SampleAdapter(final Context context, final int textViewResourceId) {
super(context, textViewResourceId);
mLayoutInflater = LayoutInflater.from(context);
mRandom = new Random();
mBackgroundColors = new ArrayList<Integer>();
mBackgroundColors.add(R.color.orange);
mBackgroundColors.add(R.color.green);
mBackgroundColors.add(R.color.blue);
mBackgroundColors.add(R.color.yellow);
mBackgroundColors.add(R.color.grey);
}

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

ViewHolder vh;
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.list_item_sample, parent, false);
vh = new ViewHolder();
vh.txtLineOne = (DynamicHeightTextView) convertView.findViewById(R.id.txt_line1);
vh.btnGo = (Button) convertView.findViewById(R.id.btn_go);

convertView.setTag(vh);
}
else {
vh = (ViewHolder) convertView.getTag();
}

double positionHeight = getPositionRatio(position);
int backgroundIndex = position >= mBackgroundColors.size() ?
position % mBackgroundColors.size() : position;

convertView.setBackgroundResource(mBackgroundColors.get(backgroundIndex));

Log.d(TAG, "getView position:" + position + " h:" + positionHeight);

vh.txtLineOne.setHeightRatio(positionHeight);
vh.txtLineOne.setText(getItem(position) + position);

vh.btnGo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
Toast.makeText(getContext(), "Button Clicked Position " +
position, Toast.LENGTH_SHORT).show();
}
});

return convertView;
}

private double getPositionRatio(final int position) {
double ratio = sPositionHeightRatios.get(position, 0.0);

if (ratio == 0) {
ratio = getRandomHeightRatio();
sPositionHeightRatios.append(position, ratio);
Log.d(TAG, "getPositionRatio:" + position + " ratio:" + ratio);
}
return ratio;
}

private double getRandomHeightRatio() {
return (mRandom.nextDouble() / 2.0) + 1.0; // height will be 1.0 - 1.5 the width
}
}

SampleData.java


package com.android.sample;

import java.util.ArrayList;
import java.util.List;

public class SampleData {

public static final int SAMPLE_DATA_ITEM_COUNT = 30;

public static ArrayList<String> generateSampleData() {
final ArrayList<String> data = new ArrayList<String>(SAMPLE_DATA_ITEM_COUNT);

for (int i = 0; i < SAMPLE_DATA_ITEM_COUNT; i++) {
data.add("SAMPLE #");
}

return data;
}

}

 

StaggeredGridActivity.java


package com.android.sample;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.grid.StaggeredGridView;

public class StaggeredGridActivity extends Activity implements AbsListView.OnScrollListener, AbsListView.OnItemClickListener, AdapterView.OnItemLongClickListener {

private static final String TAG = "StaggeredGridActivity";
public static final String SAVED_DATA_KEY = "SAVED_DATA";

private StaggeredGridView mGridView;
private boolean mHasRequestedMore;
private SampleAdapter mAdapter;

private ArrayList<String> mData;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sgv);

setTitle("SGV");
mGridView = (StaggeredGridView) findViewById(R.id.grid_view);

LayoutInflater layoutInflater = getLayoutInflater();

View header = layoutInflater.inflate(R.layout.list_item_header_footer, null);
View footer = layoutInflater.inflate(R.layout.list_item_header_footer, null);
TextView txtHeaderTitle = (TextView) header.findViewById(R.id.txt_title);
TextView txtFooterTitle =  (TextView) footer.findViewById(R.id.txt_title);
txtHeaderTitle.setText("THE HEADER!");
txtFooterTitle.setText("THE FOOTER!");

mGridView.addHeaderView(header);
mGridView.addFooterView(footer);
mAdapter = new SampleAdapter(this, R.id.txt_line1);

// do we have saved data?
if (savedInstanceState != null) {
mData = savedInstanceState.getStringArrayList(SAVED_DATA_KEY);
}

if (mData == null) {
mData = SampleData.generateSampleData();
}

for (String data : mData) {
mAdapter.add(data);
}

mGridView.setAdapter(mAdapter);
mGridView.setOnScrollListener(this);
mGridView.setOnItemClickListener(this);
mGridView.setOnItemLongClickListener(this);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_sgv_dynamic, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.col1:
mGridView.setColumnCount(1);
break;
case R.id.col2:
mGridView.setColumnCount(2);
break;
case R.id.col3:
mGridView.setColumnCount(3);
break;
}
return true;
}

@Override
protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putStringArrayList(SAVED_DATA_KEY, mData);
}

@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
Log.d(TAG, "onScrollStateChanged:" + scrollState);
}

@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
Log.d(TAG, "onScroll firstVisibleItem:" + firstVisibleItem +
" visibleItemCount:" + visibleItemCount +
" totalItemCount:" + totalItemCount);
// our handling
if (!mHasRequestedMore) {
int lastInScreen = firstVisibleItem + visibleItemCount;
if (lastInScreen >= totalItemCount) {
Log.d(TAG, "onScroll lastInScreen - so load more");
mHasRequestedMore = true;
onLoadMoreItems();
}
}
}

private void onLoadMoreItems() {
final ArrayList<String> sampleData = SampleData.generateSampleData();
for (String data : sampleData) {
mAdapter.add(data);
}
// stash all the data in our backing store
mData.addAll(sampleData);
// notify the adapter that we can update now
mAdapter.notifyDataSetChanged();
mHasRequestedMore = false;
}

@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Toast.makeText(this, "Item Clicked: " + position, Toast.LENGTH_SHORT).show();
}

@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
Toast.makeText(this, "Item Long Clicked: " + position, Toast.LENGTH_SHORT).show();
return true;
}
}

 

StaggeredGridActivityFragment.java

 


package com.android.sample;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.*;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.grid.StaggeredGridView;

import java.util.ArrayList;

public class StaggeredGridActivityFragment extends FragmentActivity {

private static final String TAG = "StaggeredGridActivityFragment";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setTitle("SGV");

final FragmentManager fm = getSupportFragmentManager();

// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
final StaggeredGridFragment fragment = new StaggeredGridFragment();
fm.beginTransaction().add(android.R.id.content, fragment).commit();
}
}


private class StaggeredGridFragment extends Fragment implements
AbsListView.OnScrollListener, AbsListView.OnItemClickListener {


private StaggeredGridView mGridView;
private boolean mHasRequestedMore;
private SampleAdapter mAdapter;

private ArrayList<String> mData;

@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}

@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.activity_sgv, container, false);
}

@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

mGridView = (StaggeredGridView) getView().findViewById(R.id.grid_view);

if (savedInstanceState == null) {
final LayoutInflater layoutInflater = getActivity().getLayoutInflater();

View header = layoutInflater.inflate(R.layout.list_item_header_footer, null);
View footer = layoutInflater.inflate(R.layout.list_item_header_footer, null);
TextView txtHeaderTitle = (TextView) header.findViewById(R.id.txt_title);
TextView txtFooterTitle = (TextView) footer.findViewById(R.id.txt_title);
txtHeaderTitle.setText("THE HEADER!");
txtFooterTitle.setText("THE FOOTER!");

mGridView.addHeaderView(header);
mGridView.addFooterView(footer);
}

if (mAdapter == null) {
mAdapter = new SampleAdapter(getActivity(), R.id.txt_line1);
}

if (mData == null) {
mData = SampleData.generateSampleData();
}

for (String data : mData) {
mAdapter.add(data);
}

mGridView.setAdapter(mAdapter);
mGridView.setOnScrollListener(this);
mGridView.setOnItemClickListener(this);
}

@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
Log.d(TAG, "onScrollStateChanged:" + scrollState);
}

@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
Log.d(TAG, "onScroll firstVisibleItem:" + firstVisibleItem +
" visibleItemCount:" + visibleItemCount +
" totalItemCount:" + totalItemCount);
// our handling
if (!mHasRequestedMore) {
int lastInScreen = firstVisibleItem + visibleItemCount;
if (lastInScreen >= totalItemCount) {
Log.d(TAG, "onScroll lastInScreen - so load more");
mHasRequestedMore = true;
onLoadMoreItems();
}
}
}

private void onLoadMoreItems() {
final ArrayList<String> sampleData = SampleData.generateSampleData();
for (String data : sampleData) {
mAdapter.add(data);
}
// stash all the data in our backing store
mData.addAll(sampleData);
// notify the adapter that we can update now
mAdapter.notifyDataSetChanged();
mHasRequestedMore = false;
}

@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Toast.makeText(getActivity(), "Item Clicked: " + position, Toast.LENGTH_SHORT).show();
}
}
}

StaggeredGridEmptyViewActivity.java


package com.android.sample;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.grid.StaggeredGridView;

import java.util.ArrayList;

public class StaggeredGridEmptyViewActivity extends Activity implements AbsListView.OnItemClickListener {

public static final String SAVED_DATA_KEY = "SAVED_DATA";
private static final int FETCH_DATA_TASK_DURATION = 2000;

private StaggeredGridView mGridView;
private SampleAdapter mAdapter;

private ArrayList<String> mData;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sgv_empy_view);

setTitle("SGV");
mGridView = (StaggeredGridView) findViewById(R.id.grid_view);

LayoutInflater layoutInflater = getLayoutInflater();

View header = layoutInflater.inflate(R.layout.list_item_header_footer, null);
View footer = layoutInflater.inflate(R.layout.list_item_header_footer, null);
TextView txtHeaderTitle = (TextView) header.findViewById(R.id.txt_title);
TextView txtFooterTitle =  (TextView) footer.findViewById(R.id.txt_title);
txtHeaderTitle.setText("THE HEADER!");
txtFooterTitle.setText("THE FOOTER!");

mGridView.addHeaderView(header);
mGridView.addFooterView(footer);
mGridView.setEmptyView(findViewById(android.R.id.empty));
mAdapter = new SampleAdapter(this, R.id.txt_line1);

// do we have saved data?
if (savedInstanceState != null) {
mData = savedInstanceState.getStringArrayList(SAVED_DATA_KEY);
fillAdapter();
}

if (mData == null) {
mData = SampleData.generateSampleData();
}


mGridView.setAdapter(mAdapter);

mGridView.setOnItemClickListener(this);

fetchData();
}

private void fillAdapter() {
for (String data : mData) {
mAdapter.add(data);
}
}

private void fetchData() {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(FETCH_DATA_TASK_DURATION);
return null;
}

@Override
protected void onPostExecute(Void aVoid) {
fillAdapter();
}
}.execute();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_sgv_empty_view, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
mAdapter.clear();
fetchData();
return true;
}

@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Toast.makeText(this, "Item Clicked: " + position, Toast.LENGTH_SHORT).show();
}

@Override
protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putStringArrayList(SAVED_DATA_KEY, mData);
}
}

 

 

admin@androidtrainee

 Previous Article Draw Android Bar Chart With Animation.
Next Article   Android satellite menu Animation.

Related Posts

  • Android New Quick Action Animation.

    July 15, 2015
  • Android satellite menu Animation.

    July 15, 2015
  • Draw Android Bar Chart With Animation.

    July 14, 2015

Leave a Reply

Cancel reply

Tags

admob Advertising Networks AerServ Airpush android android ads android Advertising Networks Android App android chart animation Android GridView android L android lollipop androidmapv2 AppBrain AppFlood Appia AppKey Appnext AppOptim Appwiz chart chartview Epom Market google place api GridView Image Loader InMobi LeadBolt location map mapv2 mapv2 api material design Minimob Mobicow MobileCore MobiMicro NativeX Pingjam RevMob StarApplication startapp TapContext touched location Widdit

Count per Day

  • 347Reads yesterday:
  • 463795Total visitors:
  • 15Visitors today:
  • 2388Visitors per month:
  • 1Visitors currently online:
© Copyright 2014. Theme by BloomPixel.
Posting....