您的位置:首页 > 移动开发

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

2017-05-13 08:59 621 查看
在写着东西之前。从网上找到非常多这方面的源代码,可是基本没有找到惬意的。包含在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)隐藏
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
super.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);
}

@Override
public 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;
}

@Override
public 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 stub
if (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 stub
if (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();

}

@Override
public void computeScroll() {
// TODO Auto-generated method stub
if (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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