Android Open Source - gridview-extend Grid View Extend






From Project

Back to project page gridview-extend.

License

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.

Java Source Code

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


}




Java Source Code List

com.mcxiaoke.view.GridViewExtendDemo.java
com.mcxiaoke.view.GridViewExtend.java
com.mcxiaoke.view.ViewCompat.java