Last active
August 29, 2015 14:24
-
-
Save johnwatsondev/5922a3be8bc2555bff93 to your computer and use it in GitHub Desktop.
Pull to refresh ListView code. It's not good. Just a demo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 短小精干的<strong>ListView</strong>下拉刷新和加载更多组件 | |
* | |
* <p><strong>变更说明:</strong> | |
* <br> | |
* 默认如果设置了{@link OnRefreshListener}接口和{@link OnLoadMoreListener}接口, | |
* 则打开下拉刷新和加载更多功能。 | |
* <br> | |
* 若设置监听器为null,抛出异常。 | |
* <br> | |
* 一共六个Flag: | |
* <br> | |
* {@link #mCanRefresh}(是否可以下拉刷新) | |
* <br> | |
* {@link #mCanLoadMore}(是否可以加载更多) | |
* <br> | |
* {@link #mIsAutoLoadMore}(是否自动加载更多) | |
* <br> | |
* {@link #mIsDataLoadFinished}(所有数据是否加载完毕) | |
* <br> | |
* {@link #mMaxPullDistance}(HeaderView最大下拉距离 (px)) | |
* <br> | |
* {@link #mIsMoveToFirstItemAfterRefresh}(下拉刷新后是否显示第一条Item) | |
* | |
* <p><strong>Note:</strong> | |
* <br> | |
* 1.新增设置所有数据加载完毕后提示 | |
* <br> | |
* 2.优化所有Item高度小于ListView高度时,自动隐藏加载更多按钮。 | |
* <br> | |
* 3.新增设置HeaderView最大下拉距离 | |
* | |
* <p>大家可以根据实际项目需要自由更改,谢谢! | |
* <br> | |
* <strong>若有改进意见,请发送到俺的邮箱哈~ 多谢各位小伙伴!^_^</strong> | |
* | |
* @date May 26, 2015 11:34:37 PM | |
* @author JohnWatsonDev | |
* @email [email protected] | |
* @blog www.johnwatsondev.com | |
*/ | |
public class PtrListView extends ListView implements OnScrollListener { | |
/** 显示格式化日期模板 */ | |
private final static String DATE_FORMAT_STR = "yyyy年MM月dd日 HH:mm"; | |
/** 实际的padding的距离与界面上偏移距离的比例 */ | |
private final static int RATIO = 3; | |
/** 松开刷新 */ | |
private final static int RELEASE_TO_REFRESH = 0; | |
/** 下拉刷新 */ | |
private final static int PULL_TO_REFRESH = 1; | |
/** 正在刷新 */ | |
private final static int REFRESHING = 2; | |
/** 刷新完成 */ | |
private final static int DONE = 3; | |
/** 加载中 */ | |
private final static int FOOTER_LOADING = 1; | |
/** 手动加载完成 */ | |
private final static int FOOTER_MANUAL_LOAD_DONE = 2; | |
/** 自动加载完成 */ | |
private final static int FOOTER_AUTO_LOAD_DONE = 3; | |
/** 0:RELEASE_TO_REFRESH; | |
* <p> 1:PULL_To_REFRESH; | |
* <p> 2:REFRESHING; | |
* <p> 3:DONE; */ | |
private int mHeaderState; | |
/** 1:加载中 - FOOTER_LOADING | |
* <p> 2:手动加载完成 - FOOTER_MANUAL_LOAD_DONE | |
* <p> 3:自动加载完成 - FOOTER_AUTO_LOAD_DONE */ | |
private int mFooterState; | |
// ================================= 功能设置Flag ================================ | |
/** 可以下拉刷新? */ | |
private boolean mCanRefresh = false; | |
/** 可以加载更多? */ | |
private boolean mCanLoadMore = false; | |
/** 可以自动加载更多吗?(注意,先判断是否有加载更多,如果没有,这个flag也没有意义) */ | |
private boolean mIsAutoLoadMore = true; | |
/** 所有数据是否加载完毕 */ | |
private boolean mIsDataLoadFinished = false; | |
/** 下拉刷新后是否显示第一条Item */ | |
private boolean mIsMoveToFirstItemAfterRefresh = true; | |
/** HeaderView最大下拉距离 (px) */ | |
private int mMaxPullDistance = 0; | |
/** | |
* true if refreshing else false | |
* | |
* @return Is header refreshing? | |
*/ | |
public boolean isRefreshing(){ | |
if(mHeaderState == REFRESHING){ | |
return true; | |
} | |
return false; | |
} | |
/** | |
* true if loading more else false | |
* | |
* @return Is footer loading more? | |
*/ | |
public boolean isLoadingMore(){ | |
if(mFooterState == FOOTER_LOADING){ | |
return true; | |
} | |
return false; | |
} | |
/** | |
* default false | |
* | |
* @return Is can loadMore? | |
*/ | |
public boolean isCanLoadMore() { | |
return mCanLoadMore; | |
} | |
public void setCanLoadMore(boolean pCanLoadMore) { | |
if(mLoadMoreListener == null){ | |
throw new InvalidSettingException("Must set a valid OnLoadMoreListener..."); | |
} | |
mCanLoadMore = pCanLoadMore; | |
if(mCanLoadMore && mIsDataLoadFinished){ | |
mIsDataLoadFinished = false; | |
} | |
// Why mFooterState != FOOTER_LOADING ? | |
// 如果正在加载中,要等待加载完成后再onScroll()中移除FootView。 | |
if(!mCanLoadMore && mFooterState != FOOTER_LOADING && getFooterViewsCount() > 0){ | |
// 突然关闭加载更多功能之后,我们要移除FootView。 | |
MyLogger.showLogWithLineNum(5,"调用set方法关闭加载更多 removeFooterView(endRootView);..."); | |
removeFooterView(); | |
}else if(mCanLoadMore && getFooterViewsCount() == 0 && mEnoughCount){ | |
addFooterView(); | |
} | |
} | |
/** | |
* default false | |
* | |
* @return Is can pull to refresh? | |
*/ | |
public boolean isCanRefresh() { | |
return mCanRefresh; | |
} | |
public void setCanRefresh(boolean pCanRefresh) { | |
if(mRefreshListener == null){ | |
throw new InvalidSettingException("Must set a valid OnRefreshListener..."); | |
} | |
mCanRefresh = pCanRefresh; | |
if(mCanRefresh && mIsDataLoadFinished){ | |
mIsDataLoadFinished = false; | |
} | |
} | |
/** | |
* default true | |
* | |
* @return Is auto loadMore? | |
*/ | |
public boolean isAutoLoadMore() { | |
return mIsAutoLoadMore; | |
} | |
public void setAutoLoadMore(boolean pIsAutoLoadMore) { | |
mIsAutoLoadMore = pIsAutoLoadMore; | |
} | |
/** | |
* default false | |
* | |
* @return Is data load finished? | |
*/ | |
public boolean isDataLoadFinished(){ | |
return mIsDataLoadFinished; | |
} | |
public void setIsDataLoadFinished(boolean pIsDataLoadFinished){ | |
mIsDataLoadFinished = pIsDataLoadFinished; | |
if(mIsDataLoadFinished){ | |
setFooterViewLoadComplete(); | |
} | |
} | |
/** | |
* 设置FooterView显示 “没有更多数据” | |
*/ | |
private void setFooterViewLoadComplete(){ | |
mFooterLoadTipsTextView.setText(R.string.p2refresh_no_more_data); | |
mFooterLoadTipsTextView.setVisibility(View.VISIBLE); | |
mFooterLoadProgressBar.setVisibility(View.GONE); | |
mFooterView.setVisibility(View.VISIBLE); | |
} | |
/** | |
* default true | |
* | |
* @return Is move to first item after refresh finished? | |
*/ | |
public boolean isMoveToFirstItemAfterRefresh() { | |
return mIsMoveToFirstItemAfterRefresh; | |
} | |
public void setMoveToFirstItemAfterRefresh( | |
boolean pIsMoveToFirstItemAfterRefresh) { | |
mIsMoveToFirstItemAfterRefresh = pIsMoveToFirstItemAfterRefresh; | |
} | |
/** | |
* Get max pull distance (This comment is garbage...) | |
* | |
* @return max pull distance | |
*/ | |
public int getMaxPullDistance() { | |
return mMaxPullDistance; | |
} | |
public void setMaxPullDistance(int maxPullDistance) { | |
mMaxPullDistance = maxPullDistance; | |
} | |
// ============================================================================ | |
private LayoutInflater mInflater; | |
private LinearLayout mHeaderView; | |
private TextView mTipsTextView; | |
private TextView mLastUpdatedTextView; | |
private ImageView mArrowImageView; | |
private ProgressBar mProgressBar; | |
private View mFooterView; | |
private ProgressBar mFooterLoadProgressBar; | |
private TextView mFooterLoadTipsTextView; | |
/** headerView动画 */ | |
private RotateAnimation mArrowAnim; | |
/** headerView反转动画 */ | |
private RotateAnimation mArrowReverseAnim; | |
/** 用于保证startY的值在一个完整的touch事件中只被记录一次 */ | |
private boolean mIsRecored; | |
private int mHeaderViewWidth; | |
private int mHeaderViewHeight; | |
/** Touch事件开始Y坐标 */ | |
private int mStartY; | |
private boolean mIsBack; | |
/** 纪录上一次Move事件的Y坐标,为了判断是下拉还是上滑 */ | |
private int mLastMoveX; | |
private int mListViewHeight; | |
private int mFirstItemIndex; | |
private int mLastItemIndex; | |
private int mCount; | |
/** Item数量足够充满ListView高度? */ | |
private boolean mEnoughCount; | |
private OnRefreshListener mRefreshListener; | |
private OnLoadMoreListener mLoadMoreListener; | |
public PtrListView(Context pContext) { | |
super(pContext); | |
init(pContext); | |
} | |
public PtrListView(Context pContext, AttributeSet pAttrs) { | |
super(pContext, pAttrs); | |
init(pContext); | |
} | |
public PtrListView(Context pContext, AttributeSet pAttrs, int pDefStyle) { | |
super(pContext, pAttrs, pDefStyle); | |
init(pContext); | |
} | |
/** | |
* 初始化操作 | |
* @param pContext | |
*/ | |
private void init(Context pContext) { | |
setCacheColorHint(pContext.getResources().getColor(R.color.transparent)); | |
mInflater = LayoutInflater.from(pContext); | |
addHeadView(); | |
setOnScrollListener(this); | |
initPullImageAnimation(0); | |
} | |
/** | |
* 为了获取ListView的高度 | |
*/ | |
private void addGlobalLayoutListener(){ | |
if(this.getViewTreeObserver().isAlive()){ | |
this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { | |
@Override | |
public void onGlobalLayout() { | |
// 此处获取高度的速度慢于getMeasureHight()获取的速度 | |
// 二者值相同 | |
// mListViewHeight = getHeight(); | |
MyLogger.showLogWithLineNum(4, "onGlobalLayout"); | |
} | |
}); | |
} | |
// Need api 11, current min is 8. | |
// this.addOnLayoutChangeListener(new OnLayoutChangeListener() { | |
// | |
// @Override | |
// public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, | |
// int oldTop, int oldRight, int oldBottom) { | |
// MyLogger.showLogWithLineNum(4, "onLayoutChange"); | |
// } | |
// }); | |
} | |
/** | |
* 添加下拉刷新的HeadView | |
*/ | |
private void addHeadView() { | |
mHeaderView = (LinearLayout) mInflater.inflate(R.layout.ptr_lv_head, null); | |
mArrowImageView = (ImageView) mHeaderView | |
.findViewById(R.id.head_arrowImageView); | |
mArrowImageView.setMinimumWidth(70); | |
mArrowImageView.setMinimumHeight(50); | |
mProgressBar = (ProgressBar) mHeaderView | |
.findViewById(R.id.head_progressBar); | |
mTipsTextView = (TextView) mHeaderView.findViewById( | |
R.id.head_tipsTextView); | |
mLastUpdatedTextView = (TextView) mHeaderView | |
.findViewById(R.id.head_lastUpdatedTextView); | |
measureView(mHeaderView); | |
mHeaderViewHeight = mHeaderView.getMeasuredHeight(); | |
mHeaderViewWidth = mHeaderView.getMeasuredWidth(); | |
mHeaderView.setPadding(0, -1 * mHeaderViewHeight, 0, 0); | |
mHeaderView.invalidate(); | |
Log.v("size", "width:" + mHeaderViewWidth + " height:" | |
+ mHeaderViewHeight); | |
addHeaderView(mHeaderView, null, false); | |
mHeaderState = DONE; | |
} | |
/** | |
* 添加加载更多FootView | |
*/ | |
private void addFooterView() { | |
mFooterView = mInflater.inflate(R.layout.ptr_lv_footer_more, null); | |
mFooterView.setVisibility(View.VISIBLE); | |
mFooterLoadProgressBar = (ProgressBar) mFooterView | |
.findViewById(R.id.pull_to_refresh_progress); | |
mFooterLoadTipsTextView = (TextView) mFooterView.findViewById(R.id.load_more); | |
mFooterView.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
// 数据加载完毕 或 非“手动加载更多”状态不允许点击 | |
if(mIsDataLoadFinished || mFooterState != FOOTER_MANUAL_LOAD_DONE) | |
return; | |
if(mCanLoadMore){ | |
if(mCanRefresh){ | |
// 当可以下拉刷新时,并且HeadView没有正在刷新,才可以点击加载更多。 | |
if(mHeaderState != REFRESHING){ | |
onLoadMore(); | |
} | |
}else { | |
// 当不能下拉刷新时,可以点击加载更多。 | |
onLoadMore(); | |
} | |
} | |
} | |
}); | |
addFooterView(mFooterView); | |
changeFooterViewStateByIsAutoLoadMore(true); | |
} | |
/** | |
* 根据是否自动加载更多Flag,更改FooterView状态。 | |
*/ | |
private void changeFooterViewStateByIsAutoLoadMore(boolean isFooterViewVisible){ | |
if(mIsAutoLoadMore){ | |
mFooterState = FOOTER_AUTO_LOAD_DONE; | |
}else{ | |
mFooterState = FOOTER_MANUAL_LOAD_DONE; | |
} | |
changeFootViewByState(isFooterViewVisible); | |
} | |
/** | |
* 实例化下拉刷新的箭头的动画效果 | |
* @param pAnimDuration 动画运行时长 | |
*/ | |
private void initPullImageAnimation(final int pAnimDuration) { | |
int _Duration; | |
if(pAnimDuration > 0){ | |
_Duration = pAnimDuration; | |
}else{ | |
_Duration = 250; | |
} | |
Interpolator _Interpolator = new LinearInterpolator(); | |
mArrowAnim = new RotateAnimation(0, -180, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f); | |
mArrowAnim.setInterpolator(_Interpolator); | |
mArrowAnim.setDuration(_Duration); | |
mArrowAnim.setFillAfter(true); | |
mArrowReverseAnim = new RotateAnimation(-180, 0, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f); | |
mArrowReverseAnim.setInterpolator(_Interpolator); | |
mArrowReverseAnim.setDuration(_Duration); | |
mArrowReverseAnim.setFillAfter(true); | |
} | |
/** | |
* 测量HeadView宽高(注意:此方法仅适用于LinearLayout,请读者自己测试验证。) | |
* @param pChild | |
*/ | |
private void measureView(View pChild) { | |
ViewGroup.LayoutParams p = pChild.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); | |
} | |
pChild.measure(childWidthSpec, childHeightSpec); | |
} | |
/** | |
* 获取view的高度 | |
* | |
* @param view | |
* @return | |
*/ | |
private int getMeasureHight(View view) { | |
int height = view.getHeight(); | |
if (height == 0) { | |
ViewUtil.measureView(view); | |
height = view.getMeasuredHeight(); | |
} | |
MyLogger.showLogWithLineNum(3, "getMeasureHight = "+height); | |
return height; | |
} | |
/** | |
* 获取所有Items的高度 | |
* | |
* @return all items' total height | |
*/ | |
private int getTotalItemsHeight(ListView listview){ | |
int numberOfItems = listview.getCount(); | |
// Get total height of all items. | |
int totalItemsHeight = 0; | |
for (int itemPos = 0; itemPos < numberOfItems; itemPos++) { | |
View item = listview.getAdapter().getView(itemPos, null, listview); | |
item.measure(0, 0); | |
totalItemsHeight += item.getMeasuredHeight(); | |
} | |
// Get total height of all item dividers. | |
int totalDividersHeight = listview.getDividerHeight() * (numberOfItems - 1); | |
int totalHeight = totalItemsHeight + totalDividersHeight; | |
MyLogger.showLogWithLineNum(3, "getHeightByItems = "+totalHeight); | |
return totalHeight; | |
} | |
@Override | |
protected void onAttachedToWindow() { | |
super.onAttachedToWindow(); | |
// MyLogger.showLogWithLineNum(4, "onAttachedToWindow"); | |
} | |
/** | |
* <strong>FirstTime Run</strong><p> | |
* onAttachedToWindow ---> onLayout ---> | |
* onLayoutChange ---> onGlobalLayout ---> onFocusChanged | |
*/ | |
@Override | |
protected void onLayout(boolean changed, int l, int t, int r, int b) { | |
super.onLayout(changed, l, t, r, b); | |
// MyLogger.showLogWithLineNum(4, "onLayout"); | |
} | |
@Override | |
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { | |
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); | |
// if(gainFocus){ | |
// MyLogger.showLogWithLineNum(4, "onFocusChanged"); | |
// } | |
} | |
// /** mFitsOnScreenRunnable是否已经运行完一遍 */ | |
// private boolean mIsFitsOnScreenRun = false; | |
/** 判断Item是否充满ListView的Runnable */ | |
// private Runnable mFitsOnScreenRunnable = new Runnable() { | |
// @Override | |
// public void run() { | |
// isItemsFitOnScreen(); | |
// } | |
// }; | |
/** | |
* 判断Item是否充满ListView | |
*/ | |
private void isItemsFitOnScreen(){ | |
if(mHeaderView.getPaddingTop() > - mHeaderViewHeight){ | |
// 说明下拉了,不进行判断,防止出现误判。 | |
return; | |
} | |
int last = getLastVisiblePosition(); | |
if(last == getCount() - 1 | |
&& getChildAt(last) != null | |
&& getChildAt(last).getBottom() < getHeight()) { | |
// It fits!(所有Item高度小于ListView高度) | |
mEnoughCount = false; | |
if(mFooterView != null && mFooterView.getVisibility() == VISIBLE){ | |
MyLogger.showLogWithLineNum(4, "fitonScreen mEnoughCount = "+mEnoughCount); | |
// 判断处当前TotalItemHeight高度不够时,我们要移除FootView。 | |
MyLogger.showLogWithLineNum(5,"TotalItemHeight高度不够 removeFooterView(endRootView);..."); | |
removeFooterView(); | |
} | |
} else { | |
// It doesn't fit...(所有Item高度大于/等于ListView高度) | |
mEnoughCount = true; | |
MyLogger.showLogWithLineNum(4, "fitonScreen mEnoughCount = "+mEnoughCount); | |
} | |
// mIsFitsOnScreenRun = true; | |
} | |
/** | |
*为了在滑到底部前,提前更改FooterView的显示内容或移除FooterView。 | |
*/ | |
@Override | |
public void onScroll(AbsListView pView, int pFirstVisibleItem, | |
int pVisibleItemCount, int pTotalItemCount) { | |
if(mListViewHeight == 0){ | |
mListViewHeight = getMeasureHight(this); | |
} | |
// http://stackoverflow.com/questions/13572667/ | |
// android-listview-detect-if-listview-data-fits-on-screen-without-scrolling | |
// this.post(mFitsOnScreenRunnable); | |
isItemsFitOnScreen();// 无需post执行此段代码 | |
// isFitOnScreen()方法可以替代下面的这段代码。 | |
// 假如一直采用getHeightByItems()和getMeasureHight()方法比较高度,滑动会很卡, | |
// 原因为两个方法不停的去Measure高度,这个很耗时,也很消耗资源,而且在UI线程执行。 | |
// 故只在mFitsOnScreenRunnable还没运行过的情况下使用该方法。 | |
// 请自行测试卡顿情况,谢谢! | |
// if(!mIsFitsOnScreenRun){ | |
// int totalItemHeight = getTotalItemsHeight(this); | |
// if(mListViewHeight == 0){ | |
// mListViewHeight = getMeasureHight(this); | |
// } | |
// | |
// if(totalItemHeight != -1 && mListViewHeight != 0){ | |
// if(totalItemHeight >= mListViewHeight){ | |
// mEnoughCount = true; | |
// }else{ | |
// mEnoughCount = false; | |
// } | |
// } | |
// } | |
// 若没有设置加载更多监听器,此功能自动屏蔽。 | |
if(mCanLoadMore && mLoadMoreListener == null){ | |
mCanLoadMore = false; | |
} | |
// 由于添加了HeaderView且HeaderView一直都存在。 | |
// 所以pTotalItemCount总数要在你设置的Data.size基础上先加一, | |
// 若开启加载更多,则pTotalItemCount再加一,也就是加上了FooterView个数。 | |
// 当滑到顶部时,FirstVisibleItem实际是HeaderView,FirstVisibleItem值为0, | |
// 当最后一个Item(如果FooterView存在,那么FooterView为最后一个Item)显示时, | |
// 恰好pFirstVisibleItem+pVisibleItemCount = pTotalItemCount。 | |
// 无论FooterView是否存在,请读者根据下面三行日志验证。 | |
mFirstItemIndex = pFirstVisibleItem; | |
mLastItemIndex = pFirstVisibleItem + pVisibleItemCount; | |
mCount = pTotalItemCount; | |
// MyLogger.showLogWithLineNum(4, "pFirstVisibleItem = "+pFirstVisibleItem); | |
// MyLogger.showLogWithLineNum(4, "pVisibleItemCount = "+pVisibleItemCount); | |
// MyLogger.showLogWithLineNum(4, "pTotalItemCount = "+pTotalItemCount); | |
if(mFooterState == FOOTER_LOADING || mIsDataLoadFinished) | |
return; | |
if(mCanLoadMore && mEnoughCount){// 存在加载更多功能并且Item数量足够 | |
if(getFooterViewsCount() == 0){ | |
addFooterView(); | |
MyLogger.showLogWithLineNum(5, "getFooterViewsCount() == 0 addFooterView"); | |
} | |
if(mIsAutoLoadMore){// 自动加载更多,FootView显示 “加载中...” | |
// 改变完状态之后,就无需重复改变。 | |
if(mFooterState != FOOTER_AUTO_LOAD_DONE){ | |
mFooterState = FOOTER_AUTO_LOAD_DONE; | |
changeFootViewByState(true); | |
MyLogger.showLogWithLineNum(5, "滚动的时候显示 加载中。。。"); | |
} | |
}else{// 手动加载更多,FootView显示 “点击加载” | |
// if判断原理同上 | |
if(mFooterState != FOOTER_MANUAL_LOAD_DONE){ | |
mFooterState = FOOTER_MANUAL_LOAD_DONE; | |
changeFootViewByState(true); | |
MyLogger.showLogWithLineNum(5, "滚动的时候显示 点击加载"); | |
} | |
} | |
}else if(mFooterView != null && mFooterView.getVisibility() == VISIBLE){ | |
// 突然关闭加载更多功能之后,我们要移除FootView。 | |
MyLogger.showLogWithLineNum(5,"滚动的时候 removeFooterView(endRootView);..."); | |
removeFooterView(); | |
} | |
} | |
/** | |
* 当关闭加载更多后,移除FooterView。 | |
*/ | |
private void removeFooterView(){ | |
changeFooterViewStateByIsAutoLoadMore(false); | |
mFooterView.setVisibility(View.GONE); | |
this.post(new Runnable() { | |
@Override | |
public void run() { | |
PtrListView.this.removeFooterView(mFooterView); | |
PtrListView.this.requestLayout(); | |
} | |
}); | |
} | |
/** | |
*判断是否到达底部并且是否可以加载更多。 | |
*/ | |
@Override | |
public void onScrollStateChanged(AbsListView pView, int pScrollState) { | |
if(mIsDataLoadFinished) | |
return; | |
if(mCanLoadMore && mEnoughCount){// 存在加载更多功能并且Item数量足够 | |
if (mLastItemIndex == mCount && pScrollState == SCROLL_STATE_IDLE) { | |
// 加载更多已经执行完毕 | |
if (mFooterState != FOOTER_LOADING) { | |
if(mIsAutoLoadMore){// 自动加载更多,我们让FootView显示 “更 多” | |
if(mCanRefresh){ | |
// 存在下拉刷新并且HeadView没有正在刷新时,FootView可以自动加载更多。 | |
if(mHeaderState != REFRESHING){ | |
onLoadMore(); | |
} | |
}else{// 没有下拉刷新,直接进行加载更多。 | |
onLoadMore(); | |
} | |
} | |
} | |
} | |
} | |
} | |
/** | |
* 改变加载更多状态 | |
*/ | |
private void changeFootViewByState(boolean isFooterViewVisible) { | |
// 允许加载更多 | |
switch (mFooterState) { | |
case FOOTER_LOADING:// 刷新中 | |
// 加载中... | |
if (mFooterLoadTipsTextView.getText().equals(R.string.p2refresh_doing_end_refresh)) { | |
break; | |
} | |
mFooterLoadTipsTextView.setText(R.string.p2refresh_doing_end_refresh); | |
mFooterLoadTipsTextView.setVisibility(View.VISIBLE); | |
mFooterLoadProgressBar.setVisibility(View.VISIBLE); | |
break; | |
case FOOTER_MANUAL_LOAD_DONE:// 手动刷新完成 | |
// 点击加载 | |
mFooterLoadTipsTextView.setText(R.string.p2refresh_end_click_load_more); | |
mFooterLoadTipsTextView.setVisibility(View.VISIBLE); | |
mFooterLoadProgressBar.setVisibility(View.GONE); | |
if(isFooterViewVisible){ | |
mFooterView.setVisibility(View.VISIBLE); | |
}else{ | |
mFooterView.setVisibility(View.GONE); | |
} | |
break; | |
case FOOTER_AUTO_LOAD_DONE:// 自动刷新完成 | |
// 加载中... | |
mFooterLoadTipsTextView.setText(R.string.p2refresh_doing_end_refresh); | |
mFooterLoadTipsTextView.setVisibility(View.VISIBLE); | |
mFooterLoadProgressBar.setVisibility(View.VISIBLE); | |
if(isFooterViewVisible){ | |
mFooterView.setVisibility(View.VISIBLE); | |
}else{ | |
mFooterView.setVisibility(View.GONE); | |
} | |
break; | |
} | |
} | |
/** | |
*原作者的,我没改动,请读者自行优化。 | |
*/ | |
public boolean onTouchEvent(MotionEvent event) { | |
if (mCanRefresh) { | |
if(mCanLoadMore && mFooterState == FOOTER_LOADING){ | |
// 如果存在加载更多功能,并且当前正在加载更多,默认不允许下拉刷新,必须加载完毕后才能使用。 | |
return super.onTouchEvent(event); | |
} | |
switch (event.getAction()) { | |
case MotionEvent.ACTION_DOWN: | |
if (mFirstItemIndex == 0 && !mIsRecored) { | |
mIsRecored = true; | |
mStartY = (int) event.getY(); | |
} | |
break; | |
case MotionEvent.ACTION_UP: | |
if (mHeaderState != REFRESHING) { | |
if (mHeaderState == DONE) { | |
} | |
if (mHeaderState == PULL_TO_REFRESH) { | |
mHeaderState = DONE; | |
changeHeaderViewByState(); | |
} | |
if (mHeaderState == RELEASE_TO_REFRESH) { | |
mHeaderState = REFRESHING; | |
changeHeaderViewByState(); | |
onRefresh(); | |
} | |
} | |
mIsRecored = false; | |
mIsBack = false; | |
break; | |
case MotionEvent.ACTION_MOVE: | |
final int tempY = (int) event.getY(); | |
// 当前可见的Item不是第一个,此时我们开始下拉,当第一个Item显示时,触发此代码段。 | |
// 开始纪录下拉的Y坐标,Header即将显示! | |
if (!mIsRecored && mFirstItemIndex == 0) { | |
mIsRecored = true; | |
mStartY = tempY; | |
} | |
if (mHeaderState != REFRESHING && mIsRecored) { | |
// 保证在设置padding的过程中,当前的位置一直是在head, | |
// 否则如果当列表超出屏幕的话,当在上推的时候,列表会同时进行滚动 | |
// 可以松手去刷新了 | |
if (mHeaderState == RELEASE_TO_REFRESH) { | |
setSelection(0); | |
// 往上推了,推到了屏幕足够掩盖head的程度,但是还没有推到全部掩盖的地步 | |
if (((tempY - mStartY) / RATIO < mHeaderViewHeight) | |
&& (tempY - mStartY) > 0) { | |
mHeaderState = PULL_TO_REFRESH; | |
changeHeaderViewByState(); | |
} | |
// 一下子推到顶了 | |
else if (tempY - mStartY <= 0) { | |
mHeaderState = DONE; | |
changeHeaderViewByState(); | |
} | |
// 往下拉了,或者还没有上推到屏幕顶部掩盖head的地步 | |
} | |
// 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态 | |
if (mHeaderState == PULL_TO_REFRESH) { | |
setSelection(0); | |
// 下拉到可以进入RELEASE_TO_REFRESH的状态 | |
if ((tempY - mStartY) / RATIO >= mHeaderViewHeight) { | |
mHeaderState = RELEASE_TO_REFRESH; | |
mIsBack = true; | |
changeHeaderViewByState(); | |
} else if (tempY - mStartY <= 0) { | |
mHeaderState = DONE; | |
changeHeaderViewByState(); | |
} | |
} | |
if (mHeaderState == DONE) { | |
if (tempY - mStartY > 0) { | |
mHeaderState = PULL_TO_REFRESH; | |
changeHeaderViewByState(); | |
} | |
} | |
if (mHeaderState == PULL_TO_REFRESH) { | |
mHeaderView.setPadding(0, (tempY - mStartY) / RATIO - mHeaderViewHeight, 0, 0); | |
} | |
if (mHeaderState == RELEASE_TO_REFRESH) { | |
int tryPadding = (tempY - mStartY) / RATIO - mHeaderViewHeight; | |
int maxPullDistance = mListViewHeight / 4; | |
if(mMaxPullDistance > 0){ | |
maxPullDistance = mMaxPullDistance; | |
} | |
// 若HeaderView底部和ListView顶部之间的距离 超过ListView高度的四分之一,我们不对HeaderView做Padding处理。 | |
if(mListViewHeight > 0 && (mHeaderView.getPaddingTop() + mHeaderViewHeight) > maxPullDistance){ | |
if((tempY - mLastMoveX) < 0 && mHeaderView.getPaddingTop() > 0){ | |
mHeaderView.setPadding(0, tryPadding, 0, 0); | |
} | |
}else{ | |
mHeaderView.setPadding(0, tryPadding, 0, 0); | |
mLastMoveX = tempY; | |
} | |
} | |
} | |
break; | |
} | |
} | |
return super.onTouchEvent(event); | |
} | |
/** | |
* 当HeadView状态改变时候,调用该方法,以更新界面 | |
*/ | |
private void changeHeaderViewByState() { | |
switch (mHeaderState) { | |
case RELEASE_TO_REFRESH: | |
mArrowImageView.setVisibility(View.VISIBLE); | |
mProgressBar.setVisibility(View.GONE); | |
mTipsTextView.setVisibility(View.VISIBLE); | |
mLastUpdatedTextView.setVisibility(View.VISIBLE); | |
mArrowImageView.clearAnimation(); | |
mArrowImageView.startAnimation(mArrowAnim); | |
// 松开刷新 | |
mTipsTextView.setText(R.string.p2refresh_release_refresh); | |
break; | |
case PULL_TO_REFRESH: | |
mProgressBar.setVisibility(View.GONE); | |
mTipsTextView.setVisibility(View.VISIBLE); | |
mLastUpdatedTextView.setVisibility(View.VISIBLE); | |
mArrowImageView.clearAnimation(); | |
mArrowImageView.setVisibility(View.VISIBLE); | |
// 是由RELEASE_TO_REFRESH状态转变来的 | |
if (mIsBack) { | |
mIsBack = false; | |
mArrowImageView.clearAnimation(); | |
mArrowImageView.startAnimation(mArrowReverseAnim); | |
// 下拉刷新 | |
mTipsTextView.setText(R.string.p2refresh_pull_to_refresh); | |
} else { | |
// 下拉刷新 | |
mTipsTextView.setText(R.string.p2refresh_pull_to_refresh); | |
} | |
break; | |
case REFRESHING: | |
mHeaderView.setPadding(0, 0, 0, 0); | |
mProgressBar.setVisibility(View.VISIBLE); | |
mArrowImageView.clearAnimation(); | |
mArrowImageView.setVisibility(View.GONE); | |
// 正在刷新... | |
mTipsTextView.setText(R.string.p2refresh_doing_head_refresh); | |
mLastUpdatedTextView.setVisibility(View.VISIBLE); | |
break; | |
case DONE: | |
mHeaderView.setPadding(0, -1 * mHeaderViewHeight, 0, 0); | |
mProgressBar.setVisibility(View.GONE); | |
mArrowImageView.clearAnimation(); | |
mArrowImageView.setImageResource(R.drawable.arrow); | |
// 下拉刷新 | |
mTipsTextView.setText(R.string.p2refresh_pull_to_refresh); | |
mLastUpdatedTextView.setVisibility(View.VISIBLE); | |
break; | |
} | |
} | |
/** | |
* 下拉刷新监听接口 | |
*/ | |
public interface OnRefreshListener { | |
public void onRefresh(); | |
} | |
/** | |
* 加载更多监听接口 | |
*/ | |
public interface OnLoadMoreListener { | |
public void onLoadMore(); | |
} | |
public void setOnRefreshListener(OnRefreshListener pRefreshListener) { | |
if(pRefreshListener != null){ | |
mRefreshListener = pRefreshListener; | |
mCanRefresh = true; | |
} | |
} | |
public void setOnLoadListener(OnLoadMoreListener pLoadMoreListener) { | |
if(pLoadMoreListener != null){ | |
mLoadMoreListener = pLoadMoreListener; | |
mCanLoadMore = true; | |
if(mCanLoadMore && getFooterViewsCount() == 0){ | |
addFooterView(); | |
} | |
} | |
} | |
/** | |
* 正在下拉刷新 | |
*/ | |
private void onRefresh() { | |
if (mRefreshListener != null) { | |
mRefreshListener.onRefresh(); | |
} | |
} | |
/** | |
* 下拉刷新完成 | |
*/ | |
public void onRefreshComplete() { | |
// 下拉刷新后是否显示第一条Item | |
if(mIsMoveToFirstItemAfterRefresh)setSelection(0); | |
mHeaderState = DONE; | |
// 最近更新: Time | |
mLastUpdatedTextView.setText( | |
getResources().getString(R.string.p2refresh_refresh_lasttime) + | |
new SimpleDateFormat(DATE_FORMAT_STR, Locale.CHINA).format(new Date())); | |
changeHeaderViewByState(); | |
// 如果数据加载完成了,那么下拉刷新之后,应该把标志位置为false,允许加载更多。 | |
if(mIsDataLoadFinished){ | |
mIsDataLoadFinished = false; | |
if(mCanLoadMore){ | |
changeFooterViewStateByIsAutoLoadMore(true); | |
} | |
} | |
} | |
/** | |
* 正在加载更多,FootView显示 :加载中... | |
*/ | |
private void onLoadMore() { | |
mFooterState = FOOTER_LOADING; | |
// 加载中... | |
mFooterLoadTipsTextView.setText(R.string.p2refresh_doing_end_refresh); | |
mFooterLoadTipsTextView.setVisibility(View.VISIBLE); | |
mFooterLoadProgressBar.setVisibility(View.VISIBLE); | |
if (mLoadMoreListener != null) { | |
mLoadMoreListener.onLoadMore(); | |
} | |
} | |
/** | |
* 加载更多完成 | |
*/ | |
public void onLoadMoreComplete() { | |
if(mIsAutoLoadMore){ | |
mFooterState = FOOTER_AUTO_LOAD_DONE; | |
}else{ | |
mFooterState = FOOTER_MANUAL_LOAD_DONE; | |
} | |
changeFootViewByState(true); | |
} | |
/** | |
* 主要更新一下刷新时间啦! | |
* @param adapter | |
*/ | |
public void setAdapter(BaseAdapter adapter) { | |
// 最近更新: Time | |
mLastUpdatedTextView.setText( | |
getResources().getString(R.string.p2refresh_refresh_lasttime) + | |
new SimpleDateFormat(DATE_FORMAT_STR, Locale.CHINA).format(new Date())); | |
super.setAdapter(adapter); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment