本篇笔记整理了ListView上拉加载更多及下拉刷新的实现,两者实现都需要用到 OnScrollListener 的事件监听,转载请注明出处
实现滚动监听,首先需要通过实现OnScrollListener 接口,重写
onScrollStateChanged 和 onScroll两个方法,分别用于监听ListView滑动状态的变化,和屏幕滚动
// 监听滑动状态的变化
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// OnScrollListener.SCROLL_STATE_FLING; //屏幕处于甩动状态
// OnScrollListener.SCROLL_STATE_IDLE; //停止滑动状态
// OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;// 手指接触状态
// 记录当前滑动状态
}
onScroll:监听屏幕滑动,并记录当前页面item显示情况
// 监听屏幕滚动的item的数量
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
onScroll中参数讲解:
应用:上拉刷新滑动到底部时加载一页新数据,显示到ListView底部,从而到达分页效果,避免一次性加载全部数据,提升了性能。
利用适配器对象的notifyDataSetChanged()方法。该方法的作用是通知适配器自己及与该数据有关的view,数据已经发生变动,要刷新自己、更新数据。
首先需要定义两个记录状态的全局变量
/** 是否正在加载中 */
private boolean mIsLoading;
/** 当前滑动状态 */
private int mCurrentScrollState;
接着ListView通过实现onScrollListener,复写onScrollStateChanged 用于保存当前滑动状态,复写onScrool判断是否该加载更多数据
// 监听滑动状态的变化
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 记录当前滑动状态
mCurrentScrollState = scrollState;
}
// 监听屏幕滚动的item的数量
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// 判断是否滑动到底部:第一个可见的个数与 所有可见Item个数之和 大于等于item总数
boolean isBottom = firstVisibleItem + visibleItemCount >= totalItemCount;
if (!mIsLoading && isBottom
&& (mCurrentScrollState != OnScrollListener.SCROLL_STATE_IDLE)) {
if (onLoadMoreListener != null) {
mIsLoading = true;
onLoadMoreListener.onLoadMore();
}
}
}
为方便查看,贴出全部代码也不过一百行,如果需要底部按钮 “加载更多”按钮可以通过ListView.AddFooter()实现,此外,在构造方法记得要绑定ListView的监听事件
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
import com.example.listview.R;
/** 滑到底部加载更多 */
public class LoadMoreListView extends ListView implements OnScrollListener {
/** 是否正在加载中 */
private boolean mIsLoading;
/** 当前滑动状态 */
private int mCurrentScrollState;
/** 底部控件 */
private View mFooterView;
private OnLoadMoreListener onLoadMoreListener;
/** * @param context * @param attrs * @param defStyle */
public LoadMoreListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
/** * @param context * @param attrs */
public LoadMoreListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
/** * @param context */
public LoadMoreListView(Context context) {
super(context);
initView(context);
}
/*** * 初始化控件,添加底部控件 * * @param context */
private void initView(Context context) {
mFooterView = LayoutInflater.from(context).inflate(
R.layout.load_more_footer, null);
addFooterView(mFooterView);
// 为自定义ListView控件绑定滚动监听事件
this.setOnScrollListener(this);
}
// 监听滑动状态的变化
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 记录当前滑动状态
mCurrentScrollState = scrollState;
}
// 监听屏幕滚动的item的数量
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// 判断是否滑动到底部:第一个可见的个数与 所有可见Item个数之和 大于等于item总数
boolean isBottom = firstVisibleItem + visibleItemCount >= totalItemCount;
if (!mIsLoading && isBottom
&& (mCurrentScrollState != OnScrollListener.SCROLL_STATE_IDLE)) {
if (onLoadMoreListener != null) {
mIsLoading = true;
onLoadMoreListener.onLoadMore();
}
}
}
/** * Notify the loading more operation has finished */
public void onLoadMoreComplete() {
mIsLoading = false;
// mFooterView.setVisibility(View.GONE);
}
/** * 设置“加载更多”监听 * * @param onLoadMoreListener */
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
/** * 当listview滑动到达底部时被回调 */
public interface OnLoadMoreListener {
public void onLoadMore();
}
}
也可参考去年的一篇文章 PullToRefreshListView的使用
当ListView处于第一个Item在顶部,继续往下拉会出来一个下拉刷新动画提示的Header,Header中的提示图标及文字会根据滑动手势而做出相应变化。
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.listview.R;
/*** * 下拉刷新 * * @author K * */
public class PullToRefreshListView extends ListView implements OnScrollListener {
private final static String TAG = PullToRefreshListView.class
.getSimpleName();
/** 下拉刷新 */
private final static int PULL_TO_REFRESH = 0;
/** 释放刷新 */
private final static int RELEASE_TO_REFRESH = 1;
/** 刷新中.. */
private final static int REFRESHING = 2;
/** 刷新完成 */
private final static int DONE = 3;
private LayoutInflater inflater;
private LinearLayout mHeaderView;
private TextView mTipsText;
private ImageView mArrowView;
private ProgressBar mSpinner;
private RotateAnimation animRotate;
private RotateAnimation animReverseRotate;
private boolean isRecored;
/** 默认paddingTop为 header高度的负值,使header在屏幕外不可见 **/
private int mHeaderViewPaddingTop;
/** header布局xml文件原始定义的paddingTop */
private int mHeaderOrgPaddingTop;
private GestureDetector gestureDetector;
private int mPullState;
public OnRefreshListener refreshListener;
public OnLastItemVisibleListener lastItemVisibleListener;
private boolean lastItemVisible;
/** 第一个Item是否可见 */
private boolean isFirstItemVisible;
public interface OnRefreshListener {
public void onRefresh();
}
public interface OnLastItemVisibleListener {
public void onLastItemVisible(int lastIndex);
}
public PullToRefreshListView(Context context) {
this(context, null);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
initArrowAnimation();
initPullHeader(context);
// 为自定义ListView控件绑定滚动监听事件
setOnScrollListener(this);
gestureDetector = new GestureDetector(context, gestureListener);
}
/*** * 实例化下拉ListView的Header布局 * * @param context */
private void initPullHeader(Context context) {
inflater = LayoutInflater.from(context);
mHeaderView = (LinearLayout) inflater.inflate(
R.layout.pull_to_refresh_head, null);
mArrowView = (ImageView) mHeaderView
.findViewById(R.id.head_arrowImageView);
mSpinner = (ProgressBar) mHeaderView
.findViewById(R.id.head_progressBar);
mTipsText = (TextView) mHeaderView.findViewById(R.id.head_tipsTextView);
mHeaderOrgPaddingTop = mHeaderView.getPaddingTop();
measureView(mHeaderView);
mHeaderViewPaddingTop = -mHeaderView.getMeasuredHeight();
setHeaderPaddingTop(mHeaderViewPaddingTop);
mHeaderView.invalidate();
addHeaderView(mHeaderView);
}
private void setHeaderPaddingTop(int paddingTop) {
mHeaderView.setPadding(mHeaderView.getPaddingLeft(), paddingTop,
mHeaderView.getPaddingRight(), mHeaderView.getPaddingBottom());
}
/** * 实例化下拉箭头动画 */
private void initArrowAnimation() {
// 定义一个旋转角度为0 到-180度的动画,时长100ms
animRotate = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animRotate.setInterpolator(new LinearInterpolator());
animRotate.setDuration(100);
animRotate.setFillAfter(true);
animReverseRotate = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animReverseRotate.setInterpolator(new LinearInterpolator());
animReverseRotate.setDuration(100);
animReverseRotate.setFillAfter(true);
}
public void onScroll(AbsListView view, int firstVisiableItem,
int visibleItemCount, int totalItemCount) {
isFirstItemVisible = firstVisiableItem == 0 ? true : false;
boolean loadMore = firstVisiableItem + visibleItemCount >= totalItemCount;
if (loadMore) {
if (mPullState != REFRESHING && lastItemVisible == false
&& lastItemVisibleListener != null) {
lastItemVisible = true;
// including Header View,here using totalItemCount - 2
lastItemVisibleListener.onLastItemVisible(totalItemCount - 2);
}
} else {
lastItemVisible = false;
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// OnScrollListener.SCROLL_STATE_FLING :手指离开屏幕甩动中
// OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:手指正在屏幕上滑动中
// OnScrollListener.SCROLL_STATE_IDLE: 闲置的,未滑动
Log.i("onScroll", "onScrollStateChanged");
}
public boolean dispatchTouchEvent(MotionEvent event) {
if (onTouched.onTouchEvent(event)) {
return true;
}
return super.dispatchTouchEvent(event);
}
private interface OnTouchEventListener {
public boolean onTouchEvent(MotionEvent ev);
}
private OnTouchEventListener onTouched = new OnTouchEventListener() {
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (isRecored) {
requestDisallowInterceptTouchEvent(false);
if (mPullState != REFRESHING) {
if (mPullState == PULL_TO_REFRESH) {
mPullState = DONE;
changeHeaderViewByState(mPullState);
} else if (mPullState == RELEASE_TO_REFRESH) {
mPullState = REFRESHING;
changeHeaderViewByState(mPullState);
onRefresh();
}
}
isRecored = false;
return true;
}
break;
}
return gestureDetector.onTouchEvent(event);
}
};
/** 自定义手势探测器 */
private OnGestureListener gestureListener = new OnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
int deltaY = (int) (e1.getY() - e2.getY());
if (mPullState != REFRESHING) {
// 第一个可见,且手势下拉
if (!isRecored && isFirstItemVisible && deltaY < 0) {
isRecored = true;
requestDisallowInterceptTouchEvent(true);
}
if (isRecored) {
int paddingTop = mHeaderView.getPaddingTop();
// 释放刷新的过程
if (paddingTop < 0 && paddingTop > mHeaderViewPaddingTop) {
if (mPullState == RELEASE_TO_REFRESH) {
changeHeaderViewByState(PULL_TO_REFRESH);
}
mPullState = PULL_TO_REFRESH;
} else if (paddingTop >= 0) {
if (mPullState == PULL_TO_REFRESH) {
changeHeaderViewByState(RELEASE_TO_REFRESH);
}
mPullState = RELEASE_TO_REFRESH;
}
// 根据手指滑动状态动态改变header高度
int topPadding = (int) (mHeaderViewPaddingTop - deltaY / 2);
mHeaderView.setPadding(mHeaderView.getPaddingLeft(),
topPadding, mHeaderView.getPaddingRight(),
mHeaderView.getPaddingBottom());
mHeaderView.invalidate();
return true;
}
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
};
public void onRefreshing() {
mPullState = REFRESHING;
changeHeaderViewByState(mPullState);
}
/** * 改变刷新状态时,调用该方法来改变headerView 显示的内容 * * @param state * 刷新状态 */
private void changeHeaderViewByState(int state) {
switch (state) {
case RELEASE_TO_REFRESH:
mSpinner.setVisibility(View.GONE);
mTipsText.setVisibility(View.VISIBLE);
mArrowView.setVisibility(View.VISIBLE);
mArrowView.clearAnimation();
mArrowView.startAnimation(animRotate);
mTipsText.setText(R.string.pull_to_refresh_release_label);
break;
case PULL_TO_REFRESH:
mSpinner.setVisibility(View.GONE);
mTipsText.setVisibility(View.VISIBLE);
mArrowView.setVisibility(View.VISIBLE);
mArrowView.clearAnimation();
mArrowView.startAnimation(animReverseRotate);
mTipsText.setText(R.string.pull_to_refresh_pull_label);
break;
case REFRESHING:
// 设置paddingTop为原始paddingTop
setHeaderPaddingTop(mHeaderOrgPaddingTop);
// 设置header布局为不可点击,进度条转圈中..
mHeaderView.invalidate();
mSpinner.setVisibility(View.VISIBLE);
mArrowView.clearAnimation();
mArrowView.setVisibility(View.GONE);
mTipsText.setText(R.string.pull_to_refresh_refreshing_label);
break;
case DONE:
// 设置header消失动画
if (mHeaderViewPaddingTop - 1 < mHeaderView.getPaddingTop()) {
ResetAnimimation animation = new ResetAnimimation(mHeaderView,
mHeaderViewPaddingTop, false);
animation.setDuration(300);
mHeaderView.startAnimation(animation);
}
mSpinner.setVisibility(View.GONE);
mArrowView.setVisibility(View.VISIBLE);
mArrowView.clearAnimation();
mArrowView.setImageResource(R.drawable.ic_pulltorefresh_arrow);
mTipsText.setText(R.string.pull_to_refresh_pull_label);
setSelection(0); // listview显示到第一个Item
break;
}
}
// 点击刷新
public void clickRefresh() {
setSelection(0);
mPullState = REFRESHING;
changeHeaderViewByState(mPullState);
onRefresh();
}
public void setOnRefreshListener(OnRefreshListener refreshListener) {
this.refreshListener = refreshListener;
}
public void setOnLastItemVisibleListener(OnLastItemVisibleListener listener) {
this.lastItemVisibleListener = listener;
}
public void onRefreshComplete(String update) {
onRefreshComplete();
}
public void onRefreshComplete() {
mPullState = DONE;
changeHeaderViewByState(mPullState);
}
private void onRefresh() {
if (refreshListener != null) {
refreshListener.onRefresh();
}
}
/*** * 计算headView的width及height值 * * @param child * 计算控件对象 */
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);
}
/** 消失动画 */
public class ResetAnimimation extends Animation {
private int targetHeight;
private int originalHeight;
private int extraHeight;
private View view;
private boolean down;
private int viewPaddingBottom;
private int viewPaddingRight;
private int viewPaddingLeft;
protected ResetAnimimation(View view, int targetHeight, boolean down) {
this.view = view;
this.viewPaddingLeft = view.getPaddingLeft();
this.viewPaddingRight = view.getPaddingRight();
this.viewPaddingBottom = view.getPaddingBottom();
this.targetHeight = targetHeight;
this.down = down;
originalHeight = view.getPaddingTop();
extraHeight = this.targetHeight - originalHeight;
}
@Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
int newHeight;
newHeight = (int) (targetHeight - extraHeight
* (1 - interpolatedTime));
view.setPadding(viewPaddingLeft, newHeight, viewPaddingRight,
viewPaddingBottom);
view.requestLayout();
}
@Override
public void initialize(int width, int height, int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
}
}