package com.mm.android.deviceaddmodule.mobilecommon.widget.sticky.stickygridheaders;
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 java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* GridView that displays items in sections with headers that stick to the top of the view.
*
*/
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 mMaskStickxinzaieaderRegion = 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 getStickxinzaieaderIsTranscluent() {
return !mMaskStickxinzaieaderRegion;
}
@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;
try {
super.onRestoreInstanceState(ss.getSuperState());
}catch (Exception e){
e.printStackTrace();
}
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;
if (adapter instanceof StickyGridHeadersBaseAdapter) {
baseAdapter = (StickyGridHeadersBaseAdapter) adapter;
} else if (adapter instanceof StickyGridHeadersSimpleAdapter) {
// Wrap up simple adapter to auto-generate the data we need.
baseAdapter = new StickyGridHeadersSimpleAdapterWrapper((StickyGridHeadersSimpleAdapter) 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 int translatePosition(int position) {
return mAdapter.translatePosition(position).mPosition;
}
public void setAreHeadersSticky(boolean useStickxinzaieaders) {
if (useStickxinzaieaders != mAreHeadersSticky) {
mAreHeadersSticky = useStickxinzaieaders;
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(OnItemClickListener listener) {
this.mOnItemClickListener = listener;
super.setOnItemClickListener(this);
}
@Override
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
this.mOnItemLongClickListener = listener;
super.setOnItemLongClickListener(this);
}
@Override
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
this.mOnItemSelectedListener = listener;
super.setOnItemSelectedListener(this);
}
@Override
public void setOnScrollListener(OnScrollListener listener) {
this.mScrollListener = listener;
}
public void setStickxinzaieaderIsTranscluent(boolean isTranscluent) {
mMaskStickxinzaieaderRegion = !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, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
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 && mMaskStickxinzaieaderRegion) {
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<>();
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 = ((StickyGridHeadersBaseAdapterWrapper.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, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
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 && mMaskStickxinzaieaderRegion) {
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, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
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);
}
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);
}
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);
}
}
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 Creator<SavedState> CREATOR = new 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));
}
}
}