Android Staggered Grid & Record View.

Create Android Staggered Lib Challenge.

—>  AndroidManifest.xml

values folder

attrs.xml











ClassLoaderSavedState.java

package deal com.android.grid;

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

public summary class ClassLoaderSavedState implements Parcelable {
public static closing ClassLoaderSavedState EMPTY_STATE = new ClassLoaderSavedState() {};

personal Parcelable mSuperState = EMPTY_STATE;
personal ClassLoader mClassLoader;

personal 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 supply) {
// ETSY : we’re utilizing the handed tremendous class loader in contrast to AbsSavedState
Parcelable superState = supply.readParcelable(mClassLoader);
mSuperState = superState != null ? superState : EMPTY_STATE;
}

closing public Parcelable getSuperState() {
return mSuperState;
}

public int describeContents() {
return 0;
}

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

public static closing Parcelable.Creator CREATOR
= new Parcelable.Creator() {

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 dimension) {
return new ClassLoaderSavedState[size];
}
};
}

ExtendableListView.java

package deal com.android.grid;

import android.annotation.SuppressLint;
import android.content material.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.help.v4.util.SparseArrayCompat;
import android.help.v4.view.MotionEventCompat;
import android.help.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 summary class ExtendableListView extends AbsListView {

personal static closing String TAG = “ExtendableListView”;

personal static closing boolean DBG = false;

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

personal static closing int INVALID_POINTER = -1;

// Structure utilizing our default current state
personal static closing int LAYOUT_NORMAL = 0;
// Structure from the primary merchandise down
personal static closing int LAYOUT_FORCE_TOP = 1;
// Structure from the saved occasion state knowledge
personal static closing int LAYOUT_SYNC = 2;

personal int mLayoutMode;

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

// Rectangle used for hit testing youngsters
// personal Rect mTouchFrame;
// TODO : ItemClick help from AdapterView

// For managing scrolling
personal VelocityTracker mVelocityTracker = null;

personal int mTouchSlop;
personal int mMaximumVelocity;
personal int mFlingVelocity;

personal boolean mInLayout;

ListAdapter mAdapter;

personal int mMotionY;
personal int mMotionX;
personal int mMotionCorrection;
personal int mMotionPosition;

personal int mLastY;

personal int mActivePointerId = INVALID_POINTER;

protected int mFirstPosition;

// are we connected to a window – we shouldn’t deal with any contact occasions if we’re not!
personal boolean mIsAttached;

personal boolean mBlockLayoutRequests = false;

// has our knowledge modified – and will we react to it
personal boolean mDataChanged;
personal int mItemCount;
personal int mOldItemCount;

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

personal RecycleBin mRecycleBin;

personal AdapterDataSetObserver mObserver;
personal int mWidthMeasureSpec;
personal FlingRunnable mFlingRunnable;

protected boolean mClipToPadding;
personal PerformClick mPerformClick;

personal Runnable mPendingCheckForTap;
personal CheckForLongPress mPendingCheckForLongPress;

personal class CheckForLongPress extends WindowRunnnable implements Runnable {
public void run() {
closing int motionPosition = mMotionPosition;
closing View little one = getChildAt(motionPosition);
if (little one != null) {
closing int longPressPosition = mMotionPosition;
closing lengthy longPressId = mAdapter.getItemId(mMotionPosition + mFirstPosition);

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

}
}
}

public class FixedViewInfo {

public View view;

public Object knowledge;

public boolean isSelectable;
}

personal ArrayList mHeaderViewInfos;
personal ArrayList mFooterViewInfos;

public ExtendableListView(closing Context context, closing AttributeSet attrs, closing int defStyle) {
tremendous(context, attrs, defStyle);

// setting as much as be a scrollable view group
setWillNotDraw(false);
setClipToPadding(false);
setFocusableInTouchMode(false);

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

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

mHeaderViewInfos = new ArrayList();
mFooterViewInfos = new ArrayList();

// begin our structure mode drawing from the highest
mLayoutMode = LAYOUT_NORMAL;
}

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

if (mAdapter != null) {
// Knowledge could have modified whereas we had been indifferent. Refresh.
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
}
mIsAttached = true;
}

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

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

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

mIsAttached = false;
}

@Override
protected void onFocusChanged(boolean gainFocus, int route, Rect previouslyFocusedRect) {
// TODO : deal with focus and its influence on choice – if we add merchandise choice help
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
// TODO : deal with focus and its influence on choice – if we add merchandise choice help
}

@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(closing ListAdapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mObserver);
}

// use a wrapper record adapter if we now have a header or footer
if (mHeaderViewInfos.dimension() > 0 || mFooterViewInfos.dimension() > 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(closing int place) {
if (place >= 0) {
mLayoutMode = LAYOUT_SYNC;
mSpecificTop = getListPaddingTop();

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

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

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

FixedViewInfo information = new FixedViewInfo();
information.view = v;
information.knowledge = knowledge;
information.isSelectable = isSelectable;
mHeaderViewInfos.add(information);

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

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

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

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

personal void removeFixedViewInfo(View v, ArrayList the place) {
int len = the place.dimension();
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 outcome = false;
if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
if (mObserver != null) {
mObserver.onChanged();
}
outcome = true;
}
removeFixedViewInfo(v, mFooterViewInfos);
return outcome;
}
return false;
}

@Override
public void setClipToPadding(closing boolean clipToPadding) {
tremendous.setClipToPadding(clipToPadding);
mClipToPadding = clipToPadding;
}

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

@Override
protected void onLayout(closing boolean modified, closing int l, closing int t, closing int r, closing int b) {
// tremendous.onLayout(modified, l, t, r, b); – skipping base AbsListView implementation on objective
// haven’t set an adapter but? get to it
if (mAdapter == null) {
return;
}

if (modified) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycleBin.markChildrenDirty(); } // TODO get the peak of the view?? mInLayout = true; layoutChildren(); mInLayout = false; } @Override protected void layoutChildren() { if (mBlockLayoutRequests) return; mBlockLayoutRequests = true; attempt { tremendous.layoutChildren(); invalidate(); if (mAdapter == null) { clearState(); invokeOnItemScrollListener(); return; } int childrenTop = getListPaddingTop(); int childCount = getChildCount(); View oldFirst = null; // our final state so we preserve our place if (mLayoutMode == LAYOUT_NORMAL) { oldFirst = getChildAt(0); } boolean dataChanged = mDataChanged; if (dataChanged) { handleDataChanged(); } // security test! // Deal with the empty set by eradicating all views which are seen // 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 youngsters into the RecycleBin. // These views will likely be reused if attainable closing int firstPosition = mFirstPosition; closing RecycleBin recycleBin = mRecycleBin; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition + i); } } else { recycleBin.fillActiveViews(childCount, firstPosition); } // Filter out previous views detachAllViewsFromParent(); recycleBin.removeSkippedScrap(); change (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), rely – 1);
return;
}

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

// TODO : add choice dealing with right here
}

public void resetToTop() {
// TO override
}

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

