原理和listview一样 ,都是重写android原生控件
Activity
package com.example.refreshgridview; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.widget.GridView; import android.widget.Toast; import com.example.refreshgridview.PullToRefreshBase.OnRefreshListener; public class MainActivity extends Activity { private PullToRefreshGridView mPullRefreshGridView; private GridView mGridView; private GridViewAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPullRefreshGridView = (PullToRefreshGridView) findViewById(R.id.video_gridView); mPullRefreshGridView.init(PullToRefreshGridView.MODE_BOTH); mGridView = mPullRefreshGridView.getRefreshableView(); mPullRefreshGridView.setOnRefreshListener(refreshListener); List<String> list = new ArrayList<String>(); for (int i = 0; i < 40; i++) { list.add(i+""); } adapter = new GridViewAdapter(MainActivity.this,list); mGridView.setAdapter(adapter); } private OnRefreshListener refreshListener = new OnRefreshListener() { @Override public void onRefresh(int mode) { if (PullToRefreshGridView.MODE_PULL_DOWN_TO_REFRESH == mPullRefreshGridView.getCurrentMode()) { Toast.makeText(MainActivity.this, "下拉刷新", Toast.LENGTH_SHORT).show(); mPullRefreshGridView.onRefreshComplete(); } else if (mode == PullToRefreshGridView.MODE_PULL_UP_TO_REFRESH) { // 加载更多 Toast.makeText(MainActivity.this, "上拉加载更多", Toast.LENGTH_SHORT).show(); mPullRefreshGridView.onRefreshComplete(); } } }; }
adapter
package com.example.refreshgridview; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.content.Intent; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; public class GridViewAdapter extends BaseAdapter { private List<String> mList = new ArrayList<String>(); private Context mContext; public GridViewAdapter(Context context,List<String> list) { super(); this.mContext = context; this.mList = list; } @Override public int getCount() { return mList.size(); } @Override public String getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ChildHolderOne holder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.item_grid_live_show, parent, false); holder = new ChildHolderOne(); holder.tvTitle = (TextView)convertView.findViewById(R.id.title_tv); convertView.setTag(holder); } else { holder = (ChildHolderOne) convertView.getTag(); } return convertView; } class ChildHolderOne { TextView tvTitle; } }
PullToRefreshGridView
package com.example.refreshgridview; import android.content.Context; import android.util.AttributeSet; import android.view.ContextMenu.ContextMenuInfo; import android.view.View; import android.widget.GridView; public class PullToRefreshGridView extends PullToRefreshAdapterViewBase<GridView> { class InternalGridView extends GridView implements EmptyViewMethodAccessor { public InternalGridView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void setEmptyView(View emptyView) { PullToRefreshGridView.this.setEmptyView(emptyView); } @Override public void setEmptyViewInternal(View emptyView) { super.setEmptyView(emptyView); } @Override public ContextMenuInfo getContextMenuInfo() { return super.getContextMenuInfo(); } } public PullToRefreshGridView(Context context) { super(context); } public PullToRefreshGridView(Context context, int mode) { super(context, mode); } public PullToRefreshGridView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected final GridView createRefreshableView(Context context, AttributeSet attrs) { GridView gv = new InternalGridView(context, attrs); // Use Generated ID (from res/values/ids.xml) gv.setId(R.id.gridview); return gv; } @Override public ContextMenuInfo getContextMenuInfo() { return ((InternalGridView) getRefreshableView()).getContextMenuInfo(); } }
PullToRefreshBase
package com.example.refreshgridview; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.LinearLayout; import android.widget.TextView; /** * * @author zlw */ public abstract class PullToRefreshBase<T extends View> extends LinearLayout { final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long startTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Override public void run() { /** * Only set startTime if this is the first time we're starting, else * actually calculate the Y delta */ if (startTime == -1) { startTime = 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() - startTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math .round((scrollFromY - scrollToY) * interpolator .getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the target Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final float FRICTION = 2.0f; static final int PULL_TO_REFRESH = 0x0; static final int RELEASE_TO_REFRESH = 0x1; static final int REFRESHING = 0x2; static final int MANUAL_REFRESHING = 0x3; public static final int MODE_PULL_DOWN_TO_REFRESH = 0x1; public static final int MODE_PULL_UP_TO_REFRESH = 0x2; public static final int MODE_BOTH = 0x3; // =========================================================== // Fields // =========================================================== private int touchSlop; private float initialMotionY; private float lastMotionX; private float lastMotionY; private boolean isBeingDragged = false; private int state = PULL_TO_REFRESH; private int mode = MODE_PULL_UP_TO_REFRESH; private int currentMode; private boolean disableScrollingWhileRefreshing = true; T refreshableView; private boolean isPullToRefreshEnabled = true; private LoadingLayout headerLayout; private LoadingLayout footerLayout; private int headerHeight; private final Handler handler = new Handler(); private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { super(context); init(context, null); } public PullToRefreshBase(Context context, int mode) { super(context); this.mode = mode; init(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Deprecated. Use {@link #getRefreshableView()} from now on. * * @deprecated * @return The Refreshable View which is currently wrapped */ public final T getAdapterView() { return refreshableView; } /** * Get the Wrapped Refreshable View. Anything returned here has already been * added to the content view. * * @return The View which is currently wrapped */ public final T getRefreshableView() { return refreshableView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public final boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Returns whether the widget has disabled scrolling on the Refreshable View * while refreshing. * * @return true if the widget has disabled scrolling while refreshing */ public final boolean isDisableScrollingWhileRefreshing() { return disableScrollingWhileRefreshing; } /** * Returns whether the Widget is currently in the Refreshing state * * @return true if the Widget is currently refreshing */ public final boolean isRefreshing() { return state == REFRESHING || state == MANUAL_REFRESHING; } /** * By default the Widget disabled scrolling on the Refreshable View while * refreshing. This method can change this behaviour. * * @param disableScrollingWhileRefreshing * - true if you want to disable scrolling while refreshing */ public final void setDisableScrollingWhileRefreshing( boolean disableScrollingWhileRefreshing) { this.disableScrollingWhileRefreshing = disableScrollingWhileRefreshing; } /** * Mark the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public final void onRefreshComplete() { if (state != PULL_TO_REFRESH) { resetHeader(); if (onShowLayoutListener != null) { onShowLayoutListener.onDismiss(); } } } /** * Set OnRefreshListener for the Widget * * @param listener * - Listener to be used when the Widget is set to Refresh */ public final void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * auto load headerLayout to refresh * * @param listener */ public final void setFirstAutoPullUpToRefresh(OnRefreshListener listener) { setRefreshingInternal(true, MODE_PULL_DOWN_TO_REFRESH); listener.onRefresh(MODE_PULL_DOWN_TO_REFRESH); } /** * set refreshLable , default use null * * @param pullLabel * @param releaseLabel * @param refreshingLabel */ public void setRefreshLabel(String pullLabel, String releaseLabel, String refreshingLabel) { if (pullLabel != null) { setPullLabel(pullLabel); } if (releaseLabel != null) { setReleaseLabel(releaseLabel); } if (refreshingLabel != null) { setRefreshingLabel(refreshingLabel); } } /** * A mutator to enable/disable Pull-to-Refresh for the current View * * @param enable * Whether Pull-To-Refresh should be used */ public final void setPullToRefreshEnabled(boolean enable) { this.isPullToRefreshEnabled = enable; } /** * Set Text to show when the Widget is being pulled, and will refresh when * released * * @param releaseLabel * - String to display */ private void setReleaseLabel(String releaseLabel) { if (null != headerLayout) { headerLayout.setReleaseLabel(releaseLabel); } if (null != footerLayout) { footerLayout.setReleaseLabel(releaseLabel); } } /** * Set Text to show when the Widget is being Pulled * * @param pullLabel * - String to display */ private void setPullLabel(String pullLabel) { if (null != headerLayout) { headerLayout.setPullLabel(pullLabel); } if (null != footerLayout) { footerLayout.setPullLabel(pullLabel); } } /** * Set Text to show when the Widget is refreshing * * @param refreshingLabel * - String to display */ private void setRefreshingLabel(String refreshingLabel) { if (null != headerLayout) { headerLayout.setRefreshingLabel(refreshingLabel); } if (null != footerLayout) { footerLayout.setRefreshingLabel(refreshingLabel); } } public final void setRefreshing() { this.setRefreshing(true); } /** * Sets the Widget to be in the refresh state. The UI will be updated to * show the 'Refreshing' view. * * @param doScroll * - true if you want to force a scroll to the Refreshing view. */ public final void setRefreshing(boolean doScroll) { if (!isRefreshing()) { setRefreshingInternal(doScroll); state = MANUAL_REFRESHING; } } public final boolean hasPullFromTop() { return currentMode != MODE_PULL_UP_TO_REFRESH; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public final boolean onTouchEvent(MotionEvent event) { if (!isPullToRefreshEnabled) { return false; } if (isRefreshing() && disableScrollingWhileRefreshing) { return true; } if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_MOVE: { if (isBeingDragged) { if (Math.abs(event.getY() - downLocation) > 5 && onShowLayoutListener != null) { onShowLayoutListener.onShow(); } lastMotionY = event.getY(); this.pullEvent(); return true; } break; } case MotionEvent.ACTION_DOWN: { if (isReadyForPull()) { downLocation = event.getY(); lastMotionY = initialMotionY = event.getY(); return true; } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { if (isBeingDragged) { isBeingDragged = false; if (state == RELEASE_TO_REFRESH && null != onRefreshListener) { setRefreshingInternal(true); onRefreshListener.onRefresh(currentMode); } else { smoothScrollTo(0); if (onShowLayoutListener != null) { onShowLayoutListener.onDismiss(); } } return true; } break; } } return false; } // remeber to down location private float downLocation = 0; @Override public final boolean onInterceptTouchEvent(MotionEvent event) { if (!isPullToRefreshEnabled) { return false; } if (isRefreshing() && disableScrollingWhileRefreshing) { return true; } final int action = event.getAction(); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { isBeingDragged = false; return false; } if (action != MotionEvent.ACTION_DOWN && isBeingDragged) { return true; } switch (action) { case MotionEvent.ACTION_MOVE: { if (isReadyForPull()) { final float y = event.getY(); final float dy = y - lastMotionY; final float yDiff = Math.abs(dy); final float xDiff = Math.abs(event.getX() - lastMotionX); if (yDiff > touchSlop && yDiff > xDiff) { if ((mode == MODE_PULL_DOWN_TO_REFRESH || mode == MODE_BOTH) && dy >= 0.0001f && isReadyForPullDown()) { lastMotionY = y; isBeingDragged = true; if (mode == MODE_BOTH) { currentMode = MODE_PULL_DOWN_TO_REFRESH; } } else if ((mode == MODE_PULL_UP_TO_REFRESH || mode == MODE_BOTH) && dy <= 0.0001f && isReadyForPullUp()) { lastMotionY = y; isBeingDragged = true; if (mode == MODE_BOTH) { currentMode = MODE_PULL_UP_TO_REFRESH; } } } } break; } case MotionEvent.ACTION_DOWN: { if (isReadyForPull()) { lastMotionY = initialMotionY = event.getY(); lastMotionX = event.getX(); isBeingDragged = false; } break; } case MotionEvent.ACTION_UP: break; } setRefreshLabel(currentMode); return isBeingDragged; } protected void addRefreshableView(Context context, T refreshableView) { addView(refreshableView, new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, 0, 1.0f)); } /** * This is implemented by derived classes to return the created View. If you * need to use a custom View (such as a custom ListView), override this * method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @param context * @param attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declaration will be routed to the * created View * @return New instance of the Refreshable View */ protected abstract T createRefreshableView(Context context, AttributeSet attrs); public final int getCurrentMode() { return currentMode; } protected final LoadingLayout getFooterLayout() { return footerLayout; } protected final LoadingLayout getHeaderLayout() { return headerLayout; } protected final int getHeaderHeight() { return headerHeight; } protected final int getMode() { return mode; } /** * Implemented by derived class to return whether the View is in a state * where the user can Pull to Refresh by scrolling down. * * @return true if the View is currently the correct state (for example, top * of a ListView) */ protected abstract boolean isReadyForPullDown(); /** * Implemented by derived class to return whether the View is in a state * where the user can Pull to Refresh by scrolling up. * * @return true if the View is currently in the correct state (for example, * bottom of a ListView) */ protected abstract boolean isReadyForPullUp(); // =========================================================== // Methods // =========================================================== protected void resetHeader() { state = PULL_TO_REFRESH; isBeingDragged = false; if (null != headerLayout) { headerLayout.reset(); } if (null != footerLayout) { footerLayout.reset(); } smoothScrollTo(0); } /** * unless special requirements to call the method ,default call the method * {@link #setRefreshingInternal(boolean doScroll)} * * @param doScroll * @param mode */ protected void setRefreshingInternal(boolean doScroll, int mode) { state = REFRESHING; setRefreshLabel(mode); if (null != headerLayout) { headerLayout.refreshing(); } if (doScroll) { smoothScrollTo(mode == MODE_PULL_DOWN_TO_REFRESH ? -headerHeight : headerHeight); } } /** * set last refresh time * * @param time */ public void setRefreshTime(String time) { TextView mHeaderTimeView = (TextView) headerLayout .findViewById(R.id.xlistview_header_time); mHeaderTimeView.setText(time); } public void setRefreshTime(long time){ TextView mHeaderTimeView = (TextView) headerLayout .findViewById(R.id.xlistview_header_time); mHeaderTimeView.setText(TimeUtil.getChatTime(time)); } protected void setRefreshingInternal(boolean doScroll) { state = REFRESHING; setRefreshLabel(currentMode); if (null != footerLayout) { footerLayout.refreshing(); } if (null != headerLayout) { headerLayout.refreshing(); } if (doScroll) { smoothScrollTo(currentMode == MODE_PULL_DOWN_TO_REFRESH ? -headerHeight : headerHeight); } } private void setRefreshLabel(int mode) { if (mode == MODE_PULL_DOWN_TO_REFRESH) { setRefreshLabel("涓嬫媺鍒锋柊", "閲婃斁绔嬪嵆鍒锋柊", "姝e湪鍒锋柊"); } if (mode == MODE_PULL_UP_TO_REFRESH) { setRefreshLabel("涓婃媺鑾峰彇鏇村", "鏉惧紑鏄剧ず鏇村", "姝e湪鍔犺浇"); } } protected final void setHeaderScroll(int y) { scrollTo(0, y); } protected final void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } if (this.getScrollY() != y) { this.currentSmoothScrollRunnable = new SmoothScrollRunnable( handler, getScrollY(), y); handler.post(currentSmoothScrollRunnable); } } public void init(int mode) { // Loading View Strings String pullLabel = context .getString(R.string.pull_to_refresh_pull_label); String refreshingLabel = context .getString(R.string.pull_to_refresh_refreshing_label); String releaseLabel = context .getString(R.string.pull_to_refresh_release_label); // Add Loading Views if (mode == MODE_PULL_DOWN_TO_REFRESH || mode == MODE_BOTH) { headerLayout = new LoadingLayout(context,MODE_PULL_DOWN_TO_REFRESH, releaseLabel, pullLabel,refreshingLabel); addView(headerLayout, 0, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)); measureView(headerLayout); headerHeight = headerLayout.getMeasuredHeight(); } if (mode == MODE_PULL_UP_TO_REFRESH || mode == MODE_BOTH) { footerLayout = new LoadingLayout(context, MODE_PULL_UP_TO_REFRESH,releaseLabel, pullLabel, refreshingLabel); addView(footerLayout, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); measureView(footerLayout); headerHeight = footerLayout.getMeasuredHeight(); } // Styleables from XML if (null != headerLayout) { // headerLayout.setTextColor(Color.BLACK); } if (null != footerLayout) { // footerLayout.setTextColor(Color.BLACK); } // Hide Loading Views switch (mode) { case MODE_BOTH: setPadding(0, -headerHeight, 0, -headerHeight); break; case MODE_PULL_UP_TO_REFRESH: setPadding(0, 0, 0, -headerHeight); break; case MODE_PULL_DOWN_TO_REFRESH: default: setPadding(0, -headerHeight, 0, 0); break; } // If we're not using MODE_BOTH, then just set currentMode to current // mode if (mode != MODE_BOTH) { currentMode = mode; } this.mode = mode; } private void init(Context context, AttributeSet attrs) { this.context = context; setOrientation(LinearLayout.VERTICAL); touchSlop = ViewConfiguration.getTouchSlop(); // Refreshable View // By passing the attrs, we can add ListView/GridView params via XML refreshableView = this.createRefreshableView(context, attrs); this.addRefreshableView(context, refreshableView); } private Context context; private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } /** * Actions a Pull Event * * @return true if the Event has been handled, false if there has been no * change */ private boolean pullEvent() { final int newHeight; final int oldHeight = this.getScrollY(); switch (currentMode) { case MODE_PULL_UP_TO_REFRESH: newHeight = Math.round(Math.max(initialMotionY - lastMotionY, 0) / FRICTION); // newHeight = Math.round((initialMotionY - lastMotionY) / // FRICTION); break; case MODE_PULL_DOWN_TO_REFRESH: default: newHeight = Math.round(Math.min(initialMotionY - lastMotionY, 0) / FRICTION); // newHeight = Math.round((initialMotionY - lastMotionY) / // FRICTION); break; } setHeaderScroll(newHeight); if (newHeight != 0) { if (state == PULL_TO_REFRESH && headerHeight < Math.abs(newHeight)) { state = RELEASE_TO_REFRESH; switch (currentMode) { case MODE_PULL_UP_TO_REFRESH: footerLayout.releaseToRefresh(); break; case MODE_PULL_DOWN_TO_REFRESH: headerLayout.releaseToRefresh(); break; } return true; } else if (state == RELEASE_TO_REFRESH && headerHeight >= Math.abs(newHeight)) { state = PULL_TO_REFRESH; switch (currentMode) { case MODE_PULL_UP_TO_REFRESH: footerLayout.pullToRefresh(); break; case MODE_PULL_DOWN_TO_REFRESH: headerLayout.pullToRefresh(); break; } return true; } } return oldHeight != newHeight; } private boolean isReadyForPull() { switch (mode) { case MODE_PULL_DOWN_TO_REFRESH: return isReadyForPullDown(); case MODE_PULL_UP_TO_REFRESH: return isReadyForPullUp(); case MODE_BOTH: return isReadyForPullUp() || isReadyForPullDown(); } return false; } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(int mode); } private OnShowLayoutListener onShowLayoutListener; public void setOnShowLayoutListener(OnShowLayoutListener listener) { this.onShowLayoutListener = listener; } public static interface OnShowLayoutListener { /** * 鏄惁姝e湪鏄剧ず搴曢儴甯冨眬 */ public void onShow(); /** * 鏄惁娑堝け */ public void onDismiss(); } public static interface OnLastItemVisibleListener { public void onLastItemVisible(); } @Override public void setLongClickable(boolean longClickable) { getRefreshableView().setLongClickable(longClickable); } }