注意到里面好多类似这样的方法public void setReleaseLabel(CharSequence releaseLabel, Mode mode) {
getLoadingLayoutProxy(mode.showHeaderLoadingLayout(),
mode.showFooterLoadingLayout()).setReleaseLabel(releaseLabel);
},这些都是ILoadingLayout这个接口的方法,看来以前代码设计者是把状态改变时的lable改变写在了基类里,然后子类直接调用方法来改变的。但是现在应该没有什么用了,因为headlayout是继承的LoadingLayout,而LoadingLayout实现了IloadingLayout接口。只需要在自定义的LoadingLayout里实现方法就可以了,基类会在适当的时机调用headlayout.setxxxlabel方法,而不是调用自己的setxxxLable了。全部删除后demo中只有一个ListAct有错误mPullRefreshListView.setOnRefreshListener(new OnRefreshListener<ListView>() {
@Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
String label = DateUtils.formatDateTime(getApplicationContext(), System.currentTimeMillis(),
DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL);
// Update the LastUpdatedLabel
// refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);
refreshView.getHeaderLayout().setLastUpdatedLabel(label);
// Do work to refresh the list here.
new GetDataTask().execute();
}
});
他在回调中还是使用的refreshView.getLoadingLayoutProxy().这里建议将基类原来的getHeaderLayout()设置为public,这样也为自定义的LoadingLayout做准备,也可以封装一部分常用的属性在基类中,比如setLastUpdatedLabel(label){内部调用headlayout去处理}其实LoadingLayout和基类之间的关系还是有点“深”,后面尝试分离一下,否则这样虽然有 一定灵活度,但其实还是定死了几个东西,比如你即使自定义,也必须有一堆很必须的值,比如updateText,可能有些时候你并不需要它,demo中在不需要的时候,其实只是没有设置text,所以就看不到。但不代表控件本身不存在。我觉得可以在LoadingLayout中只实现state周期必须的几个东西,然后在子类中去设置文本之类的操作,嗯,这些读者们自行考虑吧,其实感觉最重要的是接下来要看的ontouch事件,以及各个子类对临界值的判断。如此则可以写出更多灵活的代码,比如有些app,下拉的时候,从上面出来个发条,但是listview本身并不动,这样的情况使用博文介绍的组件是不能实现的,当然也许可以通过变动来实现,比如上篇说了,是通过padding定位header,然后通过layout自己的scrollto属性来呈现下拉的。那么问题来了,挖掘技术哪家强?。。我去,是想实现listview不动的情况应该怎么办?学会了这里的逻辑,完全可以继承一个RelativeLayout了,或者尝试改变lsitview和hear之间的margain试试?(但线性布局,它会盖着header。。。)感觉应该使用RelativeLayout,这样感觉好一点,方便操控,学着学着就想推倒了。言归正传,先把核心学到手,后面随意优化(其实也不难,使用layout(int top,int left...)来替换掉scrollto就可以了,scrollto在这里其实占了个大便宜而已,带来的劣势也看到了,不方便重叠布局Head)。
大家自行复习 onInterceptTouchEvent 和onTouch吧,或者以后我会补一篇相关文章。总结就是onInterceptTouchEvent从父类向子类传递,遇到true则停止并交由当时的子类来执行onTouch,ontouch开始反传,遇true则自己消耗掉事件及以后的事件。
@Override
public final boolean onInterceptTouchEvent(MotionEvent event) {
if (!isPullToRefreshEnabled()) {
return false;// 如果设置为不能fresh,则不拦截,交给子view处理,即所有touch事件交给了listview,上下的headlayout等也一直invisiable
}
final int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
mIsBeingDragged = false;
return false;
}
// 动作不是按下,不是松手(差不多也就剩下move了)并且当前在拖动状态,则拦截给自己的ontouch处理
if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
return true;//这里就会跳转到自己的ontouch,同时所有的子view都不再可能接收ontouch事件了
}
// mIsBeingDragged默认是false的,所以,当第一次move并且未将mIsBeingDragged设置true时,就会执行这里
switch (action) {
case MotionEvent.ACTION_MOVE: {
// If we're refreshing, and the flag is set. Eat all MOVE events
// 如何设置了 刷新中不能滑动,并且当前在刷新中,则拦截处理,这样listview就接收不到事件了,自然也就不能滑动了
if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
return true;
}
if (isReadyForPull()) {
final float y = event.getY(), x = event.getX();
final float diff, oppositeDiff, absDiff;
// We need to use the correct values, based on scroll
// direction
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
diff = x - mLastMotionX;
oppositeDiff = y - mLastMotionY;
break;
case VERTICAL:
default:
diff = y - mLastMotionY;
oppositeDiff = x - mLastMotionX;
break;
}
absDiff = Math.abs(diff);
// 移动距离超过了判断为滑动的最小距离mtouchslop..并且(按纵向分析)(纵向的移动距离大于横向 ||
// !mFilterTouchEvents)
if (absDiff > mTouchSlop
&& (!mFilterTouchEvents || absDiff > Math
.abs(oppositeDiff))) {
if (mMode.showHeaderLoadingLayout() && diff >= 1f
&& isReadyForPullStart()) {// 如果当前可以显示header(其实就是看mModel是可以下拉刷新或者上下都可以拉的情况),并且移动差距>1f(向下拉动),并且子类判断已经准备好可以开始下拉了(比如listivew的话第一个item已经到头了)//这里重点就两个,一是分辨出当前是在向上还是向下做运动,二是子类要知道现在是否到顶或底了
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_START;
}
} else if (mMode.showFooterLoadingLayout() && diff <= -1f
&& isReadyForPullEnd()) {
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_END;
}
}
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
mIsBeingDragged = false;
}
break;
}
}
return mIsBeingDragged;
}
@Override
public final boolean onTouchEvent(MotionEvent event) {
if (!isPullToRefreshEnabled()) {
return false;
}
// 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) {
//getEdgeFlags():当事件类型是ActionDown时可以通过此方法获得,手指触控开始的边界. 如果是的话,有如下几种值:EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
if (mIsBeingDragged) {
mLastMotionY = event.getY();
mLastMotionX = event.getX();
pullEvent();
return true;
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
return true;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (mIsBeingDragged) {
mIsBeingDragged = false;
if (mState == State.RELEASE_TO_REFRESH
&& (null != mOnRefreshListener || null != mOnRefreshListener2)) {
setState(State.REFRESHING, true);
return true;
}
// If we're already refreshing, just scroll back to the top
if (isRefreshing()) {
smoothScrollTo(0);
return true;
}
// If we haven't returned by here, then we're not in a state
// to pull, so just reset
setState(State.RESET);
return true;
}
break;
}
}
return false;
}
ontouch里面最重要的两个个方法就是:(擦,粘两个方法的代码就超出字数限制了)
1、//基本就是按滑动的位置来改变状态,不同的state也会在LoadingLayout里有相关的state设置,比如protected void onPullToRefresh() {
switch (mCurrentMode) {
case PULL_FROM_END:
mFooterLayout.pullToRefresh();
break;
case PULL_FROM_START:
mHeaderLayout.pullToRefresh();
break;
default:
// NO-OP
break;
}
}
在调用这个方法之前的move里面会调用onpullevent(),里面会调用 mFooterLayout.onPull(scale);这个是把当前的拖动百分比给Loadinglayout,这样就可以根据百分比显示旋转或翻转的动画了。onpullevent里面有个注意点,它是根据当前拖拽的距离和Loadinglayout的实际内容距离做的比较,它有个innerLayout,我们知道LoadingLayout有listview的0.6倍,而header的实际大小是xml定义的,而且他放在了LoadingLayout的底部(foot反之),根据这样来显示“向下拉动刷新”和“释放开始刷新”之类的字样提醒。同时会调用setState(State.PULL_TO_REFRESH);setState(State.RELEASE_TO_REFRESH);,当然字样提醒就是在对应的onReleaseToRefresh事件里,去调用mHeaderLayout.releaseTorefresh去实现的。(其实这里就是另一个方法了:onpullevent,不过贴代码就超了)
final void setState(State state, final boolean... params) {
mState = state;
if (DEBUG) {
Log.d(LOG_TAG, "State: " + mState.name());
}
switch (mState) {
case RESET:
onReset();
break;
case PULL_TO_REFRESH:
onPullToRefresh();
break;
case RELEASE_TO_REFRESH:
onReleaseToRefresh();
break;
case REFRESHING:
case MANUAL_REFRESHING:
onRefreshing(params[0]);
break;
case OVERSCROLLING:
// NO-OP
break;
}
// Call OnPullEventListener
if (null != mOnPullEventListener) {
mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
}
}
ps:其实分离Loadinglayout还是挺容易的,因为现在LoadingLayout,它管的事有点多,他管的事就是有相同布局风格的代表,比如都有updatelabel,都有mHeaderImage等等。其实他应该还有个父类,或者或他应该去掉多余的方法,再拥有个子类(因为fresh的base基类里面的headLayout和footLayout都是LoadingLayout类型,所以不适合再做父类,或者做了父类后就要把基类的head foot的对应类型改成父类),比如叫做 standerLoadinLayout(标准型Loading),然后Rotato和Flip两个都继承stantder,stander中,可以做一些原来的LoadingLayout的事情,比如拥有mHeaderText控件,在reset中可以处理settext等。我们需要自定义的时候,就可以继承简化的LoadingLayout,来做一个非常随意的独特布局,比如一个animations的帧动画。(比如下拉过程中,小人在走,松手后,小人开始跑步。比如下拉的时候火箭开始点火,松手就开始飞,加载完就飞出屏幕了等等)
到这里,基类基本就看完了,其实边写边看,感觉印象很深,写着写着也容易停下来思考问题,下边就开始看各个子类了,大体看了下,都非常简单,因为父类做了不少事情,子类只是提供了不同的临界判断标准而已。再有基类的方法有onSaveInstanceState,存储状态就不讲了,他自己存完必须的状态后,会调用子类的ab方法onPtrsave,方便不通的子类执行不通的存储,比如Listview可能希望存储当前是第几个index。webview更希望存储当前加载的哪个url。:)