@Override
public boolean onTouchEvent(MotionEvent occasion) {

if (!isEnabled())

initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(occasion);

if (!hasChildren()) return false;

boolean dealt with;
closing int motion = occasion.getAction() & MotionEventCompat.ACTION_MASK;
change (motion) {
case MotionEvent.ACTION_DOWN:
dealt with = onTouchDown(occasion);
break;

case MotionEvent.ACTION_MOVE:
dealt with = onTouchMove(occasion);
break;

case MotionEvent.ACTION_CANCEL:
dealt with = onTouchCancel(occasion);
break;

case MotionEvent.ACTION_POINTER_UP:
dealt with = onTouchPointerUp(occasion);
break;

case MotionEvent.ACTION_UP:
dealt with = onTouchUp(occasion);
break;

default:
dealt with = false;
break;
}

notifyTouchMode();

return dealt with;
}

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

if (!mIsAttached) {

return false;
}

change (motion & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
int touchMode = mTouchMode;

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

int motionPosition = findMotionRow(y);
if (touchMode != TOUCH_MODE_FLINGING && motionPosition >= 0) {
// Consumer clicked on an precise view (and was not stopping a fling).
// Bear in mind the place the movement occasion began
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: {
change (mTouchMode) {
case TOUCH_MODE_DOWN:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
pointerIndex = 0;
mActivePointerId = ev.getPointerId(pointerIndex);
}
closing 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();
}
tremendous.requestDisallowInterceptTouchEvent(disallowIntercept);
}

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

if (!mDataChanged) {
layoutChildren();
little one.setPressed(true);
setPressed(true);

closing int longPressTimeout = ViewConfiguration.getLongPressTimeout();
closing 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;
}
}
}
}
}

personal boolean onTouchDown(closing MotionEvent occasion) {
closing int x = (int) occasion.getX();
closing int y = (int) occasion.getY();
int motionPosition = pointToPosition(x, y);

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

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

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

if (occasion.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; } personal boolean onTouchMove(closing MotionEvent occasion) { closing int index = MotionEventCompat.findPointerIndex(occasion, 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 && backside <= 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;
}

personal boolean onTouchUpTap(closing MotionEvent occasion) {
closing int motionPosition = mMotionPosition;
if (motionPosition >= 0) {
closing View little one = getChildAt(motionPosition);
if (little one != null && !little one.hasFocusable()) {
if (mTouchMode != TOUCH_MODE_DOWN) {
little one.setPressed(false);
}

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

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

//            mResurrectToPosition = motionPosition;

if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
closing 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();
little one.setPressed(true);
setPressed(true);
postDelayed(new Runnable() {
public void run() {
little one.setPressed(false);
setPressed(false);
if (!mDataChanged) {
submit(performClick);
}
mTouchMode = TOUCH_MODE_IDLE;
}
}, ViewConfiguration.getPressedStateDuration());
} else {
mTouchMode = TOUCH_MODE_IDLE;
}
return true;
} else if (!mDataChanged && motionPosition >= 0 && mAdapter.isEnabled(motionPosition)) {
submit(performClick);
}
}
}
mTouchMode = TOUCH_MODE_IDLE;

return true;
}

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

personal void onSecondaryPointerUp(MotionEvent occasion) {
closing int pointerIndex = (occasion.getAction() &
MotionEventCompat.ACTION_POINTER_INDEX_MASK) >>
MotionEventCompat.ACTION_POINTER_INDEX_SHIFT;
closing int pointerId = occasion.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our lively pointer going up. Select a brand new
// lively pointer and alter accordingly.
// TODO: Make this choice extra clever.
closing int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mMotionX = (int) occasion.getX(newPointerIndex);
mMotionY = (int) occasion.getY(newPointerIndex);
mActivePointerId = occasion.getPointerId(newPointerIndex);
recycleVelocityTracker();
}
}

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

closing Handler handler = getHandler();
if (handler != null) {
handler.removeCallbacks(mPendingCheckForLongPress);
}
setPressed(false);
View motionView = getChildAt(mMotionPosition – mFirstPosition);
if (motionView != null) {
motionView.setPressed(false);
}
closing ViewParent dad or mum = getParent();
if (dad or mum != null) {
dad or mum.requestDisallowInterceptTouchEvent(true);
}

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

personal void scrollIfNeeded(closing int y) {
if (DBG) Log.d(TAG, “scrollIfNeeded y: ” + y);
closing int rawDeltaY = y – mMotionY;
closing 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) {
// cease our dad or mum
if (Math.abs(rawDeltaY) > mTouchSlop) {
closing ViewParent dad or mum = getParent();
if (dad or mum != null) {
dad or mum.requestDisallowInterceptTouchEvent(true);
}
}

closing int motionIndex;
if (mMotionPosition >= 0) {
motionIndex = mMotionPosition – mFirstPosition;
}
else {

motionIndex = getChildCount() / 2;
}

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

// Examine to see if we now have ran into the scroll restrict
View motionView = this.getChildAt(motionIndex);
if (motionView != null) {
if (atEdge) {
// TODO : edge impact & overscroll
}
mMotionY = y;
}
mLastY = y;
}

}
// TODO : ELSE SUPPORT OVERSCROLL!
}

personal int findMotionRow(int y) {
int childCount = getChildCount();
if (childCount > 0) {
// all the time from the highest
for (int i = 0; i < childCount; i++) { View v = getChildAt(i); if (y <= v.getBottom()) { return mFirstPosition + i; } } } return INVALID_POSITION; } personal boolean moveTheChildren(int deltaY, int incrementalDeltaY) { if (DBG) Log.d(TAG, "moveTheChildren deltaY: " + deltaY + "incrementalDeltaY: " + incrementalDeltaY); // there's nothing to maneuver! if (!hasChildren()) return true; closing int firstTop = getHighestChildTop(); closing int lastBottom = getLowestChildBottom(); int effectivePaddingTop = 0; int effectivePaddingBottom = 0; if (mClipToPadding) { effectivePaddingTop = getListPaddingTop(); effectivePaddingBottom = getListPaddingBottom(); } closing int gridHeight = getHeight(); closing int spaceAbove = effectivePaddingTop - getFirstChildTop(); closing int finish = gridHeight - effectivePaddingBottom; closing int spaceBelow = getLastChildBottom() - finish; closing int top = 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);
closing 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; } closing boolean isDown = incrementalDeltaY < 0; closing int headerViewsCount = getHeaderViewsCount(); closing int footerViewsStart = mItemCount - getFooterViewsCount(); int begin = 0; int rely = 0; if (isDown) { int prime = -incrementalDeltaY; if (mClipToPadding) { prime += getListPaddingTop(); } for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= prime) {
break;
}
else {
rely++;
int place = firstPosition + i;
if (place >= headerViewsCount && place < footerViewsStart) { mRecycleBin.addScrapView(child, position); } } } } else { int bottom = gridHeight - incrementalDeltaY; if (mClipToPadding) { bottom -= getListPaddingBottom(); } for (int i = childCount - 1; i >= 0; i–) {
closing View little one = getChildAt(i);
if (little one.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && place < footerViewsStart) { mRecycleBin.addScrapView(child, position); } } } } mBlockLayoutRequests = true; if (count > 0) {
if (DBG) Log.d(TAG, “scrap – detachViewsFromParent start:” + begin + ” rely:” + rely);
detachViewsFromParent(begin, rely);
mRecycleBin.removeSkippedScrap();
onChildrenDetached(begin, rely);
}

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

