Back to project page gridview-extend.
The source code is released under:
Apache License
If you think the Android project gridview-extend listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package com.mcxiaoke.view; //from w w w. ja v a 2 s . c o m import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.GridView; import android.widget.LinearLayout; /** * User: mcxiaoke * Date: 13-8-25 * Time: ????2:38 */ public class GridViewExtend extends LinearLayout { public static final String LOG_TAG = GridViewExtend.class.getCanonicalName(); public static final boolean DEBUG = true; static final float FRICTION = 2.0f; public static final int SMOOTH_SCROLL_DURATION_MS = 200; public static final int SMOOTH_SCROLL_LONG_DURATION_MS = 325; static final int DEMO_SCROLL_INTERVAL = 225; private Context mContext; private LayoutInflater mInflater; private Resources mResources; private FrameLayout mRefreshableViewWrapper; private GridView mGridView; private LinearLayout mHeaderView; private int mTouchSlop; private float mLastMotionX, mLastMotionY; private float mInitialMotionX, mInitialMotionY; private boolean mLayoutVisibilityChangesEnabled; private boolean mIsBeingDragged = false; public GridViewExtend(Context context) { super(context); initialize(context); } public GridViewExtend(Context context, AttributeSet attrs) { super(context, attrs); initialize(context); } public GridViewExtend(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(context); } private void initialize(Context context) { setOrientation(LinearLayout.VERTICAL); setGravity(Gravity.CENTER); mContext = context; mInflater = LayoutInflater.from(mContext); mResources = mContext.getResources(); ViewConfiguration config = ViewConfiguration.get(context); mTouchSlop = config.getScaledTouchSlop(); mGridView = new GridView(mContext); mGridView.setNumColumns(3); mGridView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); mGridView.setId(android.R.id.list); addRefreshableView(mContext, mGridView); mHeaderView = (LinearLayout) mInflater.inflate(R.layout.gridview_header, null); addHeaderAndFooter(); } protected void addHeaderAndFooter() { // We need to use the correct LayoutParam values, based on scroll // direction final LayoutParams lp = getHeaderLayoutParams(); // Remove Header, and then add Header Loading View again if needed if (this == mHeaderView.getParent()) { removeView(mHeaderView); } addViewInternal(mHeaderView, 0, lp); // Hide Loading Views refreshHeaderAndFooter(); } /** * Re-measure the Loading Views height, and adjust internal padding as * necessary */ protected final void refreshHeaderAndFooter() { int pLeft = getPaddingLeft(); int pTop = getPaddingTop(); int pRight = getPaddingRight(); int pBottom = getPaddingBottom(); int headerHeight = mHeaderView.getMeasuredHeight(); // pTop = -headerHeight; if (DEBUG) { Log.e(LOG_TAG, "refreshHeaderAndFooter() headerHeight=" + headerHeight); Log.e(LOG_TAG, "refreshHeaderAndFooter() pLeft=" + pLeft + " pRight=" + pRight); Log.e(LOG_TAG, "refreshHeaderAndFooter() pTop=" + pTop + " pBottom=" + pBottom); Log.e(LOG_TAG, String.format("Setting Padding. L: %d, T: %d, R: %d, B: %d", pLeft, pTop, pRight, pBottom)); } setPadding(pLeft, pTop, pRight, pBottom); } private LayoutParams getHeaderLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { final int action = event.getAction(); if (DEBUG) { Log.v(LOG_TAG, "onInterceptTouchEvent event.action=" + event.getAction() + " mIsBeingDragged=" + mIsBeingDragged); } if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mIsBeingDragged = false; return false; } if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) { return true; } switch (action) { case MotionEvent.ACTION_MOVE: { if (DEBUG) { Log.v(LOG_TAG, "onInterceptTouchEvent() ACTION_MOVE mIsBeingDragged=" + mIsBeingDragged); } // If we're refreshing, and the flag is set. Eat all MOVE events // if sticky, return true; // if (!mScrollingWhileRefreshingEnabled && isRefreshing()) { // return true; // } final float y = event.getY(), x = event.getX(); final float diff, oppositeDiff, absDiff, absOppositeDiff; diff = y - mLastMotionY; oppositeDiff = x - mLastMotionX; absDiff = Math.abs(diff); absOppositeDiff = Math.abs(oppositeDiff); if (DEBUG) { Log.v(LOG_TAG, "onInterceptTouchEvent() ACTION_MOVE diff=" + diff + " oppositeDiff=" + oppositeDiff); } if (absDiff > mTouchSlop && absDiff > absOppositeDiff) { if (diff >= 1f) { mLastMotionY = y; mLastMotionX = x; mIsBeingDragged = true; } else if (diff <= -1f) { mLastMotionY = y; mLastMotionX = x; mIsBeingDragged = true; } } break; } case MotionEvent.ACTION_DOWN: { mLastMotionY = mInitialMotionY = event.getY(); mLastMotionX = mInitialMotionX = event.getX(); mIsBeingDragged = false; if (DEBUG) { Log.v(LOG_TAG, "onInterceptTouchEvent() ACTION_DOWN mLastMotionY=" + mLastMotionY + " mLastMotionX=" + mLastMotionX); } break; } } if (DEBUG) { Log.v(LOG_TAG, "onInterceptTouchEvent() return mIsBeingDragged=" + mIsBeingDragged); } return mIsBeingDragged; } @Override public final boolean onTouchEvent(MotionEvent event) { if (DEBUG) { Log.v(LOG_TAG, "onTouchEvent event.action=" + event.getAction() + " mIsBeingDragged=" + mIsBeingDragged); } // If we're refreshing, and the flag is set. Eat the event // if (!mScrollingWhileRefreshingEnabled && isRefreshing()) { // return true; // } if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_MOVE: { if (DEBUG) { Log.v(LOG_TAG, "onTouchEvent ACTION_MOVE mIsBeingDragged=" + mIsBeingDragged); } if (mIsBeingDragged) { mLastMotionY = event.getY(); mLastMotionX = event.getX(); Log.v(LOG_TAG, "onTouchEvent ACTION_MOVE mLastMotionY=" + mLastMotionY + " mLastMotionX=" + mLastMotionX); pullEvent(); return true; } Log.v(LOG_TAG, "onTouchEvent ACTION_MOVE mLastMotionY=" + mLastMotionY + " mLastMotionX=" + mLastMotionX); break; } case MotionEvent.ACTION_DOWN: { mLastMotionY = mInitialMotionY = event.getY(); mLastMotionX = mInitialMotionX = event.getX(); if (DEBUG) { Log.v(LOG_TAG, "onTouchEvent ACTION_DOWN mIsBeingDragged=" + mIsBeingDragged); Log.v(LOG_TAG, "onTouchEvent ACTION_DOWN mLastMotionY=" + mLastMotionY + " mLastMotionX=" + mLastMotionX); } return true; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { if (DEBUG) { Log.v(LOG_TAG, "onTouchEvent ACTION_UP mIsBeingDragged=" + mIsBeingDragged); Log.v(LOG_TAG, "onTouchEvent ACTION_DOWN mLastMotionY=" + mLastMotionY + " mLastMotionX=" + mLastMotionX); } if (mIsBeingDragged) { mIsBeingDragged = false; // If we're already refreshing, just scroll back to the top // smoothScrollTo(0); return true; } break; } } return false; } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if (DEBUG) { Log.d(LOG_TAG, "addView: " + child.getClass().getSimpleName()); } final GridView refreshableView = getRefreshableView(); if (refreshableView != null) { ((ViewGroup) refreshableView).addView(child, index, params); } else { throw new UnsupportedOperationException("Refreshable View is not a ViewGroup so can't addView"); } } public GridView getRefreshableView() { return mGridView; } private void addRefreshableView(Context context, GridView refreshableView) { mRefreshableViewWrapper = new FrameLayout(context); mRefreshableViewWrapper.addView(refreshableView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); addViewInternal(mRefreshableViewWrapper, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } /** * Used internally for adding view. Need because we override addView to * pass-through to the Refreshable View */ protected final void addViewInternal(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); } /** * Used internally for adding view. Need because we override addView to * pass-through to the Refreshable View */ protected final void addViewInternal(View child, ViewGroup.LayoutParams params) { super.addView(child, -1, params); } /** * Actions a Pull Event * * @return true if the Event has been handled, false if there has been no * change */ private void pullEvent() { final int newScrollValue; final int itemDimension = mHeaderView.getHeight(); ; final float initialMotionValue, lastMotionValue; initialMotionValue = mInitialMotionY; lastMotionValue = mLastMotionY; newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0)); if (DEBUG) { Log.v(LOG_TAG, "pullEvent() mInitialMotionY=" + mInitialMotionY + " mLastMotionY=" + mLastMotionY + " newScrollValue=" + newScrollValue); } setHeaderScroll(newScrollValue); } @Override protected final void onSizeChanged(int w, int h, int oldw, int oldh) { if (DEBUG) { Log.e(LOG_TAG, String.format("onSizeChanged. W: %d, H: %d", w, h)); } super.onSizeChanged(w, h, oldw, oldh); // We need to update the header/footer when our size changes refreshHeaderAndFooter(); // Update the Refreshable View layout refreshRefreshableViewSize(w, h); /** * As we're currently in a Layout Pass, we need to schedule another one * to layout any changes we've made here */ post(new Runnable() { @Override public void run() { requestLayout(); } }); } protected final void refreshRefreshableViewSize(int width, int height) { if (DEBUG) { Log.v(LOG_TAG, "refreshRefreshableViewSize() width=" + width + " height=" + height); } // We need to set the Height of the Refreshable View to the same as // this layout LayoutParams lp = (LayoutParams) mRefreshableViewWrapper.getLayoutParams(); if (lp.height != height) { lp.height = height; mRefreshableViewWrapper.requestLayout(); } } /** * Helper method which just calls scrollTo() in the correct scrolling * direction. * * @param value - New Scroll value */ protected final void setHeaderScroll(int value) { if (DEBUG) { Log.d(LOG_TAG, "setHeaderScroll: " + value); } if (mLayoutVisibilityChangesEnabled) { if (value < 0) { mHeaderView.setVisibility(View.VISIBLE); } else if (value > 0) { } else { mHeaderView.setVisibility(View.INVISIBLE); } } scrollTo(0, value); } /** * Smooth Scroll to position using the default duration of * {@value #SMOOTH_SCROLL_DURATION_MS} ms. * * @param scrollValue - Position to scroll to */ protected final void smoothScrollTo(int scrollValue) { smoothScrollTo(scrollValue, SMOOTH_SCROLL_DURATION_MS); } /** * Smooth Scroll to position using the default duration of * {@value #SMOOTH_SCROLL_DURATION_MS} ms. * * @param scrollValue - Position to scroll to * @param listener - Listener for scroll */ protected final void smoothScrollTo(int scrollValue, OnSmoothScrollFinishedListener listener) { smoothScrollTo(scrollValue, SMOOTH_SCROLL_DURATION_MS, 0, listener); } /** * Smooth Scroll to position using the specific duration * * @param scrollValue - Position to scroll to * @param duration - Duration of animation in milliseconds */ private final void smoothScrollTo(int scrollValue, long duration) { smoothScrollTo(scrollValue, duration, 0, null); } private final void smoothScrollTo(int newScrollValue, long duration, long delayMillis, OnSmoothScrollFinishedListener listener) { if (null != mCurrentSmoothScrollRunnable) { mCurrentSmoothScrollRunnable.stop(); } if (DEBUG) { Log.v(LOG_TAG, String.format("smoothScrollTo() newScrollValue:%1$d, duration:%2$d, delayMillis:%3$d", newScrollValue, duration, delayMillis)); } final int oldScrollValue = getScrollY(); if (oldScrollValue != newScrollValue) { if (null == mScrollAnimationInterpolator) { // Default interpolator is a Decelerate Interpolator mScrollAnimationInterpolator = new DecelerateInterpolator(); } mCurrentSmoothScrollRunnable = new SmoothScrollRunnable(oldScrollValue, newScrollValue, duration, listener); if (delayMillis > 0) { postDelayed(mCurrentSmoothScrollRunnable, delayMillis); } else { post(mCurrentSmoothScrollRunnable); } } } private Interpolator mScrollAnimationInterpolator; private SmoothScrollRunnable mCurrentSmoothScrollRunnable; final class SmoothScrollRunnable implements Runnable { private final int mScrollToY; private final int mScrollFromY; private final long mDuration; private OnSmoothScrollFinishedListener mListener; private boolean mContinueRunning = true; private long mStartTime = -1; private int mCurrentY = -1; public SmoothScrollRunnable(int fromY, int toY, long duration, OnSmoothScrollFinishedListener listener) { mScrollFromY = fromY; mScrollToY = toY; mDuration = duration; mListener = listener; } @Override public void run() { /** * Only set mStartTime if this is the first time we're starting, * else actually calculate the Y delta */ if (mStartTime == -1) { mStartTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce software float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - mStartTime)) / mDuration; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round(mScrollFromY - mScrollToY); mCurrentY = mScrollFromY - deltaY; setHeaderScroll(mCurrentY); } // If we're not at the target Y, keep going... if (mContinueRunning && mScrollToY != mCurrentY) { ViewCompat.postOnAnimation(GridViewExtend.this, this); } else { if (null != mListener) { mListener.onSmoothScrollFinished(); } } } public void stop() { mContinueRunning = false; removeCallbacks(this); } } static interface OnSmoothScrollFinishedListener { void onSmoothScrollFinished(); } }