Android PullToRefresh 下拉刷新,上拉更多,支持ScrollView,ListView,可方便拓展GridView,WebView等

来源:互联网 发布:telnet 发送字符 端口 编辑:程序博客网 时间:2024/06/10 21:08

在写着东西之前,从网上找到很多这方面的源码,但是基本没有找到满意的,包括在GitHub上的比较有名的Android-PullToRefresh-master,思来想去还是自己写吧,当然其中借鉴了一些别的开源代码!

废话不多说,直接上代码,注释很全乎,应该不难理解,Demo下载地址在最后:

package com.zs.pulltorefreshtest;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.LinearLayout;import android.widget.ListView;import android.widget.RelativeLayout;import android.widget.ScrollView;import android.widget.Scroller;/** * 下拉刷新控件,主要测试了ScrollView,代码中已实现ListView下拉和上拉刷新,不过没有怎么测 * 至于GridView、WebView等,代码中没有实现,不过很好拓展,在isReadyForPullUp() 和  * isReadyForPullDown()这两个方法中加入相应的View的上下边界判断就OK了 * @author zhangshuo  * @version 1.0 */public class PullToRefreshView extends RelativeLayout {/**手指滑动距离与控件移动距离的比例为2:1*/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;/**私有模式,不提供对外调用, * 仅用来标示“用户下拉刷新成功后,headerView显示在头部,当用户手指向上滑动时,将headerView跟随用户滑动向上滑动” * 及“用户上拉更多成功后,footerView显示在底部,当用户手指向下滑动时,将footerView跟随用户滑动向下滑动”这两个过程的模式*/private static final int MODE_PULL_TO_SCROLL_HEADER_OR_FOOTER = 0x0;/**标示当前支持下拉刷新模式*/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;private Context context;/**滚动对象*/private Scroller scroller;/**判断用户手指的移动距离是否足以响应为move*/private int touchSlop;private float initialMotionY;private float lastMotionX;private float lastMotionY;private boolean isBeingDragged = false;/**记录headerView当前的状态*/private int headerState = PULL_TO_REFRESH;/**记录footerView当前的状态*/private int footerState = PULL_TO_REFRESH;/**当前所支持的模式*/private int mode = MODE_PULL_DOWN_TO_REFRESH;/**当前处于的模式*/private int currentMode;/**根据不同的mode,contentView所在父View的位置不同,下拉刷新时为1,上拉更多时为1,上拉下拉都支持时为2*/private int index = 1;/**标示当处于刷新状态时,是否需要禁用滑动*/private boolean disableScrollingWhileRefreshing = false;/**标示是否允许滑动刷新*/private boolean isPullToRefreshEnabled = true;private LoadingLayout headerLayout;private LoadingLayout footerLayout;private int headerHeight;/**记录当处于刷新状态时,用户继续下拉的次数*/private int pullWithRefreshingCount = 0;/**记录当处于加载更多状态时,用户继续上拉的次数*/private int pullWithLoadingMoreCount = 0;/**刷新回调接口*/private OnRefreshListener onRefreshListener;/**加载更多回调接口*/private OnLoadMoreListener onLoadMoreListener;public PullToRefreshView(Context context) {super(context);init(context, null);}public PullToRefreshView(Context context, AttributeSet attrs) {super(context, attrs);init(context, attrs);}/** * @方法描述:初始化方法 * @作者:zhangshuo * @param context * @param attrs */private void init(Context context, AttributeSet attrs) {scroller = new Scroller(context);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();this.context = context;this.addLoadingView();}/** * @方法描述: 根据当前模式设置,加载头部和底部布局 * @作者:zhangshuo */public void addLoadingView() {String pullDownLabel = context.getString(R.string.pull_to_refresh_pull_down_label);String refreshingDownLabel = context.getString(R.string.pull_to_refresh_refreshing_down_label);String releaseDownLabel = context.getString(R.string.pull_to_refresh_release_down_label);String pullUpLabel = context.getString(R.string.pull_to_refresh_pull_up_label);String refreshingUpLabel = context.getString(R.string.pull_to_refresh_refreshing_up_label);String releaseUpLabel = context.getString(R.string.pull_to_refresh_release_up_label);/*加载头部和底部View*/if (mode == MODE_PULL_DOWN_TO_REFRESH || mode == MODE_BOTH) {headerLayout = new LoadingLayout(context,MODE_PULL_DOWN_TO_REFRESH, releaseDownLabel, pullDownLabel,refreshingDownLabel);addView(headerLayout, 0, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_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,releaseUpLabel, pullUpLabel, refreshingUpLabel);RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);      lp2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);       addView(footerLayout, lp2);  measureView(footerLayout);headerHeight = footerLayout.getMeasuredHeight();}/*隐藏头部和底部View*/switch (mode) {case MODE_BOTH:index = 2;setPadding(0, -headerHeight, 0, -headerHeight);break;case MODE_PULL_UP_TO_REFRESH:index = 1;setPadding(0, 0, 0, -headerHeight);break;case MODE_PULL_DOWN_TO_REFRESH:default:index = 1;setPadding(0, -headerHeight, 0, 0);break;}}/** * 在头部和底部View添加完成后,重新布局,以避免在隐藏headerView和footerView时会把一部分内容(contentView)隐藏 */@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {// TODO Auto-generated method stubsuper.onLayout(changed, l, t, r, b);View contentView = null;RelativeLayout.LayoutParams lp1 = null; switch (mode) {case MODE_BOTH:contentView = this.getChildAt(index);lp1 = (LayoutParams) contentView.getLayoutParams();lp1.setMargins(0, headerHeight, 0, headerHeight);break;case MODE_PULL_UP_TO_REFRESH:contentView = this.getChildAt(index);lp1 = (LayoutParams) contentView.getLayoutParams();lp1.setMargins(0, 0, 0, headerHeight);break;case MODE_PULL_DOWN_TO_REFRESH:default:contentView = this.getChildAt(index);lp1 = (LayoutParams) contentView.getLayoutParams();lp1.setMargins(0, headerHeight, 0, 0);break;}}private void measureView(View child) {ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_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);}@Overridepublic final boolean onInterceptTouchEvent(MotionEvent event) {Log.e("Intercept", "start");if (!isPullToRefreshEnabled) {return false;}if ((isLoadingMore() || 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_DOWN: {Log.e("Intercept", "down");if (isReadyForPull()) {lastMotionY = initialMotionY = event.getY();lastMotionX = event.getX();isBeingDragged = false;}break;}case MotionEvent.ACTION_MOVE: {Log.e("Intercept", "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;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;currentMode = MODE_PULL_UP_TO_REFRESH;}else if((isRefreshing() && getScrollY() < 0)|| (isLoadingMore() && getScrollY() > 0)){/*当前headerView或footerView处于显示状态,开启跟随手指滑动模式*/lastMotionY = y;isBeingDragged = true;currentMode = MODE_PULL_TO_SCROLL_HEADER_OR_FOOTER;}}}break;}}return isBeingDragged;}@Overridepublic final boolean onTouchEvent(MotionEvent event) {Log.e("Touch", "start");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_DOWN: {Log.e("Touch", "down");if (isReadyForPull()) {lastMotionY = initialMotionY = event.getY();return true;}break;}case MotionEvent.ACTION_MOVE: {Log.e("Touch", "move");if (isBeingDragged) {lastMotionY = event.getY();this.pullEvent();return true;}break;}case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP: {Log.e("Touch", "up");if (isBeingDragged) {isBeingDragged = false;if(isRefreshing() && pullWithRefreshingCount == 0){pullWithRefreshingCount = 1;}if(isLoadingMore() && pullWithLoadingMoreCount == 0){pullWithLoadingMoreCount = 1;}switch (currentMode) {case MODE_PULL_TO_SCROLL_HEADER_OR_FOOTER:/*将headerView和footerView隐藏*/smoothScrollTo(0);break;case MODE_PULL_UP_TO_REFRESH:/*判断是否激活加载更多*/if (footerState == RELEASE_TO_REFRESH && null != onLoadMoreListener) {setLoadingMoreInternal(true);onLoadMoreListener.onLoadMore();} else {smoothScrollTo(0);}break;case MODE_PULL_DOWN_TO_REFRESH:/*判断是否激活刷新*/if (headerState == RELEASE_TO_REFRESH && null != onRefreshListener) {setRefreshingInternal(true);onRefreshListener.onRefresh();} else {smoothScrollTo(0);}break;}return true;}break;}}return false;}/** * @方法描述:处理用户滑动的方法 * @作者:zhangshuo * * @return */private boolean pullEvent() {final int newHeight;final int oldHeight = this.getScrollY();switch (currentMode) {case MODE_PULL_TO_SCROLL_HEADER_OR_FOOTER:newHeight = Math.round((initialMotionY - lastMotionY));break;case MODE_PULL_UP_TO_REFRESH:newHeight = Math.round(Math.max(initialMotionY - lastMotionY, 0)/ FRICTION);break;case MODE_PULL_DOWN_TO_REFRESH:default:newHeight = Math.round(Math.min(initialMotionY - lastMotionY, 0)/ FRICTION);break;}if(isRefreshing() && pullWithRefreshingCount == 0){/*处于刷新状态下,第一次继续下拉,此时headerView已经显示在头部*/if((-headerHeight + newHeight) < 0){scrollTo(-headerHeight + newHeight);}else{scrollTo(0);if(((ScrollView)getChildAt(index)).getChildAt(0).getHeight() > getChildAt(index).getHeight()){getChildAt(index).scrollTo(0, newHeight - headerHeight);}}}else if(isLoadingMore() && pullWithLoadingMoreCount == 0){/*处于刷新状态下,第一次继续下拉,此时headerView已经显示在头部*/if((headerHeight + newHeight) > 0){scrollTo(headerHeight + newHeight);}else{scrollTo(0);if(((ScrollView)getChildAt(index)).getChildAt(0).getHeight() > getChildAt(index).getHeight()){getChildAt(index).scrollTo(0, newHeight + headerHeight + ((ScrollView)getChildAt(index)).getChildAt(0).getHeight() - getChildAt(index).getHeight());}}}else{scrollTo(newHeight);}if (newHeight != 0) {switch (currentMode) {case MODE_PULL_UP_TO_REFRESH:if (footerState == PULL_TO_REFRESH && headerHeight < Math.abs(newHeight)) {footerState = RELEASE_TO_REFRESH;footerLayout.releaseToRefresh();return true;} else if (footerState == RELEASE_TO_REFRESH&& headerHeight >= Math.abs(newHeight)) {footerState = PULL_TO_REFRESH;footerLayout.pullToRefresh();return true;}break;case MODE_PULL_DOWN_TO_REFRESH:if (headerState == PULL_TO_REFRESH && headerHeight < Math.abs(newHeight)) {headerState = RELEASE_TO_REFRESH;headerLayout.releaseToRefresh();return true;} else if (headerState == RELEASE_TO_REFRESH&& headerHeight >= Math.abs(newHeight)) {headerState = PULL_TO_REFRESH;headerLayout.pullToRefresh();return true;}break;}}return oldHeight != newHeight;}/** * @方法描述: 判断当前状态是否可以进行上拉更多或下拉刷新的滑动操作 * @作者:zhangshuo * @return */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;}/** * @方法描述: 判断当前状态是否可以进行下拉刷新操作 * @作者:zhangshuo * @return */private boolean isReadyForPullDown() {// TODO Auto-generated method stubif (getChildCount() > 1) {Log.e("Ready--down", String.valueOf(getChildCount()));View childView = this.getChildAt(index);if (childView instanceof ListView) {int top = ((ListView) childView).getChildAt(0).getTop();int pad = ((ListView) childView).getListPaddingTop();if ((Math.abs(top - pad)) < 3&& ((ListView) childView).getFirstVisiblePosition() == 0) {return true;} else {return false;}} else if (childView instanceof ScrollView) {Log.e("Ready--down", "scrollView");if (((ScrollView) childView).getScrollY() == 0) {return true;} else {return false;}}}return false;}/** * @方法描述:判断当前状态是否可以上拉更多的滑动操作 * @作者:zhangshuo * @return */private boolean isReadyForPullUp() {// TODO Auto-generated method stubif (getChildCount() > 1) {Log.e("Ready--up", String.valueOf(getChildCount()));View childView = this.getChildAt(index);if (childView instanceof ListView) {int top = ((ListView) childView).getChildAt(((ListView) childView).getCount()).getBottom();int pad = ((ListView) childView).getListPaddingBottom();if ((Math.abs(top - pad)) < 3&& ((ListView) childView).getFirstVisiblePosition() == ((ListView) childView).getCount()) {return true;} else {return false;}} else if (childView instanceof ScrollView) {Log.e("Ready--up", "scrollView");int off = ((ScrollView) childView).getScrollY()+ ((ScrollView) childView).getHeight()- ((ScrollView) childView).getChildAt(0).getHeight();if (off >= 0) {return true;} else {return false;}}}return false;}/** * @方法描述:是否允许上拉更多或下拉刷新的滑动操作 * @作者:zhangshuo * @return */public final boolean isPullToRefreshEnabled() {return isPullToRefreshEnabled;}/** * @方法描述:当处于刷新状态时,是否需要禁用滑动 * @作者:zhangshuo * @return */public final boolean isDisableScrollingWhileRefreshing() {return disableScrollingWhileRefreshing;}/** * @方法描述:当前正处于刷新中 * @作者:zhangshuo * @return */public final boolean isRefreshing() {return headerState == REFRESHING || headerState == MANUAL_REFRESHING;}/** * @方法描述:当前正处于加载更多中 * @作者:zhangshuo * @return */public final boolean isLoadingMore() {return footerState == REFRESHING || footerState == MANUAL_REFRESHING;}/** * @方法描述: 设置当处于刷新状态时,是否需要禁用滑动 * @作者:zhangshuo * @param disableScrollingWhileRefreshing */public final void setDisableScrollingWhileRefreshing(boolean disableScrollingWhileRefreshing) {this.disableScrollingWhileRefreshing = disableScrollingWhileRefreshing;}/** * @方法描述:结束刷新状态 * @作者:zhangshuo * */public final void onRefreshComplete() {if (headerState != PULL_TO_REFRESH) {resetHeader();}pullWithRefreshingCount = 0;}/** * @方法描述:结束加载更多状态 * @作者:zhangshuo * */public final void onLoadMoreComplete() {if (footerState != PULL_TO_REFRESH) {resetFooter();}pullWithLoadingMoreCount = 0;}/** * @方法描述: 设置否允许滑动刷新 * @作者:zhangshuo * @param enable */public final void setPullToRefreshEnabled(boolean enable) {this.isPullToRefreshEnabled = enable;}/** * @方法描述:强制设置为刷新状态 * @作者:zhangshuo */public final void setRefreshing() {this.setRefreshing(true);}/** * @方法描述:强制设置为加载更多状态 * @作者:zhangshuo */public final void setLoadingMore(){this.setLoadingMore(true);}/** * @方法描述:强制设置为刷新状态 * @作者:zhangshuo * @param doScroll */public final void setRefreshing(boolean doScroll) {if (!isRefreshing()) {setRefreshingInternal(doScroll);headerState = MANUAL_REFRESHING;}}/** * @方法描述:强制设置为加载更多状态 * @作者:zhangshuo * @param doScroll */public final void setLoadingMore(boolean doScroll) {if (!isLoadingMore()) {setLoadingMoreInternal(doScroll);footerState = MANUAL_REFRESHING;}}protected 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;}/** * @方法描述:重置headerView * @作者:zhangshuo */protected void resetHeader() {headerState = PULL_TO_REFRESH;isBeingDragged = false;if (null != headerLayout) {headerLayout.reset();}smoothScrollTo(0);}/** * @方法描述:重置footerView * @作者:zhangshuo */protected void resetFooter() {footerState = PULL_TO_REFRESH;isBeingDragged = false;if (null != footerLayout) {footerLayout.reset();}smoothScrollTo(0);}/** * @方法描述:强制设置为刷新状态,并显示出headerView * @作者:zhangshuo * @param doScroll */protected void setRefreshingInternal(boolean doScroll) {headerState = REFRESHING;pullWithRefreshingCount = 0;if (null != headerLayout) {headerLayout.refreshing();}if (doScroll) {smoothScrollTo(-headerHeight);}}/** * @方法描述:强制设置为加载更多状态,并显示出footerView * @作者:zhangshuo * @param doScroll */protected void setLoadingMoreInternal(boolean doScroll) {footerState = REFRESHING;pullWithLoadingMoreCount = 0;if (null != footerLayout) {footerLayout.refreshing();}if (doScroll) {smoothScrollTo(headerHeight);}}protected final void scrollTo(int y) {scrollTo(0, y);}protected final void smoothScrollTo(int y) {scroller.startScroll(0, getScrollY(), 0, -(getScrollY() - y), 500);invalidate();}@Overridepublic void computeScroll() {// TODO Auto-generated method stubif (scroller.computeScrollOffset()) {scrollTo(0, this.scroller.getCurrY());postInvalidate();}}/** * @方法描述: 设置刷新回调接口 * @作者:zhangshuo * @param listener */public final void setOnRefreshListener(OnRefreshListener listener) {this.onRefreshListener = listener;}/** * @方法描述:设置加载更多回调接口 * @作者:zhangshuo * @param listener */public final void setOnLoadMoreListener(OnLoadMoreListener listener){this.onLoadMoreListener  = listener;}/** * @CLASS:OnRefreshListener * @描述: 刷新回调接口 * @作者:zhangshuo * @版本:v1.0 * @日期:2014年7月15日 上午11:59:50 */public static interface OnRefreshListener {public void onRefresh();}/** * @CLASS:OnLoadMoreListener * @描述: 加载更多回调接口 * @作者:zhangshuo * @版本:v1.0 * @日期:2014年7月15日 下午12:00:06 */public static interface OnLoadMoreListener {public void onLoadMore();}}
<span style="font-size:24px;">由于时间关系,我主要测试了ScrollView,代码中已实现ListView下拉和上拉刷新,不过没有怎么测,</span>
<span style="font-size:24px;">至于GridView、WebView等,代码中没有实现,不过很好拓展,在isReadyForPullUp() 和 isReadyForPullDown()这两个方法中加入相应的View的上下边界判断就OK了!</span>



源码下载地址:http://download.csdn.net/detail/super_spy/7642641

0 1
原创粉丝点击