offsetChildrenTopAndBottom(incrementalDeltaY);

if (isDown) {
mFirstPosition += rely;
}

closing int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(isDown); } // TODO : contact mode selector dealing with mBlockLayoutRequests = false; invokeOnItemScrollListener(); return false; } protected void onChildrenDetached(closing int begin, closing int rely) { } protected void fillGap(boolean down) { closing int rely = getChildCount(); if (down) { // fill down from the highest of the place under our final int place = mFirstPosition + rely; closing int startOffset = getChildTop(place); fillDown(place, startOffset); } else { // refill from the underside of the place above our first. int place = mFirstPosition - 1; closing int startOffset = getChildBottom(place); fillUp(place, startOffset); } adjustViewsAfterFillGap(down); } protected void adjustViewsAfterFillGap(boolean down) { if (down) { correctTooHigh(getChildCount()); } else { correctTooLow(getChildCount()); } } personal View fillDown(int pos, int nextTop) { if (DBG) Log.d(TAG, "fillDown - pos:" + pos + " nextTop:" + nextTop); View selectedView = null; int finish = getHeight(); if (mClipToPadding) { finish -= getListPaddingBottom(); } whereas ((nextTop < finish || 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 > finish || hasSpaceUp()) && pos >= 0) {
// TODO : add choice help
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;
}

personal 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 under;
}
}

personal View makeAndAddView(int place, int y, boolean flowDown, boolean chosen) {
View little one;

onChildCreated(place, flowDown);

if (!mDataChanged) {
// Attempt to use an current view for this place
little one = mRecycleBin.getActiveView(place);
if (little one != null) {

// Discovered it — we’re utilizing an current little one
// This simply must be positioned
setupChild(little one, place, y, flowDown, chosen, true);
return little one;
}
}

// Make a brand new view for this place, or convert an unused view if attainable
little one = obtainView(place, mIsScrap);
// This must be positioned and measured
setupChild(little one, place, y, flowDown, chosen, mIsScrap[0]);

return little one;
}

personal void setupChild(View little one, int place, int y, boolean flowDown,
boolean chosen, boolean recycled) {
closing boolean isSelected = false; // TODO : chosen && shouldShowSelector();
closing boolean updateChildSelected = isSelected != little one.isSelected();
closing int mode = mTouchMode;
closing 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);
}
little one.measure(childWidthSpec, childHeightSpec);
}

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

protected LayoutParams generateHeaderFooterLayoutParams(closing View little one) {
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}

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

scrapView = mRecycleBin.getScrapView(place);

View little one;
if (scrapView != null) {
if (DBG) Log.d(TAG, “getView from scrap position:” + place);
little one = mAdapter.getView(place, scrapView, this);

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

return little one;
}

personal void correctTooHigh(int childCount) {
// First see if the final merchandise is seen. If it isn’t, it’s OK for the
// prime of the record to be pushed up.
int lastPosition = mFirstPosition + childCount – 1;
if (lastPosition == mItemCount – 1 && childCount > 0) {

// … and its backside edge
closing int lastBottom = getLowestChildBottom();

// That is backside of our drawable space
closing int finish = (getBottom() – getTop()) – getListPaddingBottom();

// That is how far the underside fringe of the final view is from the underside of the
// drawable space
int bottomOffset = finish – lastBottom;

closing int firstTop = getHighestChildTop();

// Ensure we’re 1) Too excessive, and a pair of) Both there are extra rows above the
// first row or the primary row is scrolled off the highest of the drawable space
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 hole that was opened above mFirstPosition with extra rows, if
// attainable
int previousPosition = mFirstPosition – 1;
fillUp(previousPosition, getNextChildUpsBottom(previousPosition));
// Shut up the remaining hole
adjustViewsUpOrDown();
}

}
}
}

personal void correctTooLow(int childCount) {
// First see if the primary merchandise is seen. If it isn’t, it’s OK for the
// backside of the record to be pushed down.
if (mFirstPosition == 0 && childCount > 0) {

// … and its prime edge
closing int firstTop = getHighestChildTop();

// That is prime of our drawable space
closing int begin = getListPaddingTop();

// That is backside of our drawable space
closing int finish = (getTop() – getBottom()) – getListPaddingBottom();

// That is how far the highest fringe of the primary view is from the highest of the
// drawable space
int topOffset = firstTop – begin;
closing int lastBottom = getLowestChildBottom();

int lastPosition = mFirstPosition + childCount – 1;

// Ensure we’re 1) Too low, and a pair of) Both there are extra rows under the
// final row or the final row is scrolled off the underside of the drawable space
if (topOffset > 0) {
if (lastPosition < mItemCount - 1 || lastBottom > finish) {
if (lastPosition == mItemCount – 1) {
// Don’t pull the underside too far up
topOffset = Math.min(topOffset, lastBottom – finish);
}
// Transfer every little thing 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 got here up quick. Slide all views as much as make them
// align with the highest
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(rely – 1).getBottom() : paddingTop;
}

protected int getChildBottom(closing int place) {
int rely = getChildCount();
int paddingBottom = 0;
if (mClipToPadding) {
paddingBottom = getListPaddingBottom();
}
return rely > 0 ? getChildAt(0).getTop() : getHeight() – paddingBottom;
}

protected int getNextChildDownsTop(closing int place) {
closing int rely = getChildCount();
return rely > 0 ? getChildAt(rely – 1).getBottom() : 0;
}

protected int getNextChildUpsBottom(closing int place) {
closing int rely = getChildCount();
if (rely == 0) {
return 0;
}
return rely > 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);
closing int rely = getChildCount();
for (int i = 0; i < rely; i++) { closing 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); } personal void initOrResetVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.receive(); } else { mVelocityTracker.clear(); } } personal void initVelocityTrackerIfNotExists() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.receive(); } } personal void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } personal void startFlingRunnable(closing float velocity) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } mFlingRunnable.begin((int) -velocity); } personal void stopFlingRunnable() { if (mFlingRunnable != null) { mFlingRunnable.endFling(); } } personal class FlingRunnable implements Runnable { personal closing Scroller mScroller; personal int mLastFlingY; FlingRunnable() { mScroller = new Scroller(getContext()); } void begin(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 length) { 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) {
// Record is transferring in the direction of the highest. Use first view as mMotionPosition
mMotionPosition = mFirstPosition;
// Don’t fling greater than 1 display screen
delta = Math.min(getHeight() – getPaddingBottom() – getPaddingTop() – 1, delta);
}
else {
// Record is transferring in the direction of the underside. Use final view as mMotionPosition
int offsetToLast = getChildCount() – 1;
mMotionPosition = mFirstPosition + offsetToLast;

// Don’t fling greater than 1 display screen
delta = Math.max(-(getHeight() – getPaddingBottom() – getPaddingTop() – 1), delta);
}

closing boolean atEnd = moveTheChildren(delta, delta);

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

}

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

