在上一篇博文的最后,我说要写一个下拉刷新的ListView和RecyclerView,并且可以直接使用QQheader
先说声对不起了,上一篇博文的那种设计绝对有问题,会出现很多重复代码!我的脸被自己打了,好疼 /(ㄒoㄒ)/~~
上一篇博文:Android 怎么实现支持所有View的通用的下拉刷新控件
本篇博文准备讲如何实现下拉刷新和上拉加载,写的比较烂,篇幅比较长。
先预览下最终效果吧,不然我怕你们看不下去=。=
一、下拉刷新的实现方式
在上一篇博文中,我们通过移动header的位置来实现下拉刷新。
但是这种方式在ListView中并不是十分合适,所以我需要修改一下实现方式。
通过修改header的高度的方式来实现下拉刷新。
二、加载更多的实现方式
加载更多一般情况下有三种方式:
1. 滑动到最底部的时候显示footer,上拉加载更多
2. 滑动到底部的时候,自动加载更多
3. 快要到底部的时候(还没到底部,比如bilibili客户端),自动加载更多
而博主我是用第二种, 虽然三种都可以同时实现,但是博主表示懒癌发作了=。=
关于监听滚动事件也有两种方式:
一是监听onScrollStateChange,二是监听onScroll。
这两者有什么区别呢,为什么我会选择onScroll抛弃onScrollStateChange:
在一次滚动事件中,onScrollStateChange只会回调两次或三次,分别是scroll、fling、idle。那么,第三种方式就不合适了,你并不能知道你当前滑动到哪个位置了
在界面初始化时, onScroll会执行, 而ScrollState不会。
有人可能会问,这有什么用,不会执行不是更好么?
如果你分页加载,首次加载的时候很奇葩的数据没有满屏怎么办,这时候是不是应该再加载一页数据。而onScroll因为在初始化时会执行,所以不需要做任何事情就能自适应了。
三、ListView中的事件和滚动的处理
下面代码以ListView为例,而RecyclerView代码稍微会多一些,等以后我完成整个框架的时候直接上传到Github再说。
我们先继承ListView,处理一下事件
因为打算将逻辑都交给header和footer处理,所以代码出奇的少,干干净净。
也因为将逻辑交给header/footer处理,ListView并不知道什么时候调用刷新方法。所以当header进入刷新状态时,就要回调ListView的刷新方法。所以我们需要定义一个接口。
/**
* Created by AItsuki on 2016/6/22.
* 需要使用刷新header的控件必须实现此接口
*/
public interface Refreshing {
void onRefreshCallBack();
}
/**
* Created by AItsuki on 2016/6/24.
* 同Refreshing
*/
public interface Loading {
void onLoadMoreCallBack();
}
/**
* Created by AItsuki on 2016/6/21.
*/
public class AListView extends ListView implements AbsListView.OnScrollListener, Refreshing, Loading {
private int activePointerId;
private float lastY;
public AListView(Context context) {
super(context);
initView();
}
public AListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public AListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int actionMasked = ev.getActionMasked();
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
activePointerId = ev.getPointerId(0);
lastY = ev.getY(0);
// header和footer的按下处理在这里!
break;
case MotionEvent.ACTION_MOVE:
float y = ev.getY(MotionEventCompat.findPointerIndex(ev, activePointerId));
float diffY = y - lastY;
lastY = y;
// --------------header----------------------
if (getFirstVisiblePosition() == 0 && diffY > 0) {
// header的onMove在这处理
} else if (diffY < 0 && refreshHeader.getVisibleHeight() > 0) {
// 往上滑动,header回去的过程中,listView也会跟着滚动,导致滑动致header消失后,其实header高度还没有到0
// setSelection可以保证header一直处于顶部
setSelection(0);
// header的onMove在这处理
}
// --------------footer--------------------
int lastVisible = getLastVisiblePosition();
int lastPosition = getAdapter().getCount() - 1;
if (getFirstVisiblePosition() != 0 && lastVisible == lastPosition) {
// footer的onMove也在这里,让footer也能拉动,弹弹弹!
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
onSecondaryPointerDown(ev);
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// header和footer的手指松开处理在这里!
break;
}
return super.onTouchEvent(ev);
}
private void onSecondaryPointerDown(MotionEvent ev) {
int pointerIndex = MotionEventCompat.getActionIndex(ev);
lastY = ev.getY(pointerIndex);
activePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
if (pointerId == activePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastY = ev.getY(newPointerIndex);
activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// footer的onScroll事件在此~
}
/**
* Refreshing接口的方法, 当header进入刷新状态时会回调此方法
*/
@Override
public void onRefreshCallBack() {
}
/**
* Loading接口的方法,当footer进入加载状态时会回调此方法
*/
@Override
public void onLoadMoreCallBack() {
}
}
header和footer需要处理的地方都已经注释过了,接下来我们就需要在header和footer中处理事件了。
四、Header基类
相对于上一篇博客来说,header多了一个Failure状态,不然无论刷新成功还是失败,header都显示刷新成功不太好。
package com.aitsuki.library;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;
/**
* Created by AItsuki on 2016/6/21.
* RefreshHeader相当于一个容器,装着我们自定义的Header
* child通过getContentView方法将header传进来。
*/
public abstract class RefreshHeader extends LinearLayout {
private static final String TAG = "RefreshHeader";
private static final float DRAG_RATE = 0.5f;
// scroller duration
private static final int SCROLL_TO_TOP_DURATION = 800;
private static final int SCROLL_TO_REFRESH_DURATION = 250;
private static final long SHOW_COMPLETED_TIME = 500;
private static final long SHOW_FAILURE_TIME = 500;
private long showCompletedTime = SHOW_COMPLETED_TIME;
private long showFailureTime = SHOW_FAILURE_TIME;
private View contentView; // header
private int refreshHeight; // 刷新高度
private int maxDragDistance;
private boolean isTouch;
private State state = State.RESET;
private final AutoScroll autoScroll;
private Refreshing refreshing;
public RefreshHeader(Context context) {
super(context);
maxDragDistance = Utils.dip2px(context, 500); // 默认500dp
autoScroll = new AutoScroll();
// content由子类创建
contentView = getContentView();
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
addView(contentView, lp);
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
refreshHeight = getMeasuredHeight();
Log.e(TAG, "refreshHeight = " + refreshHeight);
}
protected void bindRefreshing(Refreshing refreshing) {
this.refreshing = refreshing;
}
protected void setVisibleHeight(int height) {
height = Math.max(0, height);
LayoutParams lp = (LayoutParams) contentView.getLayoutParams();
lp.height = height;
contentView.setLayoutParams(lp);
}
/**
* 设置成功状态时,header的停留时间
*/
public void setShowCompletedTime(long time) {
if (time >= 0) {
showCompletedTime = time;
}
}
/**
* 设置失败状态时,header的停留时间
*/
public void setShowFailureTime(long time) {
if (time >= 0) {
showFailureTime = time;
}
}
/**
* 设置最大下拉高度(默认300dp)
*
* @param distance 最大下拉高度, 单位px
*/
public void setMaxDragDistance(int distance) {
if (distance > 0) {
maxDragDistance = distance;
}
}
public int getVisibleHeight() {
return contentView.getLayoutParams().height;
}
public int getRefreshHeight() {
return refreshHeight;
}
public State getState() {
return state;
}
public boolean isTouch() {
return isTouch;
}
public void onPress() {
isTouch = true;
removeCallbacks(delayToScrollTopRunnable);
autoScroll.stop();
}
public void onMove(float offset) {
if (offset == 0) {
return;
}
float dragRate = DRAG_RATE;
if (offset > 0) {
offset = offset * dragRate + 0.5f;
if(getVisibleHeight() > refreshHeight) {
// 因为忽略了refreshHeight,所以这里会超出最大高度,直接价格判断省事。
if(getVisibleHeight() >= maxDragDistance) {
offset = 0;
} else {
float extra = getVisibleHeight() - refreshHeight;
float extraPercent = Math.min(1, extra / maxDragDistance);
dragRate = Utils.calcDragRate(extraPercent);
offset = offset * dragRate + 0.5f;
}
}
}
int target = (int) Math.max(0, getVisibleHeight() + offset);
// 1. 在RESET状态时,第一次下拉出现header的时候,设置状态变成PULL
if (state == State.RESET && getVisibleHeight() == 0 && target > 0) {
changeState(State.PULL);
}
// 2. 在PULL或者COMPLETE状态时,header回到顶部的时候,状态变回RESET
if (getVisibleHeight() > 0 && target <= 0) {
if (state == State.PULL || state == State.COMPLETE || state == State.FAILURE) {
changeState(State.RESET);
}
}
// 3. 如果是从底部回到顶部的过程(往上滚动),并且手指是松开状态, 并且当前是PULL状态,状态变成LOADING
if (state == State.PULL && !isTouch && getVisibleHeight() > refreshHeight && target <= refreshHeight) {
// 这时候我们需要强制停止autoScroll
autoScroll.stop();
changeState(State.REFRESHING);
target = refreshHeight;
}
setVisibleHeight(target);
Log.e("123123", "onMove: height = "+ getVisibleHeight());
onPositionChange();
}
/**
* 松开手指,滚动到刷新高度或者滚动回顶部
*/
public void onRelease() {
isTouch = false;
if (state == State.REFRESHING) {
if (getVisibleHeight() > refreshHeight) {
autoScroll.scrollTo(refreshHeight, SCROLL_TO_REFRESH_DURATION);
}
} else {
autoScroll.scrollTo(0, SCROLL_TO_TOP_DURATION);
}
}
public void setRefreshComplete(boolean success) {
if (success) {
changeState(State.COMPLETE);
} else {
changeState(State.FAILURE);
}
if (getVisibleHeight() == 0) {
changeState(State.RESET);
return;
}
if (!isTouch && getVisibleHeight() > 0) {
if (success) {
postDelayed(delayToScrollTopRunnable, showCompletedTime);
} else {
postDelayed(delayToScrollTopRunnable, showFailureTime);
}
}
}
private void changeState(State state) {
Log.e(TAG, "changeState: " + state.name());
this.state = state;
switch (state) {
case RESET:
onReset();
break;
case PULL:
onPullStart();
break;
case REFRESHING:
onRefreshing();
if (refreshing != null) {
refreshing.onRefreshCallBack();
}
break;
case COMPLETE:
onComplete();
break;
case FAILURE:
onFailure();
break;
}
}
/**
* header的内容
*
* @return view
*/
public abstract View getContentView();
/**
* header回到顶部了
*/
public abstract void onReset();
/**
* header被下拉了
*/
public abstract void onPullStart();
/**
* header高度变化的回调
*/
public abstract void onPositionChange();
/**
* header正在刷新
*/
public abstract void onRefreshing();
/**
* header刷新成功
*/
public abstract void onComplete();
/**
* header刷新失败
*/
public abstract void onFailure();
/**
* 自动刷新
*/
public void autoRefresh() {
if (state != State.RESET) {
return;
}
// post可以保证View已经测量完毕
post(new Runnable() {
@Override
public void run() {
setVisibleHeight(refreshHeight);
changeState(State.REFRESHING);
}
});
}
private class AutoScroll implements Runnable {
private Scroller scroller;
private int lastY;
public AutoScroll() {
scroller = new Scroller(getContext());
}
@Override
public void run() {
boolean finished = !scroller.computeScrollOffset() || scroller.isFinished();
if (!finished) {
int currY = scroller.getCurrY();
int offset = currY - lastY;
lastY = currY;
onMove(offset);
post(this);
} else {
stop();
}
}
public void scrollTo(int to, int duration) {
int from = getVisibleHeight();
int distance = to - from;
stop();
if (distance == 0) {
return;
}
scroller.startScroll(0, 0, 0, distance, duration);
post(this);
}
private void stop() {
removeCallbacks(this);
if (!scroller.isFinished()) {
scroller.forceFinished(true);
}
lastY = 0;
}
}
public enum State {
RESET, PULL, REFRESHING, FAILURE, COMPLETE
}
// 刷新成功,显示500ms成功状态再滚动回顶部
private Runnable delayToScrollTopRunnable = new Runnable() {
@Override
public void run() {
autoScroll.scrollTo(0, SCROLL_TO_TOP_DURATION);
}
};
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (autoScroll != null) {
autoScroll.stop();
}
if (delayToScrollTopRunnable != null) {
removeCallbacks(delayToScrollTopRunnable);
delayToScrollTopRunnable = null;
}
}
}
具体的逻辑不多解释,因为上一篇博客中已经将事件处理的逻辑说的很清楚了,其实是懒_(:з」∠)_
五、Footer基类
footer的状态有四个RESET, LOADING, FAILURE, NO_MORE
reset:因为加载数据后,footer已经看不见了,所以没必要success状态,直接回到reset就行了
failure: 刷新失败,显示failure状态,用户点击footer就可以重新加载。
no_more:没有更多数据的时候进入这个状态,footer高度设置为0,当下拉刷新或者手动设置后会重新显示。
在这里特别说明一点,footer的contentView不能设置点击事件,事件应该在ListView中有onItemClick处理。
因为如果footer设置了点击事件,那么事件处理会出现问题。因为多点触控的原因,是通过getY() getX()获取坐标的,当手指在footer中滑动时,获取到的坐标位置是相对于footer的。
package com.aitsuki.library;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;
/**
* Created by AItsuki on 2016/6/23.
* 逻辑:
* 拉到最底部的时候显示footer -- 从reset进入loading状态
* 加载成功后,隐藏footer
* 加载失败,点击footer可以重新加载
* <p/>
* 1. loading失败 -- 到failure状态
* 2. loading成功 -- 到Reset状态,隐藏footer
*/
public abstract class LoadMoreFooter extends LinearLayout {
private static final float DRAG_RATE = 0.5f;
private final AutoScroll autoScroll;
private final int maxDragDistance;
private View contentView; // footer
private State state = State.RESET;
private final int footerHeight;
private Loading loading;
private boolean canLoadMore = true;
public LoadMoreFooter(Context context) {
super(context);
maxDragDistance = Utils.dip2px(context, 500); // 默认500dp
autoScroll = new AutoScroll();
contentView = getContentView();
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
addView(contentView, lp);
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footerHeight = getMeasuredHeight();
}
protected void bindLoading(Loading loading) {
this.loading = loading;
}
protected void setVisibleHeight(int height) {
LayoutParams lp = (LayoutParams) contentView.getLayoutParams();
lp.height = height;
contentView.setLayoutParams(lp);
}
public int getVisibleHeight() {
return contentView.getLayoutParams().height;
}
protected void setLoadMoreComplete(boolean successful) {
if (successful) {
changeState(State.RESET);
} else {
changeState(State.FAILURE);
}
}
protected void onScroll(int lastVisiblePosition, int count) {
// 当没有数据的时候,不显示footer
if(count <= 0) {
contentView.setVisibility(GONE);
} else if(count > 0 && contentView.getVisibility() == GONE) {
contentView.setVisibility(VISIBLE);
}
if (count > 0 && lastVisiblePosition >= count && state == State.RESET && canLoadMore) {
changeState(LoadMoreFooter.State.LOADING);
}
}
protected void onPress() {
autoScroll.stop();
}
protected void onRelease() {
int height = canLoadMore ? footerHeight : 0;
if(getVisibleHeight() > height) {
autoScroll.scrollTo(height, 250);
}
}
public State getState() {
return state;
}
protected void changeState(State state) {
this.state = state;
switch (state) {
case RESET:
onReset();
break;
case LOADING:
onLoading();
if (loading != null) {
loading.onLoadMoreCallBack();
}
break;
case FAILURE:
onFailure();
break;
case NO_MORE:
onNoMore();
break;
}
}
protected void onMove(float offset) {
if (offset == 0) {
return;
}
float dragRate = DRAG_RATE;
if (offset < 0) {
offset = offset * dragRate - 0.5f;
if(getVisibleHeight() > footerHeight) {
// 因为忽略了refreshHeight,所以这里会超出最大高度,直接价格判断省事。
if(getVisibleHeight() >= maxDragDistance) {
offset = 0;
} else {
float extra = getVisibleHeight() - footerHeight;
float extraPercent = Math.min(1, extra / maxDragDistance);
dragRate = Utils.calcDragRate(extraPercent);
offset = offset * dragRate - 0.5f;
}
}
}
// 往上滑动的时候才增加footer的高度,所以offset为负数。
int height = canLoadMore ? footerHeight : 0;
int target = (int) Math.max(height, getVisibleHeight() - offset);
setVisibleHeight(target);
}
protected void setCanLoadMore(boolean canLoadMore) {
// 设置不能加载更多
if(!canLoadMore && state != State.NO_MORE) {
changeState(State.NO_MORE);
setVisibleHeight(0);
this.canLoadMore = false;
} else if(canLoadMore && state == State.NO_MORE) {
changeState(State.RESET);
setVisibleHeight(footerHeight);
this.canLoadMore = true;
}
}
private class AutoScroll implements Runnable {
private Scroller scroller;
private int lastY;
public AutoScroll() {
scroller = new Scroller(getContext());
}
@Override
public void run() {
boolean finished = !scroller.computeScrollOffset() || scroller.isFinished();
if (!finished) {
int currY = scroller.getCurrY();
int offset = currY - lastY;
lastY = currY;
onMove(offset);
post(this);
} else {
stop();
}
}
public void scrollTo(int to, int duration) {
int from = getVisibleHeight();
int distance = to - from;
stop();
if (distance == 0) {
return;
}
scroller.startScroll(0, 0, 0, -distance, duration);
post(this);
}
private void stop() {
removeCallbacks(this);
if (!scroller.isFinished()) {
scroller.forceFinished(true);
}
lastY = 0;
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (autoScroll != null) {
autoScroll.stop();
}
}
protected void onItemClick() {
if(state == State.FAILURE) {
changeState(State.LOADING);
}
}
public enum State {
RESET, LOADING, FAILURE, NO_MORE
}
protected abstract View getContentView();
protected abstract void onReset();
protected abstract void onNoMore();
protected abstract void onLoading();
protected abstract void onFailure();
}
六、 AListView的完整代码
package com.aitsuki.library;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListView;
/**
* Created by AItsuki on 2016/6/21.
* 下拉刷新:支持多点触控,支持自定义header,完美体验!
* 加载更多:在这里有两种方式
* 1. 监听scrollState,在idle状态下并且到达listView底部,显示加载更多
* 2. 监听scrollState,滚动到某个位置(比如倒数第3个item时就开始loadmore)
*/
public class AListView extends ListView implements AbsListView.OnScrollListener, AdapterView.OnItemClickListener,Refreshing, Loading {
private int activePointerId;
private float lastY;
private OnRefreshListener onRefreshListener;
private OnLoadMoreListener onLoadMoreListener;
private RefreshHeader refreshHeader;
private LoadMoreFooter loadMoreFooter;
private OnScrollListener scrollListener; // user's scroll listener
private OnItemClickListener onItemClickListener; // user's itemClick listener
private boolean waitingLoadingCompletedToRefresh; // 等待加载更多成功后去调用刷新
private boolean waitingRefreshCompletedToLoadMore; // 等待刷新成功后去调用加载更多
public enum CallBackMode {
NORMAL, ENQUEUE
}
private CallBackMode callBackMode = CallBackMode.ENQUEUE;
public AListView(Context context) {
super(context);
initView();
}
public AListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public AListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public void setCanLoadMore(boolean canLoadMore) {
loadMoreFooter.setCanLoadMore(canLoadMore);
}
private void initView() {
super.setOnScrollListener(this);
super.setOnItemClickListener(this);
setOverScrollMode(OVER_SCROLL_NEVER);
QQHeader QQHeader = new QQHeader(getContext());
setRefreshHeader(QQHeader);
QQFooter footer = new QQFooter(getContext());
setLoadMoreFooter(footer);
}
/**
* 设置刷新头部
* @param refreshHeader header
*/
public void setRefreshHeader(RefreshHeader refreshHeader) {
if (refreshHeader != null && this.refreshHeader != refreshHeader) {
removeHeaderView(this.refreshHeader);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
refreshHeader.setLayoutParams(lp);
refreshHeader.bindRefreshing(this);
addHeaderView(refreshHeader, null, false);
this.refreshHeader = refreshHeader;
}
}
/**
* 设置加载更多的footer
* @param footer footer
*/
public void setLoadMoreFooter(LoadMoreFooter footer) {
if (footer != null && loadMoreFooter != footer) {
removeFooterView(footer);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
footer.setLayoutParams(lp);
footer.bindLoading(this);
addFooterView(footer);
this.loadMoreFooter = footer;
}
}
/**
* 刷新侦听
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
this.onRefreshListener = onRefreshListener;
}
/**
* 加载更多侦听
*/
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
/**
* 当数据加载完毕后,需要调用此方法让header回到原位
* @param success 刷新成功,刷新失败(header的状态不一致:complete或者failure)
*/
public void refreshComplete(boolean success) {
refreshHeader.setRefreshComplete(success);
// 刷新成功后,将failure状态重置
if (success && loadMoreFooter.getState() == LoadMoreFooter.State.FAILURE) {
loadMoreFooter.changeState(LoadMoreFooter.State.RESET);
}
// 刷新成功后,如果需要加载更多,那么回调onLoadMore
if (waitingRefreshCompletedToLoadMore) {
onLoadMoreCallBack();
waitingRefreshCompletedToLoadMore = false;
}
}
/**
* 当数据加载完毕后,需要调用此方法重置footer的状态
* @param success 刷新成功:隐藏了。 刷新失败,会显示失败状态(点击重试)
*/
public void loadMoreComplete(boolean success) {
loadMoreFooter.setLoadMoreComplete(success);
Log.e("123123", "loadMoreComplete");
// 加载成功后,如果需要刷新,那么回调onRefresh
if (waitingLoadingCompletedToRefresh) {
onRefreshCallBack();
waitingLoadingCompletedToRefresh = false;
}
}
/**
* 自动刷新
*/
public void autoRefresh() {
refreshHeader.autoRefresh();
}
/**
* 当刷新和加载更多同时进行时,可能会导致分页加载时的页面错误。<br/>
* 可以通过此方法设置处理方式:<br/>
* NORMAL: 不做处理,但是开发者必须自己处理。<br/>
* ENQUEUE: 队列模式,刷新和加载更多同时进行时,只会回调其中一个,成功后再回调另一个(默认ENQUEUE)
*/
public void setCallBackMode(CallBackMode callBackMode) {
this.callBackMode = callBackMode;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int actionMasked = ev.getActionMasked();
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
activePointerId = ev.getPointerId(0);
lastY = ev.getY(0);
refreshHeader.onPress();
loadMoreFooter.onPress();
break;
case MotionEvent.ACTION_MOVE:
float y = ev.getY(MotionEventCompat.findPointerIndex(ev, activePointerId));
float diffY = y - lastY;
lastY = y;
// --------------header----------------------
if (getFirstVisiblePosition() == 0 && diffY > 0) {
refreshHeader.onMove(diffY);
} else if (diffY < 0 && refreshHeader.getVisibleHeight() > 0) {
// 往上滑动,header回去的过程中,listView也会跟着滚动,导致滑动致header消失后,其实header高度还没有到0
// setSelection可以保证header一直处于顶部
refreshHeader.onMove(diffY);
setSelection(0);
}
// --------------footer--------------------
int lastVisible = getLastVisiblePosition();
int lastPosition = getAdapter().getCount() - 1;
if (getFirstVisiblePosition() != 0 && lastVisible == lastPosition) {
loadMoreFooter.onMove(diffY);
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
onSecondaryPointerDown(ev);
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
refreshHeader.onRelease();
loadMoreFooter.onRelease();
break;
}
return super.onTouchEvent(ev);
}
private void onSecondaryPointerDown(MotionEvent ev) {
int pointerIndex = MotionEventCompat.getActionIndex(ev);
lastY = ev.getY(pointerIndex);
activePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
if (pointerId == activePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastY = ev.getY(newPointerIndex);
activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
}
}
/**
* Refreshing接口的方法, 当header进入刷新状态时会回调此方法
*/
@Override
public void onRefreshCallBack() {
if (onRefreshListener != null) {
if (loadMoreFooter.getState() == LoadMoreFooter.State.LOADING
&& callBackMode == CallBackMode.ENQUEUE) {
// 等待加载更多完毕后回调onRefresh方法
waitingLoadingCompletedToRefresh = true;
} else {
onRefreshListener.onRefresh();
}
}
}
/**
* Loading接口的方法,当footer进入加载状态时会回调此方法
*/
@Override
public void onLoadMoreCallBack() {
if (onLoadMoreListener != null) {
if (refreshHeader.getState() == RefreshHeader.State.REFRESHING
&& callBackMode == CallBackMode.ENQUEUE) {
// 等待刷新完毕后回调onLoadMore方法
waitingRefreshCompletedToLoadMore = true;
} else {
onLoadMoreListener.onLoadMore();
}
}
}
@Override
public void setOnScrollListener(OnScrollListener l) {
this.scrollListener = l;
}
/**
* onScrollStateChanged和onScroll的区别:
* <p/>
* ListView滚动时,会一直执行onScroll。 而scrollState只会执行三次
* <p/>
* 在初始化时, onScroll会执行, 而ScrollState不会
* <p/>
* 在顶部或者底部滑动时,onScroll不会执行, 而ScrollState执行
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollListener != null) {
scrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (scrollListener != null) {
scrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
int lastVisiblePosition = getLastVisiblePosition();
int count = totalItemCount - getHeaderViewsCount() - getFooterViewsCount();
if (loadMoreFooter != null) {
loadMoreFooter.onScroll(lastVisiblePosition, count);
}
}
@Override
public void setOnItemClickListener(OnItemClickListener listener) {
onItemClickListener = listener;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
/*
* 在加载更多成功的那一刻点击item,可能会报空指针
* 因为此时loadMoreFooter这个item已经从listView中暂时移除
* 所以在这里做了parent判断
*/
int footerPosition = -1;
ViewParent viewParent = loadMoreFooter.getParent();
if(viewParent != null) {
footerPosition = getPositionForView(loadMoreFooter);
if(position == footerPosition) {
loadMoreFooter.onItemClick();
}
}
if(onItemClickListener != null && position != footerPosition) {
onItemClickListener.onItemClick(parent, view, position, id);
}
}
}