package com.roboo.like.google.views;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.GridView;
import android.widget.ListAdapter;
import com.roboo.like.google.adapters.StickyGridHeadersBaseAdapter;
import com.roboo.like.google.adapters.StickyGridHeadersBaseAdapterWrapper;
import com.roboo.like.google.adapters.StickyGridHeadersBaseAdapterWrapper.HeaderFillerView;
import com.roboo.like.google.adapters.StickyHeadersAdapter;
import com.roboo.like.google.adapters.StickyGridHeadersSimpleAdapterWrapper;
/**
* 带有sticky功能的GridView
*
*/
public class StickyGridHeadersGridView extends GridView implements OnScrollListener, OnItemClickListener, OnItemSelectedListener, OnItemLongClickListener
{
private static final String ERROR_PLATFORM = "Error supporting platform " + Build.VERSION.SDK_INT + ".";
private static final int MATCHED_STICKIED_HEADER = -2;
private static final int NO_MATCHED_HEADER = -1;
protected static final int TOUCH_MODE_DONE_WAITING = 2;
protected static final int TOUCH_MODE_DOWN = 0;
protected static final int TOUCH_MODE_FINISHED_LONG_PRESS = -2;
protected static final int TOUCH_MODE_REST = -1;
protected static final int TOUCH_MODE_TAP = 1;
static final String TAG = StickyGridHeadersGridView.class.getSimpleName();
private static MotionEvent.PointerCoords[] getPointerCoords(MotionEvent e)
{
int n = e.getPointerCount();
MotionEvent.PointerCoords[] r = new MotionEvent.PointerCoords[n];
for (int i = 0; i < n; i++)
{
r[i] = new MotionEvent.PointerCoords();
e.getPointerCoords(i, r[i]);
}
return r;
}
private static int[] getPointerIds(MotionEvent e)
{
int n = e.getPointerCount();
int[] r = new int[n];
for (int i = 0; i < n; i++)
{
r[i] = e.getPointerId(i);
}
return r;
}
public CheckForHeaderLongPress mPendingCheckForLongPress;
public CheckForHeaderTap mPendingCheckForTap;
private boolean mAreHeadersSticky = true;
private final Rect mClippingRect = new Rect();
private boolean mClippingToPadding;
private boolean mClipToPaddingHasBeenSet;
private int mColumnWidth;
private long mCurrentHeaderId = -1;
private DataSetObserver mDataSetObserver = new DataSetObserver()
{
@Override
public void onChanged()
{
reset();
}
@Override
public void onInvalidated()
{
reset();
}
};
private int mHeaderBottomPosition;
private boolean mHeadersIgnorePadding;
private int mHorizontalSpacing;
private boolean mMaskStickyHeaderRegion = true;
private float mMotionY;
/**
* Must be set from the wrapped GridView in the constructor.
*/
private int mNumColumns;
private boolean mNumColumnsSet;
private int mNumMeasuredColumns = 1;
private OnHeaderClickListener mOnHeaderClickListener;
private OnHeaderLongClickListener mOnHeaderLongClickListener;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private OnItemSelectedListener mOnItemSelectedListener;
private PerformHeaderClick mPerformHeaderClick;
private OnScrollListener mScrollListener;
private int mScrollState = SCROLL_STATE_IDLE;
private View mStickiedHeader;
private Runnable mTouchModeReset;
private int mTouchSlop;
private int mVerticalSpacing;
protected StickyGridHeadersBaseAdapterWrapper mAdapter;
protected boolean mDataChanged;
protected int mMotionHeaderPosition;
protected int mTouchMode;
boolean mHeaderChildBeingPressed = false;
public StickyGridHeadersGridView(Context context)
{
this(context, null);
}
public StickyGridHeadersGridView(Context context, AttributeSet attrs)
{
this(context, attrs, android.R.attr.gridViewStyle);
}
public StickyGridHeadersGridView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
super.setOnScrollListener(this);
setVerticalFadingEdgeEnabled(false);
if (!mNumColumnsSet)
{
mNumColumns = AUTO_FIT;
}
ViewConfiguration vc = ViewConfiguration.get(context);
mTouchSlop = vc.getScaledTouchSlop();
}
public boolean areHeadersSticky()
{
return mAreHeadersSticky;
}
/**
* Gets the header at an item position. However, the position must be that
* of a HeaderFiller.
*
* @param position
* Position of HeaderFiller.
* @return Header View wrapped in HeaderFiller or null if no header was
* found.
*/
public View getHeaderAt(int position)
{
if (position == MATCHED_STICKIED_HEADER)
{
return mStickiedHeader;
}
try
{
return (View) getChildAt(position).getTag();
}
catch (Exception e)
{}
return null;
}
/**
* Get the currently stickied header.
*
* @return Current stickied header.
*/
public View getStickiedHeader()
{
return mStickiedHeader;
}
public boolean getStickyHeaderIsTranscluent()
{
return !mMaskStickyHeaderRegion;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
mOnItemClickListener.onItemClick(parent, view, mAdapter.translatePosition(position).mPosition, id);
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
return mOnItemLongClickListener.onItemLongClick(parent, view, mAdapter.translatePosition(position).mPosition, id);
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
mOnItemSelectedListener.onItemSelected(parent, view, mAdapter.translatePosition(position).mPosition, id);
}
@Override
public void onNothingSelected(AdapterView<?> parent)
{
mOnItemSelectedListener.onNothingSelected(parent);
}
@Override
public void onRestoreInstanceState(Parcelable state)
{
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mAreHeadersSticky = ss.areHeadersSticky;
requestLayout();
}
@Override
public Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.areHeadersSticky = mAreHeadersSticky;
return ss;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if (mScrollListener != null)
{
mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO)
{
scrollChanged(firstVisibleItem);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
{
if (mScrollListener != null)
{
mScrollListener.onScrollStateChanged(view, scrollState);
}
mScrollState = scrollState;
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
final int action = ev.getAction();
boolean wasHeaderChildBeingPressed = mHeaderChildBeingPressed;
if (mHeaderChildBeingPressed)
{
final View tempHeader = getHeaderAt(mMotionHeaderPosition);
final View headerHolder = mMotionHeaderPosition == MATCHED_STICKIED_HEADER ? tempHeader : getChildAt(mMotionHeaderPosition);
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)
{
mHeaderChildBeingPressed = false;
}
if (tempHeader != null)
{
tempHeader.dispatchTouchEvent(transformEvent(ev, mMotionHeaderPosition));
tempHeader.invalidate();
tempHeader.postDelayed(new Runnable()
{
public void run()
{
invalidate(0, headerHolder.getTop(), getWidth(), headerHolder.getTop() + headerHolder.getHeight());
}
}, ViewConfiguration.getPressedStateDuration());
invalidate(0, headerHolder.getTop(), getWidth(), headerHolder.getTop() + headerHolder.getHeight());
}
}
switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null)
{
mPendingCheckForTap = new CheckForHeaderTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
final int y = (int) ev.getY();
mMotionY = y;
mMotionHeaderPosition = findMotionHeader(y);
if (mMotionHeaderPosition == NO_MATCHED_HEADER || mScrollState == SCROLL_STATE_FLING)
{
// Don't consume the event and pass it to super because we
// can't handle it yet.
break;
}
else
{
View tempHeader = getHeaderAt(mMotionHeaderPosition);
if (tempHeader != null)
{
if (tempHeader.dispatchTouchEvent(transformEvent(ev, mMotionHeaderPosition)))
{
mHeaderChildBeingPressed = true;
tempHeader.setPressed(true);
}
tempHeader.invalidate();
if (mMotionHeaderPosition != MATCHED_STICKIED_HEADER)
{
tempHeader = getChildAt(mMotionHeaderPosition);
}
invalidate(0, tempHeader.getTop(), getWidth(), tempHeader.getTop() + tempHeader.getHeight());
}
}
mTouchMode = TOUCH_MODE_DOWN;
return true;
case MotionEvent.ACTION_MOVE:
if (mMotionHeaderPosition != NO_MATCHED_HEADER && Math.abs(ev.getY() - mMotionY) > mTouchSlop)
{
// Detected scroll initiation so cancel touch completion on
// header.
mTouchMode = TOUCH_MODE_REST;
// if (!mHeaderChildBeingPressed) {
final View header = getHeaderAt(mMotionHeaderPosition);
if (header != null)
{
header.setPressed(false);
header.invalidate();
}
final Handler handler = getHandler();
if (handler != null)
{
handler.removeCallbacks(mPendingCheckForLongPress);
}
mMotionHeaderPosition = NO_MATCHED_HEADER;
// }
}
break;
case MotionEvent.ACTION_UP:
if (mTouchMode == TOUCH_MODE_FINISHED_LONG_PRESS)
{
mTouchMode = TOUCH_MODE_REST;
return true;
}
if (mTouchMode == TOUCH_MODE_REST || mMotionHeaderPosition == NO_MATCHED_HEADER)
{
break;
}
final View header = getHeaderAt(mMotionHeaderPosition);
if (!wasHeaderChildBeingPressed)
{
if (header != null)
{
if (mTouchMode != TOUCH_MODE_DOWN)
{
header.setPressed(false);
}
if (mPerformHeaderClick == null)
{
mPerformHeaderClick = new PerformHeaderClick();
}
final PerformHeaderClick performHeaderClick = mPerformHeaderClick;
performHeaderClick.mClickMotionPosition = mMotionHeaderPosition;
performHeaderClick.rememberWindowAttachCount();
if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)
{
final Handler handler = getHandler();
if (handler != null)
{
handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress);
}
if (!mDataChanged)
{
/*
* Got here so must be a tap. The long press would
* have triggered on the callback handler.
*/
mTouchMode = TOUCH_MODE_TAP;
header.setPressed(true);
setPressed(true);
if (mTouchModeReset != null)
{
removeCallbacks(mTouchModeReset);
}
mTouchModeReset = new Runnable()
{
@Override
public void run()
{
mMotionHeaderPosition = NO_MATCHED_HEADER;
mTouchModeReset = null;
mTouchMode = TOUCH_MODE_REST;
header.setPressed(false);
setPressed(false);
header.invalidate();
invalidate(0, header.getTop(), getWidth(), header.getHeight());
if (!mDataChanged)
{
performHeaderClick.run();
}
}
};
postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration());
}
else
{
mTouchMode = TOUCH_MODE_REST;
}
}
else if (!mDataChanged)
{
performHeaderClick.run();
}
}
}
mTouchMode = TOUCH_MODE_REST;
return true;
}
return super.onTouchEvent(ev);
}
public boolean performHeaderClick(View view, long id)
{
if (mOnHeaderClickListener != null)
{
playSoundEffect(SoundEffectConstants.CLICK);
if (view != null)
{
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
mOnHeaderClickListener.onHeaderClick(this, view, id);
return true;
}
return false;
}
public boolean performHeaderLongPress(View view, long id)
{
boolean handled = false;
if (mOnHeaderLongClickListener != null)
{
handled = mOnHeaderLongClickListener.onHeaderLongClick(this, view, id);
}
if (handled)
{
if (view != null)
{
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
}
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
@Override
public void setAdapter(ListAdapter adapter)
{
if (mAdapter != null && mDataSetObserver != null)
{
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
if (!mClipToPaddingHasBeenSet)
{
mClippingToPadding = true;
}
StickyGridHeadersBaseAdapter baseAdapter = null;
if (adapter instanceof StickyGridHeadersBaseAdapter)
{
baseAdapter = (StickyGridHeadersBaseAdapter) adapter;
}
else if (adapter instanceof StickyHeadersAdapter)
{
baseAdapter = new StickyGridHeadersSimpleAdapterWrapper((StickyHeadersAdapter) adapter);
}
else
{
// Wrap up a list adapter so it is an adapter with zero headers.
// baseAdapter = new StickyGridHeadersListAdapterWrapper(adapter);
}
this.mAdapter = new StickyGridHeadersBaseAdapterWrapper(getContext(), this, baseAdapter);
this.mAdapter.registerDataSetObserver(mDataSetObserver);
reset();
super.setAdapter(this.mAdapter);
}
public void setAreHeadersSticky(boolean useStickyHeaders)
{
if (useStickyHeaders != mAreHeadersSticky)
{
mAreHeadersSticky = useStickyHeaders;
requestLayout();
}
}
@Override
public void setClipToPadding(boolean clipToPadding)
{
super.setClipToPadding(clipToPadding);
mClippingToPadding = clipToPadding;
mClipToPaddingHasBeenSet = true;
}
@Override
public void setColumnWidth(int columnWidth)
{
super.setColumnWidth(columnWidth);
mColumnWidth = columnWidth;
}
/**
* If set to true, headers will ignore horizontal padding.
*
* @param b
* if true, horizontal padding is ignored by headers
*/
public void setHeadersIgnorePadding(boolean b)
{
mHeadersIgnorePadding = b;
}
@Override
public void setHorizontalSpacing(int horizontalSpacing)
{
super.setHorizontalSpacing(horizontalSpacing);
mHorizontalSpacing = horizontalSpacing;
}
@Override
public void setNumColumns(int numColumns)
{
super.setNumColumns(numColumns);
mNumColumnsSet = true;
this.mNumColumns = numColumns;
if (numColumns != AUTO_FIT && mAdapter != null)
{
mAdapter.setNumColumns(numColumns);
}
}
public void setOnHeaderClickListener(OnHeaderClickListener listener)
{
mOnHeaderClickListener = listener;
}
public void setOnHeaderLongClickListener(OnHeaderLongClickListener listener)
{
if (!isLongClickable())
{
setLongClickable(true);
}
mOnHeaderLongClickListener = listener;
}
@Override
public void setOnItemClickListener(android.widget.AdapterView.OnItemClickListener listener)
{
this.mOnItemClickListener = listener;
super.setOnItemClickListener(this);
}
@Override
public void setOnItemLongClickListener(android.widget.AdapterView.OnItemLongClickListener listener)
{
this.mOnItemLongClickListener = listener;
super.setOnItemLongClickListener(this);
}
@Override
public void setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener listener)
{
this.mOnItemSelectedListener = listener;
super.setOnItemSelectedListener(this);
}
@Override
public void setOnScrollListener(OnScrollListener listener)
{
this.mScrollListener = listener;
}
public void setStickyHeaderIsTranscluent(boolean isTranscluent)
{
mMaskStickyHeaderRegion = !isTranscluent;
}
@Override
public void setVerticalSpacing(int verticalSpacing)
{
super.setVerticalSpacing(verticalSpacing);
mVerticalSpacing = verticalSpacing;
}
private int findMotionHeader(float y)
{
if (mStickiedHeader != null && y <= mHeaderBottomPosition)
{
return MATCHED_STICKIED_HEADER;
}
int vi = 0;
for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition();)
{
long id = getItemIdAtPosition(i);
if (id == StickyGridHeadersBaseAdapterWrapper.ID_HEADER)
{
View headerWrapper = getChildAt(vi);
int bottom = headerWrapper.getBottom();
int top = headerWrapper.getTop();
if (y <= bottom && y >= top)
{
return vi;
}
}
i += mNumMeasuredColumns;
vi += mNumMeasuredColumns;
}
return NO_MATCHED_HEADER;
}
private int getHeaderHeight()
{
if (mStickiedHeader != null)
{
return mStickiedHeader.getMeasuredHeight();
}
return 0;
}
private long headerViewPositionToId(int pos)
{
if (pos == MATCHED_STICKIED_HEADER)
{
return mCurrentHeaderId;
}
return mAdapter.getHeaderId(getFirstVisiblePosition() + pos);
}
private void measureHeader()
{
if (mStickiedHeader == null)
{
return;
}
int widthMeasureSpec;
if (mHeadersIgnorePadding)
{
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
}
else
{
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
}
int heightMeasureSpec = 0;
ViewGroup.LayoutParams params = mStickiedHeader.getLayoutParams();
if (params != null && params.height > 0)
{
heightMeasureSpec = MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY);
}
else
{
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
mStickiedHeader.measure(MeasureSpec.makeMeasureSpec(0, 0), MeasureSpec.makeMeasureSpec(0, 0));
mStickiedHeader.measure(widthMeasureSpec, heightMeasureSpec);
if (mHeadersIgnorePadding)
{
mStickiedHeader.layout(getLeft(), 0, getRight(), mStickiedHeader.getMeasuredHeight());
}
else
{
mStickiedHeader.layout(getLeft() + getPaddingLeft(), 0, getRight() - getPaddingRight(), mStickiedHeader.getMeasuredHeight());
}
}
private void reset()
{
mHeaderBottomPosition = 0;
swapStickiedHeader(null);
mCurrentHeaderId = INVALID_ROW_ID;
}
private void scrollChanged(int firstVisibleItem)
{
if (mAdapter == null || mAdapter.getCount() == 0 || !mAreHeadersSticky)
{
return;
}
View firstItem = getChildAt(0);
if (firstItem == null)
{
return;
}
long newHeaderId;
int selectedHeaderPosition = firstVisibleItem;
int beforeRowPosition = firstVisibleItem - mNumMeasuredColumns;
if (beforeRowPosition < 0)
{
beforeRowPosition = firstVisibleItem;
}
int secondRowPosition = firstVisibleItem + mNumMeasuredColumns;
if (secondRowPosition >= mAdapter.getCount())
{
secondRowPosition = firstVisibleItem;
}
if (mVerticalSpacing == 0)
{
newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
}
else if (mVerticalSpacing < 0)
{
newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
View firstSecondRowView = getChildAt(mNumMeasuredColumns);
if (firstSecondRowView.getTop() <= 0)
{
newHeaderId = mAdapter.getHeaderId(secondRowPosition);
selectedHeaderPosition = secondRowPosition;
}
else
{
newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
}
}
else
{
int margin = getChildAt(0).getTop();
if (0 < margin && margin < mVerticalSpacing)
{
newHeaderId = mAdapter.getHeaderId(beforeRowPosition);
selectedHeaderPosition = beforeRowPosition;
}
else
{
newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
}
}
if (mCurrentHeaderId != newHeaderId)
{
swapStickiedHeader(mAdapter.getHeaderView(selectedHeaderPosition, mStickiedHeader, this));
measureHeader();
mCurrentHeaderId = newHeaderId;
}
final int childCount = getChildCount();
if (childCount != 0)
{
View viewToWatch = null;
int watchingChildDistance = 99999;
// Find the next header after the stickied one.
for (int i = 0; i < childCount; i += mNumMeasuredColumns)
{
View child = super.getChildAt(i);
int childDistance;
if (mClippingToPadding)
{
childDistance = child.getTop() - getPaddingTop();
}
else
{
childDistance = child.getTop();
}
if (childDistance < 0)
{
continue;
}
if (mAdapter.getItemId(getPositionForView(child)) == StickyGridHeadersBaseAdapterWrapper.ID_HEADER && childDistance < watchingChildDistance)
{
viewToWatch = child;
watchingChildDistance = childDistance;
}
}
int headerHeight = getHeaderHeight();
// Work out where to draw stickied header using synchronised
// scrolling.
if (viewToWatch != null)
{
if (firstVisibleItem == 0 && super.getChildAt(0).getTop() > 0 && !mClippingToPadding)
{
mHeaderBottomPosition = 0;
}
else
{
if (mClippingToPadding)
{
mHeaderBottomPosition = Math.min(viewToWatch.getTop(), headerHeight + getPaddingTop());
mHeaderBottomPosition = mHeaderBottomPosition < getPaddingTop() ? headerHeight + getPaddingTop() : mHeaderBottomPosition;
}
else
{
mHeaderBottomPosition = Math.min(viewToWatch.getTop(), headerHeight);
mHeaderBottomPosition = mHeaderBottomPosition < 0 ? headerHeight : mHeaderBottomPosition;
}
}
}
else
{
mHeaderBottomPosition = headerHeight;
if (mClippingToPadding)
{
mHeaderBottomPosition += getPaddingTop();
}
}
}
}
private void swapStickiedHeader(View newStickiedHeader)
{
detachHeader(mStickiedHeader);
attachHeader(newStickiedHeader);
mStickiedHeader = newStickiedHeader;
}
private MotionEvent transformEvent(MotionEvent e, int headerPosition)
{
if (headerPosition == MATCHED_STICKIED_HEADER)
{
return e;
}
long downTime = e.getDownTime();
long eventTime = e.getEventTime();
int action = e.getAction();
int pointerCount = e.getPointerCount();
int[] pointerIds = getPointerIds(e);
MotionEvent.PointerCoords[] pointerCoords = getPointerCoords(e);
int metaState = e.getMetaState();
float xPrecision = e.getXPrecision();
float yPrecision = e.getYPrecision();
int deviceId = e.getDeviceId();
int edgeFlags = e.getEdgeFlags();
int source = e.getSource();
int flags = e.getFlags();
View headerHolder = getChildAt(headerPosition);
for (int i = 0; i < pointerCount; i++)
{
pointerCoords[i].y -= headerHolder.getTop();
}
MotionEvent n = MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerIds, pointerCoords, metaState, xPrecision, yPrecision, deviceId, edgeFlags, source, flags);
return n;
}
@Override
protected void dispatchDraw(Canvas canvas)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO)
{
scrollChanged(getFirstVisiblePosition());
}
boolean drawStickiedHeader = mStickiedHeader != null && mAreHeadersSticky && mStickiedHeader.getVisibility() == View.VISIBLE;
int headerHeight = getHeaderHeight();
int top = mHeaderBottomPosition - headerHeight;
// Mask the region where we will draw the header later, but only if we
// will draw a header and masking is requested.
if (drawStickiedHeader && mMaskStickyHeaderRegion)
{
if (mHeadersIgnorePadding)
{
mClippingRect.left = 0;
mClippingRect.right = getWidth();
}
else
{
mClippingRect.left = getPaddingLeft();
mClippingRect.right = getWidth() - getPaddingRight();
}
mClippingRect.top = mHeaderBottomPosition;
mClippingRect.bottom = getHeight();
canvas.save();
canvas.clipRect(mClippingRect);
}
// ...and draw the grid view.
super.dispatchDraw(canvas);
// Find headers.
List<Integer> headerPositions = new ArrayList<Integer>();
int vi = 0;
for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition();)
{
long id = getItemIdAtPosition(i);
if (id == StickyGridHeadersBaseAdapterWrapper.ID_HEADER)
{
headerPositions.add(vi);
}
i += mNumMeasuredColumns;
vi += mNumMeasuredColumns;
}
// Draw headers in list.
for (int i = 0; i < headerPositions.size(); i++)
{
View frame = getChildAt(headerPositions.get(i));
View header;
try
{
header = (View) frame.getTag();
}
catch (Exception e)
{
return;
}
boolean headerIsStickied = ((HeaderFillerView) frame).getHeaderId() == mCurrentHeaderId && frame.getTop() < 0 && mAreHeadersSticky;
if (header.getVisibility() != View.VISIBLE || headerIsStickied)
{
continue;
}
int widthMeasureSpec;
if (mHeadersIgnorePadding)
{
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
}
else
{
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
}
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
header.measure(MeasureSpec.makeMeasureSpec(0, 0), MeasureSpec.makeMeasureSpec(0, 0));
header.measure(widthMeasureSpec, heightMeasureSpec);
if (mHeadersIgnorePadding)
{
header.layout(getLeft(), 0, getRight(), frame.getHeight());
}
else
{
header.layout(getLeft() + getPaddingLeft(), 0, getRight() - getPaddingRight(), frame.getHeight());
}
if (mHeadersIgnorePadding)
{
mClippingRect.left = 0;
mClippingRect.right = getWidth();
}
else
{
mClippingRect.left = getPaddingLeft();
mClippingRect.right = getWidth() - getPaddingRight();
}
mClippingRect.bottom = frame.getBottom();
mClippingRect.top = frame.getTop();
canvas.save();
canvas.clipRect(mClippingRect);
if (mHeadersIgnorePadding)
{
canvas.translate(0, frame.getTop());
}
else
{
canvas.translate(getPaddingLeft(), frame.getTop());
}
header.draw(canvas);
canvas.restore();
}
if (drawStickiedHeader && mMaskStickyHeaderRegion)
{
canvas.restore();
}
else if (!drawStickiedHeader)
{
// Done.
return;
}
// Draw stickied header.
int wantedWidth;
if (mHeadersIgnorePadding)
{
wantedWidth = getWidth();
}
else
{
wantedWidth = getWidth() - getPaddingLeft() - getPaddingRight();
}
if (mStickiedHeader.getWidth() != wantedWidth)
{
int widthMeasureSpec;
if (mHeadersIgnorePadding)
{
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
}
else
{
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY); // Bug
// here
}
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
mStickiedHeader.measure(MeasureSpec.makeMeasureSpec(0, 0), MeasureSpec.makeMeasureSpec(0, 0));
mStickiedHeader.measure(widthMeasureSpec, heightMeasureSpec);
if (mHeadersIgnorePadding)
{
mStickiedHeader.layout(getLeft(), 0, getRight(), mStickiedHeader.getHeight());
}
else
{
mStickiedHeader.layout(getLeft() + getPaddingLeft(), 0, getRight() - getPaddingRight(), mStickiedHeader.getHeight());
}
}
if (mHeadersIgnorePadding)
{
mClippingRect.left = 0;
mClippingRect.right = getWidth();
}
else
{
mClippingRect.left = getPaddingLeft();
mClippingRect.right = getWidth() - getPaddingRight();
}
mClippingRect.bottom = top + headerHeight;
if (mClippingToPadding)
{
mClippingRect.top = getPaddingTop();
}
else
{
mClippingRect.top = 0;
}
canvas.save();
canvas.clipRect(mClippingRect);
if (mHeadersIgnorePadding)
{
canvas.translate(0, top);
}
else
{
canvas.translate(getPaddingLeft(), top);
}
if (mHeaderBottomPosition != headerHeight)
{
canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(), 255 * mHeaderBottomPosition / headerHeight, Canvas.ALL_SAVE_FLAG);
}
mStickiedHeader.draw(canvas);
if (mHeaderBottomPosition != headerHeight)
{
canvas.restore();
}
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (mNumColumns == AUTO_FIT)
{
int numFittedColumns;
if (mColumnWidth > 0)
{
int gridWidth = Math.max(MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(), 0);
numFittedColumns = gridWidth / mColumnWidth;
// Calculate measured columns accounting for requested grid
// spacing.
if (numFittedColumns > 0)
{
while (numFittedColumns != 1)
{
if (numFittedColumns * mColumnWidth + (numFittedColumns - 1) * mHorizontalSpacing > gridWidth)
{
numFittedColumns--;
}
else
{
break;
}
}
}
else
{
// Could not fit any columns in grid width, so default to a
// single column.
numFittedColumns = 1;
}
}
else
{
// Mimic vanilla GridView behaviour where there is not enough
// information to auto-fit columns.
numFittedColumns = 2;
}
mNumMeasuredColumns = numFittedColumns;
}
else
{
// There were some number of columns requested so we will try to
// fulfil the request.
mNumMeasuredColumns = mNumColumns;
}
// Update adapter with number of columns.
if (mAdapter != null)
{
mAdapter.setNumColumns(mNumMeasuredColumns);
}
measureHeader();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void attachHeader(View header)
{
if (header == null)
{
return;
}
try
{
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
attachInfoField.setAccessible(true);
Method method = View.class.getDeclaredMethod("dispatchAttachedToWindow", Class.forName("android.view.View$AttachInfo"), Integer.TYPE);
method.setAccessible(true);
method.invoke(header, attachInfoField.get(this), View.GONE);
}
catch (NoSuchMethodException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (ClassNotFoundException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (IllegalArgumentException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (IllegalAccessException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (InvocationTargetException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (NoSuchFieldException e)
{
throw new RuntimePlatformSupportException(e);
}
}
public void detachHeader(View header)
{
if (header == null)
{
return;
}
try
{
Method method = View.class.getDeclaredMethod("dispatchDetachedFromWindow");
method.setAccessible(true);
method.invoke(header);
}
catch (NoSuchMethodException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (IllegalArgumentException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (IllegalAccessException e)
{
throw new RuntimePlatformSupportException(e);
}
catch (InvocationTargetException e)
{
throw new RuntimePlatformSupportException(e);
}
}
public interface OnHeaderClickListener
{
void onHeaderClick(AdapterView<?> parent, View view, long id);
}
public interface OnHeaderLongClickListener
{
boolean onHeaderLongClick(AdapterView<?> parent, View view, long id);
}
private class CheckForHeaderLongPress extends WindowRunnable implements Runnable
{
@Override
public void run()
{
final View child = getHeaderAt(mMotionHeaderPosition);
if (child != null)
{
final long longPressId = headerViewPositionToId(mMotionHeaderPosition);
boolean handled = false;
if (sameWindow() && !mDataChanged)
{
handled = performHeaderLongPress(child, longPressId);
}
if (handled)
{
mTouchMode = TOUCH_MODE_FINISHED_LONG_PRESS;
setPressed(false);
child.setPressed(false);
}
else
{
mTouchMode = TOUCH_MODE_DONE_WAITING;
}
}
}
}
private class PerformHeaderClick extends WindowRunnable implements Runnable
{
int mClickMotionPosition;
@Override
public void run()
{
// The data has changed since we posted this action to the event
// queue, bail out before bad things happen.
if (mDataChanged)
return;
if (mAdapter != null && mAdapter.getCount() > 0 && mClickMotionPosition != INVALID_POSITION && mClickMotionPosition < mAdapter.getCount() && sameWindow())
{
final View view = getHeaderAt(mClickMotionPosition);
// If there is no view then something bad happened, the view
// probably scrolled off the screen, and we should cancel the
// click.
if (view != null)
{
performHeaderClick(view, headerViewPositionToId(mClickMotionPosition));
}
}
}
}
/**
* A base class for Runnables that will check that their view is still
* attached to the original window as when the Runnable was created.
*/
private class WindowRunnable
{
private int mOriginalAttachCount;
public void rememberWindowAttachCount()
{
mOriginalAttachCount = getWindowAttachCount();
}
public boolean sameWindow()
{
return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
}
}
final class CheckForHeaderTap implements Runnable
{
@Override
public void run()
{
if (mTouchMode == TOUCH_MODE_DOWN)
{
mTouchMode = TOUCH_MODE_TAP;
final View header = getHeaderAt(mMotionHeaderPosition);
if (header != null && !mHeaderChildBeingPressed)
{
if (!mDataChanged)
{
header.setPressed(true);
setPressed(true);
refreshDrawableState();
final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
final boolean longClickable = isLongClickable();
if (longClickable)
{
if (mPendingCheckForLongPress == null)
{
mPendingCheckForLongPress = new CheckForHeaderLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress, longPressTimeout);
}
else
{
mTouchMode = TOUCH_MODE_DONE_WAITING;
}
}
else
{
mTouchMode = TOUCH_MODE_DONE_WAITING;
}
}
}
}
}
class RuntimePlatformSupportException extends RuntimeException
{
private static final long serialVersionUID = -6512098808936536538L;
public RuntimePlatformSupportException(Exception e)
{
super(ERROR_PLATFORM, e);
}
}
/**
* Constructor called from {@link #CREATOR}
*/
static class SavedState extends BaseSavedState
{
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
{
@Override
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
boolean areHeadersSticky;
public SavedState(Parcelable superState)
{
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in)
{
super(in);
areHeadersSticky = in.readByte() != 0;
}
@Override
public String toString()
{
return "StickyGridHeadersGridView.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " areHeadersSticky=" + areHeadersSticky + "}";
}
@Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeByte((byte) (areHeadersSticky ? 1 : 0));
}
}
}