public void notifyTouchMode() {
// solely inform the scroll listener about some issues we wish it to know
change (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;
}
}

personal OnScrollListener mOnScrollListener;

public void setOnScrollListener(OnScrollListener scrollListener) {
tremendous.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”) personal 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 simply eliminated our empty view, ensure the record view is seen
setVisibility(View.VISIBLE);
}

// We are actually GONE, so pending layouts is not going to be dispatched.
// Drive one right here to guarantee that the state of the record 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 {

personal Parcelable mInstanceState = null;

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

mRecycleBin.clearTransientStateViews();

// Detect the case the place a cursor that was beforehand invalidated has
// been repopulated with new knowledge.
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()) {
// Bear in mind the present state for the case the place our internet hosting exercise is being
// stopped and later restarted
mInstanceState = ExtendableListView.this.onSaveInstanceState();
}

// Knowledge is invalid so we should always 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;

// Place of the view within the knowledge
int place;

// adapter ID the view represents fetched from the adapter if it’s steady
lengthy itemId = -1;

// adapter view sort
int viewType;

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

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

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

public LayoutParams(ViewGroup.LayoutParams supply) {
tremendous(supply);
}

}

class RecycleBin {

personal int mFirstActivePosition;

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

personal ArrayList[] mScrapViews;

personal int mViewTypeCount;

personal ArrayList mCurrentScrap;

personal ArrayList mSkippedScrap;

personal SparseArrayCompat mTransientStateViews;

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

public void markChildrenDirty() {
if (mViewTypeCount == 1) {
closing ArrayList scrap = mCurrentScrap;
closing int scrapCount = scrap.dimension();
for (int i = 0; i < scrapCount; i++) { scrap.get(i).forceLayout(); } } else { closing int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { closing ArrayList scrap = mScrapViews[i];
closing int scrapCount = scrap.dimension();
for (int j = 0; j < scrapCount; j++) { scrap.get(j).forceLayout(); } } } if (mTransientStateViews != null) { closing int rely = mTransientStateViews.dimension(); for (int i = 0; i < count; i++) { mTransientStateViews.valueAt(i).forceLayout(); } } } public boolean shouldRecycleViewType(int viewType) { return viewType >= 0;
}

void clear() {
if (mViewTypeCount == 1) {
closing ArrayList scrap = mCurrentScrap;
closing int scrapCount = scrap.dimension();
for (int i = 0; i < scrapCount; i++) { removeDetachedView(scrap.take away(scrapCount - 1 - i), false); } } else { closing int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { closing ArrayList scrap = mScrapViews[i];
closing int scrapCount = scrap.dimension();
for (int j = 0; j < scrapCount; j++) { removeDetachedView(scrap.take away(scrapCount - 1 - j), false); } } } if (mTransientStateViews != null) { mTransientStateViews.clear(); } } void fillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.size < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; closing 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.size) { closing View match = activeViews[index]; activeViews[index] = null; return match; } return null; } View getTransientStateView(int place) { if (mTransientStateViews == null) { return null; } closing int index = mTransientStateViews.indexOfKey(place); 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.size) { return retrieveFromScrap(mScrapViews[whichScrap], place); } } return null; } void addScrapView(View scrap, int place) { if (DBG) Log.d(TAG, "addScrapView position = " + place); LayoutParams lp = (LayoutParams) scrap.getLayoutParams(); if (lp == null) { return; } lp.place = place; // Do not put header or footer views or views that needs to be ignored // into the scrap heap int viewType = lp.viewType; closing boolean scrapHasTransientState = ViewCompat.hasTransientState(scrap); if (!shouldRecycleViewType(viewType) || scrapHasTransientState) { if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) { if (mSkippedScrap == null) { mSkippedScrap = new ArrayList();
}
mSkippedScrap.add(scrap);
}
if (scrapHasTransientState) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArrayCompat();
}
mTransientStateViews.put(place, scrap);
}
return;
}

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

void removeSkippedScrap() {
if (mSkippedScrap == null) {
return;
}
closing int rely = mSkippedScrap.dimension();
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 scrapViews = mCurrentScrap;
closing int rely = activeViews.size;
for (int i = rely – 1; i >= 0; i–) {
closing View sufferer = activeViews[i];
if (sufferer != null) {
closing LayoutParams lp = (LayoutParams) sufferer.getLayoutParams();
activeViews[i] = null;

closing boolean scrapHasTransientState = ViewCompat.hasTransientState(sufferer);
int viewType = lp.viewType;

if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
// Don’t transfer views that needs to be ignored
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
removeDetachedView(sufferer, false);
}
if (scrapHasTransientState) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArrayCompat();
}
mTransientStateViews.put(mFirstActivePosition + i, sufferer);
}
proceed;
}

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

pruneScrapViews();
}

personal void pruneScrapViews() {
closing int maxViews = mActiveViews.size;
closing int viewTypeCount = mViewTypeCount;
closing ArrayList[] scrapViews = mScrapViews;
for (int i = 0; i < viewTypeCount; ++i) { closing ArrayList scrapPile = scrapViews[i];
int dimension = scrapPile.dimension();
closing int extras = dimension – maxViews;
dimension–;
for (int j = 0; j < extras; j++) { removeDetachedView(scrapPile.take away(size--), false); } } if (mTransientStateViews != null) { for (int i = 0; i < mTransientStateViews.dimension(); i++) { closing View v = mTransientStateViews.valueAt(i); if (!ViewCompat.hasTransientState(v)) { mTransientStateViews.removeAt(i); i--; } } } } void setCacheColorHint(int shade) { if (mViewTypeCount == 1) { closing ArrayList scrap = mCurrentScrap;
closing int scrapCount = scrap.dimension();
for (int i = 0; i < scrapCount; i++) { scrap.get(i).setDrawingCacheBackgroundColor(shade); } } else { closing int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { closing ArrayList scrap = mScrapViews[i];
closing int scrapCount = scrap.dimension();
for (int j = 0; j < scrapCount; j++) { scrap.get(j).setDrawingCacheBackgroundColor(shade); } } } // Simply in case that is referred to as throughout a structure go closing View[] activeViews = mActiveViews; closing int rely = activeViews.size; for (int i = 0; i < rely; ++i) { closing View sufferer = activeViews[i]; if (sufferer != null) { sufferer.setDrawingCacheBackgroundColor(shade); } } } } static View retrieveFromScrap(ArrayList scrapViews, int place) {
int dimension = scrapViews.dimension();
if (dimension > 0) {
// See if we nonetheless have a view for this place.
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 mostly on the offset of the primary 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; } } personal void clearState() { // cleanup headers and footers earlier than eradicating the views clearRecycledState(mHeaderViewInfos); clearRecycledState(mFooterViewInfos); removeAllViewsInLayout(); mFirstPosition = 0; mDataChanged = false; mRecycleBin.clear(); mNeedSync = false; mSyncState = null; mLayoutMode = LAYOUT_NORMAL; invalidate(); } personal void clearRecycledState(ArrayList infos) {
if (infos == null) return;
for (FixedViewInfo information : infos) {
closing View little one = information.view;
closing ViewGroup.LayoutParams p = little one.getLayoutParams();

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

public static class ListSavedState extends ClassLoaderSavedState {
protected lengthy selectedId;
protected lengthy firstId;
protected int viewTop;
protected int place;
protected int top;

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

public ListSavedState(Parcel in) {
tremendous(in);
selectedId = in.readLong();
firstId = in.readLong();
viewTop = in.readInt();
place = in.readInt();
top = in.readInt();
}

@Override
public void writeToParcel(Parcel out, int flags) {
tremendous.writeToParcel(out, flags);
out.writeLong(selectedId);
out.writeLong(firstId);
out.writeInt(viewTop);
out.writeInt(place);
out.writeInt(top);
}

@Override
public String toString() {
return “ExtendableListView.ListSavedState{”
+ Integer.toHexString(System.identityHashCode(this))
+ ” selectedId=” + selectedId
+ ” firstId=” + firstId
+ ” viewTop=” + viewTop
+ ” place=” + place
+ ” top=” + top + “}”;
}

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

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

@Override
public Parcelable onSaveInstanceState() {

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

if (mSyncState != null) {
// Simply preserve what we final restored.
ss.selectedId = mSyncState.selectedId;
ss.firstId = mSyncState.firstId;
ss.viewTop = mSyncState.viewTop;
ss.place = mSyncState.place;
ss.top = mSyncState.top;
return ss;
}

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

// TODO : sync choice after we deal with it
if (haveChildren && mFirstPosition > 0) {

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

return ss;
}

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

mSyncHeight = ss.top;

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

personal class PerformClick extends WindowRunnnable implements Runnable {
int mClickMotionPosition;

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

closing ListAdapter adapter = mAdapter;
closing int motionPosition = mClickMotionPosition;
if (adapter != null && mItemCount > 0 &&
motionPosition != INVALID_POSITION &&
motionPosition < adapter.getCount() && sameWindow()) { closing View view = getChildAt(motionPosition); // a repair by @pboos if (view != null) { closing int clickPosition = motionPosition + mFirstPosition; performItemClick(view, clickPosition, adapter.getItemId(clickPosition)); } } } } personal boolean performLongPress(closing View little one, closing int longPressPosition, closing lengthy longPressId) { boolean dealt with = false; OnItemLongClickListener onItemLongClickListener = getOnItemLongClickListener(); if (onItemLongClickListener != null) { dealt with = onItemLongClickListener.onItemLongClick(ExtendableListView.this, little one, longPressPosition, longPressId); } if (dealt with) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return dealt with; } personal class WindowRunnnable { personal int mOriginalAttachCount; public void rememberWindowAttachCount() { mOriginalAttachCount = getWindowAttachCount(); } public boolean sameWindow() { return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount; } } }

HeaderViewListAdapter.java

package deal 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 {

personal closing ListAdapter mAdapter;

// These two ArrayList are assumed to NOT be null.
// They’re certainly created when declared in ListView after which shared.
ArrayList mHeaderViewInfos;
ArrayList mFooterViewInfos;

// Used as a placeholder in case the supplied information views are certainly null.
// At present solely utilized by some CTS exams, which can be eliminated.
static closing ArrayList EMPTY_INFO_LIST =
new ArrayList();

boolean mAreAllFixedViewsSelectable;

personal closing boolean mIsFilterable;

public HeaderViewListAdapter(ArrayList headerViewInfos,
ArrayList 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.dimension();
}

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

public boolean isEmpty()

personal boolean areAllListInfosSelectable(ArrayList infos) {
if (infos != null) {
for (StaggeredGridView.FixedViewInfo information : infos) {
if (!information.isSelectable) {
return false;
}
}
}
return true;
}

public boolean removeHeader(View v) {
for (int i = 0; i < mHeaderViewInfos.dimension(); i++) { StaggeredGridView.FixedViewInfo information = mHeaderViewInfos.get(i); if (information.view == v) { mHeaderViewInfos.take away(i); mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos); return true; } } return false; } public boolean removeFooter(View v) { for (int i = 0; i < mFooterViewInfos.dimension(); i++) { StaggeredGridView.FixedViewInfo information = mFooterViewInfos.get(i); if (information.view == v) { mFooterViewInfos.take away(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 place) { // Header (unfavourable positions will throw an ArrayIndexOutOfBoundsException) int numHeaders = getHeadersCount(); if (place < numHeaders) { return mHeaderViewInfos.get(place).isSelectable; } // Adapter closing int adjPosition = place - 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 place) { // Header (unfavourable positions will throw an ArrayIndexOutOfBoundsException) int numHeaders = getHeadersCount(); if (place < numHeaders) { return mHeaderViewInfos.get(place).knowledge; } // Adapter closing int adjPosition = place - 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 = place – 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 place, View convertView, ViewGroup dad or mum) { // Header (unfavourable positions will throw an ArrayIndexOutOfBoundsException) int numHeaders = getHeadersCount(); if (place < numHeaders) { return mHeaderViewInfos.get(place).view; } // Adapter closing int adjPosition = place - 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 = place – 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 deal com.android.grid;

import android.content material.Context;
import android.content material.res.Configuration;
import android.content material.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 {

personal static closing String TAG = “StaggeredGridView”;
personal static closing boolean DBG = false;

personal static closing int DEFAULT_COLUMNS_PORTRAIT = 2;
personal static closing int DEFAULT_COLUMNS_LANDSCAPE = 3;

personal int mColumnCount;
personal int mItemMargin;
personal int mColumnWidth;
personal boolean mNeedSync;

personal int mColumnCountPortrait = DEFAULT_COLUMNS_PORTRAIT;
personal int mColumnCountLandscape = DEFAULT_COLUMNS_LANDSCAPE;

personal SparseArray mPositionData;
personal int mGridPaddingLeft;
personal int mGridPaddingRight;
personal int mGridPaddingTop;
personal int mGridPaddingBottom;

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

GridItemRecord() { }

personal 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 closing Parcelable.Creator CREATOR
= new Parcelable.Creator() {
public GridItemRecord createFromParcel(Parcel in) {
return new GridItemRecord(in);
}

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

personal int[] mColumnTops;

personal int[] mColumnBottoms;

personal int[] mColumnLefts;

personal int mDistanceToTop;

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

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

public StaggeredGridView(closing Context context, closing AttributeSet attrs, closing int defStyle) {
tremendous(context, attrs, defStyle);

if (attrs != null) {
// get the variety of columns in portrait and panorama
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; // decided onMeasure
// Creating these empty arrays to keep away from saving null states
mColumnTops = new int[0];
mColumnBottoms = new int[0];
mColumnLefts = new int[0];
mPositionData = new SparseArray();
}

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

// Grid padding is utilized to the record merchandise rows however 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 prime, int proper, int backside) {
mGridPaddingLeft = left;
mGridPaddingTop = prime;
mGridPaddingRight = proper;
mGridPaddingBottom = backside;
}

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
//
personal boolean isLandscape() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}

@Override
protected void onMeasure(closing int widthMeasureSpec, closing int heightMeasureSpec) {
tremendous.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.top, MeasureSpec.EXACTLY);
}
else {
childHeightSpec = MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED);
}
little one.measure(childWidthSpec, childHeightSpec);
}

closing int childHeight = getChildHeight(little one);
setPositionHeightRatio(place, childHeight);

if (DBG) Log.d(TAG, “onMeasureChild AFTER position:” + place +
” 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(closing int place, closing boolean flowDown) {
tremendous.onChildCreated(place, flowDown);
if (!isHeaderOrFooter(place)) {
// will we have already got a column for this place?
closing int column = getChildColumn(place, flowDown);
setPositionColumn(place, column);
if (DBG) Log.d(TAG, “onChildCreated position:” + place +
” is in column:” + column);
}
else {
setPositionIsHeaderFooter(place);
}
}

personal void requestLayoutChildren() {
closing int rely = getChildCount();
for (int i = 0; i < rely; i++) { closing View v = getChildAt(i); if (v != null) v.requestLayout(); } } @Override protected void layoutChildren() { preLayoutChildren(); tremendous.layoutChildren(); } personal void preLayoutChildren() { // on a significant re-layout reset for our subsequent structure go if (!mNeedSync) { Arrays.fill(mColumnBottoms, 0); } else { mNeedSync = false; } // copy the tops into the underside // since we'll redo a structure go that can draw down from // the highest System.arraycopy(mColumnTops, 0, mColumnBottoms, 0, mColumnCount); } // NOTE : Views will both be structure out through onLayoutChild // OR // Views will likely be offset if they're lively however offscreen in order that we will recycle! // Each onLayoutChild() and onOffsetChild are referred to as after we measure our view // see ExtensibleListView.setupChild(); @Override protected void onLayoutChild(closing View little one, closing int place, closing boolean flowDown, closing int childrenLeft, closing int childTop, closing int childRight, closing int childBottom) { if (isHeaderOrFooter(place)) { layoutGridHeaderFooter(little one, place, flowDown, childrenLeft, childTop, childRight, childBottom); } else { layoutGridChild(little one, place, flowDown, childrenLeft, childRight); } } personal void layoutGridHeaderFooter(closing View little one, closing int place, closing boolean flowDown, closing int childrenLeft, closing int childTop, closing int childRight, closing int childBottom) { // offset the highest and backside of all our columns // if it is the footer we wish it under the bottom little one backside int gridChildTop; int gridChildBottom; if (flowDown) { gridChildTop = getLowestPositionedBottom(); gridChildBottom = gridChildTop + getChildHeight(little one); } else { gridChildBottom = getHighestPositionedTop(); gridChildTop = gridChildBottom - getChildHeight(little one); } for (int i = 0; i < mColumnCount; i++) { updateColumnTopIfNeeded(i, gridChildTop); updateColumnBottomIfNeeded(i, gridChildBottom); } tremendous.onLayoutChild(little one, place, flowDown, childrenLeft, gridChildTop, childRight, gridChildBottom); } personal void layoutGridChild(closing View little one, closing int place, closing boolean flowDown, closing int childrenLeft, closing int childRight) { // stash the underside and the highest if it is greater positioned int column = getPositionColumn(place); int gridChildTop; int gridChildBottom; int childTopMargin = getChildTopMargin(place); int childBottomMargin = getChildBottomMargin(); int verticalMargins = childTopMargin + childBottomMargin; if (flowDown) { gridChildTop = mColumnBottoms[column]; // the following gadgets prime is the final gadgets backside gridChildBottom = gridChildTop + (getChildHeight(little one) + verticalMargins); } else { gridChildBottom = mColumnTops[column]; // the underside of the following column up is our prime gridChildTop = gridChildBottom - (getChildHeight(little one) + verticalMargins); } if (DBG) Log.d(TAG, "onLayoutChild position:" + place + " column:" + column + " gridChildTop:" + gridChildTop + " gridChildBottom:" + gridChildBottom); // we additionally know the column of this view so let's stash it within the // view's structure params GridLayoutParams layoutParams = (GridLayoutParams) little one.getLayoutParams(); layoutParams.column = column; updateColumnBottomIfNeeded(column, gridChildBottom); updateColumnTopIfNeeded(column, gridChildTop); // subtract the margins earlier than structure gridChildTop += childTopMargin; gridChildBottom -= childBottomMargin; little one.structure(childrenLeft, gridChildTop, childRight, gridChildBottom); } @Override protected void onOffsetChild(closing View little one, closing int place, closing boolean flowDown, closing int childrenLeft, closing int childTop) { // if the kid is recycled and is simply offset // we nonetheless wish to add its deets into our retailer if (isHeaderOrFooter(place)) { offsetGridHeaderFooter(little one, place, flowDown, childrenLeft, childTop); } else { offsetGridChild(little one, place, flowDown, childrenLeft, childTop); } } personal void offsetGridHeaderFooter(closing View little one, closing int place, closing boolean flowDown, closing int childrenLeft, closing int childTop) { // offset the highest and backside of all our columns // if it is the footer we wish it under the bottom little one backside int gridChildTop; int gridChildBottom; if (flowDown) { gridChildTop = getLowestPositionedBottom(); gridChildBottom = gridChildTop + getChildHeight(little one); } else { gridChildBottom = getHighestPositionedTop(); gridChildTop = gridChildBottom - getChildHeight(little one); } for (int i = 0; i < mColumnCount; i++) { updateColumnTopIfNeeded(i, gridChildTop); updateColumnBottomIfNeeded(i, gridChildBottom); } tremendous.onOffsetChild(little one, place, flowDown, childrenLeft, gridChildTop); } personal void offsetGridChild(closing View little one, closing int place, closing boolean flowDown, closing int childrenLeft, closing int childTop) { // stash the underside and the highest if it is greater positioned int column = getPositionColumn(place); int gridChildTop; int gridChildBottom; int childTopMargin = getChildTopMargin(place); int childBottomMargin = getChildBottomMargin(); int verticalMargins = childTopMargin + childBottomMargin; if (flowDown) { gridChildTop = mColumnBottoms[column]; // the following gadgets prime is the final gadgets backside gridChildBottom = gridChildTop + (getChildHeight(little one) + verticalMargins); } else { gridChildBottom = mColumnTops[column]; // the underside of the following column up is our prime gridChildTop = gridChildBottom - (getChildHeight(little one) + verticalMargins); } if (DBG) Log.d(TAG, "onOffsetChild position:" + place + " column:" + column + " childTop:" + childTop + " gridChildTop:" + gridChildTop + " gridChildBottom:" + gridChildBottom); // we additionally know the column of this view so let's stash it within the // view's structure params GridLayoutParams layoutParams = (GridLayoutParams) little one.getLayoutParams(); layoutParams.column = column; updateColumnBottomIfNeeded(column, gridChildBottom); updateColumnTopIfNeeded(column, gridChildTop); tremendous.onOffsetChild(little one, place, flowDown, childrenLeft, gridChildTop + childTopMargin); } personal int getChildHeight(closing View little one) { return little one.getMeasuredHeight(); } personal int getChildTopMargin(closing int place) { boolean isFirstRow = place < (getHeaderViewsCount() + mColumnCount); return isFirstRow ? mItemMargin : 0; } personal int getChildBottomMargin() { return mItemMargin; } @Override protected LayoutParams generateChildLayoutParams(closing View little one) { GridLayoutParams layoutParams = null; closing ViewGroup.LayoutParams childParams = little one.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; } personal 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(closing int place) {
if (isHeaderOrFooter(place)) {
return tremendous.getChildLeft(place);
}
else {
closing int column = getPositionColumn(place);
return mColumnLefts[column];
}
}

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

@Override
protected int getNextChildDownsTop(closing int place) {
if (isHeaderOrFooter(place)) {
return tremendous.getNextChildDownsTop(place);
}
else {
return getHighestPositionedBottom();
}
}

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

@Override
protected int getNextChildUpsBottom(closing int place) {
if (isHeaderOrFooter(place)) {
return tremendous.getNextChildUpsBottom(place);
}
else {
return getLowestPositionedTop();
}
}

@Override
protected int getLastChildBottom() {
closing int lastPosition = mFirstPosition + (getChildCount() – 1);
if (isHeaderOrFooter(lastPosition)) {
return tremendous.getLastChildBottom();
}
return getHighestPositionedBottom();
}

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

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

@Override
protected int getLowestChildBottom() {
closing int lastPosition = mFirstPosition + (getChildCount() – 1);
if (isHeaderOrFooter(lastPosition)) {
return tremendous.getLowestChildBottom();
}
return getLowestPositionedBottom();
}

@Override
protected void offsetChildrenTopAndBottom(closing int offset) {
tremendous.offsetChildrenTopAndBottom(offset);
offsetAllColumnsTopAndBottom(offset);
offsetDistanceToTop(offset);
}

protected void offsetChildrenTopAndBottom(closing int offset, closing int column) {
if (DBG) Log.d(TAG, “offsetChildrenTopAndBottom: ” + offset + ” column:” + column);
closing int rely = getChildCount();
for (int i = 0; i < rely; i++) { closing 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); } personal void offsetDistanceToTop(closing int offset) { mDistanceToTop += offset; if (DBG) Log.d(TAG, "offset mDistanceToTop:" + mDistanceToTop); } public int getDistanceToTop() { return mDistanceToTop; } personal void offsetAllColumnsTopAndBottom(closing int offset) { if (offset != 0) { for (int i = 0; i < mColumnCount; i++) { offsetColumnTopAndBottom(offset, i); } } } personal void offsetColumnTopAndBottom(closing int offset, closing int column) { if (offset != 0) { mColumnTops[column] += offset; mColumnBottoms[column] += offset; } } @Override protected void adjustViewsAfterFillGap(closing boolean down) { tremendous.adjustViewsAfterFillGap(down); // repair vertical gaps when hitting the highest after a rotate // solely when scrolling again up! if (!down) { alignTops(); } } personal void alignTops() { if (mFirstPosition == getHeaderViewsCount()) { // we're exhibiting all of the views earlier than the header views int[] nonHeaderTops = getHighestNonHeaderTops(); // we should always 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 of the tops are aligned
}
// what’s the best
if (nonHeaderTops[i] < highestTop) { highestTop = nonHeaderTops[i]; highestColumn = i; } } // skip the remainder. if (isAligned) return; // we have the best 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 little one = getChildAt(i); if (little one != null && little one.getLayoutParams() != null && little one.getLayoutParams() instanceof GridLayoutParams) { // is that this kid's prime the best non GridLayoutParams lp = (GridLayoutParams) little one.getLayoutParams(); // is it a toddler that is not a header if (lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && little one.getTop() < nonHeaderTops[lp.column]) { nonHeaderTops[lp.column] = little one.getTop(); } } } } return nonHeaderTops; } @Override protected void onChildrenDetached(closing int begin, closing int rely) { tremendous.onChildrenDetached(begin, rely); // undergo our remaining views and sync the highest and backside stash. // Restore the highest and backside column boundaries from the views we nonetheless have Arrays.fill(mColumnTops, Integer.MAX_VALUE); Arrays.fill(mColumnBottoms, 0); for (int i = 0; i < getChildCount(); i++) { closing View little one = getChildAt(i); if (little one != null) { closing LayoutParams childParams = (LayoutParams) little one.getLayoutParams(); if (childParams.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && childParams instanceof GridLayoutParams) { GridLayoutParams layoutParams = (GridLayoutParams) childParams; int column = layoutParams.column; int place = layoutParams.place; closing int childTop = little one.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 right here
closing int childTop = little one.getTop();
closing int childBottom = little one.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 finish = mClipToPadding ? getRowPaddingTop() : 0;
return getLowestPositionedTop() > finish;
}

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

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

@Override
protected void onSizeChanged(int w, int h) {
tremendous.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 now have knowledge
if (getCount() > 0 && mPositionData.dimension() > 0) {
onColumnSync();
}

requestLayout();
}
}

personal int calculateColumnWidth(closing int gridWidth) {
closing int listPadding = getRowPaddingLeft() + getRowPaddingRight();
return (gridWidth – listPadding – mItemMargin * (mColumnCount + 1)) / mColumnCount;
}

personal int calculateColumnLeft(closing int colIndex) {
return getRowPaddingLeft() + mItemMargin + ((mItemMargin + mColumnWidth) * colIndex);
}

/***
* Our mColumnTops and mColumnBottoms must be re-built as much as the
* mSyncPosition – the next structure request will then
* structure the that place after which fillUp and fillDown appropriately.
*/
personal void onColumnSync() {
// re-calc tops for brand new column rely!
int syncPosition = Math.min(mSyncPosition, getCount() – 1);

SparseArray positionHeightRatios = new SparseArray(syncPosition);
for (int pos = 0; pos < syncPosition; pos++) { // test for weirdness closing 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 place whereas on the similar time // rebuilding our GridItemRecord assortment if (DBG) Log.d(TAG, "onColumnSync column width:" + mColumnWidth); for (int pos = 0; pos < syncPosition; pos++) { //Examine for weirdness once more closing Double heightRatio = positionHeightRatios.get(pos); if(heightRatio == null){ break; } closing GridItemRecord rec = getOrCreateRecord(pos); closing int top = (int) (mColumnWidth * heightRatio); rec.heightRatio = heightRatio; int prime; int backside; // test for headers if (isHeaderOrFooter(pos)) { // the following prime is the underside for that column prime = getLowestPositionedBottom(); backside = prime + top; for (int i = 0; i < mColumnCount; i++) { mColumnTops[i] = prime; mColumnBottoms[i] = backside; } } else { // what is the subsequent column down ? closing int column = getHighestPositionedBottomColumn(); // the following prime is the underside for that column prime = mColumnBottoms[column]; backside = prime + top + getChildTopMargin(pos) + getChildBottomMargin(); mColumnTops[column] = prime; mColumnBottoms[column] = backside; rec.column = column; } if (DBG) Log.d(TAG, "onColumnSync position:" + pos + " top:" + prime + " bottom:" + backside + " height:" + top + " heightRatio:" + heightRatio); } // our sync place will likely be displayed on this column closing int syncColumn = getHighestPositionedBottomColumn(); setPositionColumn(syncPosition, syncColumn); // we wish to offset from top of the sync place // minus the offset int syncToBottom = mColumnBottoms[syncColumn]; int offset = -syncToBottom + mSpecificTop; // offset all columns by offsetAllColumnsTopAndBottom(offset); // sync the space to prime mDistanceToTop = -syncToBottom; // stash our bottoms in our tops - although these will likely be copied again to the bottoms System.arraycopy(mColumnBottoms, 0, mColumnTops, 0, mColumnCount); } // ////////////////////////////////////////////////////////////////////////////////////////// // GridItemRecord UTILS // personal void setPositionColumn(closing int place, closing int column) { GridItemRecord rec = getOrCreateRecord(place); rec.column = column; } personal void setPositionHeightRatio(closing int place, closing int top) { GridItemRecord rec = getOrCreateRecord(place); rec.heightRatio = (double)  top / (double) mColumnWidth; if (DBG) Log.d(TAG, "position:" + place + " width:" + mColumnWidth + " height:" + top + " heightRatio:" + rec.heightRatio); } personal void setPositionIsHeaderFooter(closing int place) { GridItemRecord rec = getOrCreateRecord(place); rec.isHeaderFooter = true; } personal GridItemRecord getOrCreateRecord(closing int place) { GridItemRecord rec = mPositionData.get(place, null); if (rec == null) { rec = new GridItemRecord(); mPositionData.append(place, rec); } return rec; } personal int getPositionColumn(closing int place) { GridItemRecord rec = mPositionData.get(place, null); return rec != null ? rec.column : -1; } // ////////////////////////////////////////////////////////////////////////////////////////// // HELPERS // personal boolean isHeaderOrFooter(closing int place) { closing int viewType = mAdapter.getItemViewType(place); return viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER; } personal int getChildColumn(closing int place, closing boolean flowDown) { // will we have already got a column for this little one place? int column = getPositionColumn(place); // we do not have the column or it now not matches in our grid closing int columnCount = mColumnCount; if (column < 0 || column >= columnCount) {
// if we’re taking place –
// get the best positioned (lowest worth)
// column backside
if (flowDown) {
column = getHighestPositionedBottomColumn();
}
else {
column = getLowestPositionedTopColumn();

}
}
return column;
}

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

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

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

personal void initColumnLefts() {
for (int i = 0; i < mColumnCount; i++) { mColumnLefts[i] = calculateColumnLeft(i); } } // ////////////////////////////////////////////////////////////////////////////////////////// // BOTTOM // personal int getHighestPositionedBottom() { closing int column = getHighestPositionedBottomColumn(); return mColumnBottoms[column]; } personal int getHighestPositionedBottomColumn() { int columnFound = 0; int highestPositionedBottom = Integer.MAX_VALUE; // the best positioned backside is the one with the bottom worth 😀 for (int i = 0; i < mColumnCount; i++) { int backside = mColumnBottoms[i]; if (backside < highestPositionedBottom) { highestPositionedBottom = backside; columnFound = i; } } return columnFound; } personal int getLowestPositionedBottom() { closing int column = getLowestPositionedBottomColumn(); return mColumnBottoms[column]; } personal int getLowestPositionedBottomColumn() { int columnFound = 0; int lowestPositionedBottom = Integer.MIN_VALUE; // the bottom positioned backside is the one with the best worth 😀 for (int i = 0; i < mColumnCount; i++) { int bottom = mColumnBottoms[i]; if (bottom > lowestPositionedBottom) {
lowestPositionedBottom = backside;
columnFound = i;
}
}
return columnFound;
}

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

personal int getLowestPositionedTop() {
closing int column = getLowestPositionedTopColumn();
return mColumnTops[column];
}

personal int getLowestPositionedTopColumn() {
int columnFound = 0;
// we’ll go backwards by way of because the proper most
// will doubtless be the bottom positioned High
int lowestPositionedTop = Integer.MIN_VALUE;
// the bottom positioned prime is the one with the best worth 😀
for (int i = 0; i < mColumnCount; i++) { int top = mColumnTops[i]; if (top > lowestPositionedTop) {
lowestPositionedTop = prime;
columnFound = i;
}
}
return columnFound;
}

personal int getHighestPositionedTop() {
closing int column = getHighestPositionedTopColumn();
return mColumnTops[column];
}

personal int getHighestPositionedTopColumn() {
int columnFound = 0;
int highestPositionedTop = Integer.MAX_VALUE;
// the best positioned prime is the one with the bottom worth 😀
for (int i = 0; i < mColumnCount; i++) { int prime = mColumnTops[i]; if (prime < 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) {
tremendous.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 closing Creator CREATOR
= new Creator() {
public GridListSavedState createFromParcel(Parcel in) {
return new GridListSavedState(in);
}

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

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

// from the record state
ss.selectedId = listState.selectedId;
ss.firstId = listState.firstId;
ss.viewTop = listState.viewTop;
ss.place = listState.place;
ss.top = listState.top;

// 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();
}

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;
tremendous.onRestoreInstanceState(ss);
}
}

DynamicHeightImageView.java

package deal com.android.grid.util;

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

public class DynamicHeightImageView extends ImageView {

personal double mHeightRatio;

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

public DynamicHeightImageView(Context context) {
tremendous(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 picture views dimension
int width = MeasureSpec.getSize(widthMeasureSpec);
int top = (int) (width * mHeightRatio);
setMeasuredDimension(width, top);
}
else {
tremendous.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

DynamicHeightTextView.java

package deal com.android.grid.util;

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

public class DynamicHeightTextView extends TextView {

personal double mHeightRatio;

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

public DynamicHeightTextView(Context context) {
tremendous(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 picture views dimension
int width = MeasureSpec.getSize(widthMeasureSpec);
int top = (int) (width * mHeightRatio);
setMeasuredDimension(width, top);
}
else {
tremendous.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

—–> Create Android Staggered Grid Challenge And Hyperlink Above Challenge AS Lib Challenge.

—>  AndroidManifest.xml











In Values Folder

colours.xml



#ffffdc7e #ffd3d3d3 #ff8dd304 #ff82e0ff #fffffbae #fff10800 #1A000000

integers.xml



2

IN Structure Folder

activity_list_view.xml

activity_main.xml