ListView (4)滚动事件/上拉刷新/下拉刷新的实现
来源:互联网 发布:英伦风的男装品牌 知乎 编辑:程序博客网 时间:2024/06/04 00:29
本篇笔记整理了ListView上拉加载更多及下拉刷新的实现,两者实现都需要用到 OnScrollListener 的事件监听,转载请注明出处
ListView的 滚动事件监听
实现滚动监听,首先需要通过实现OnScrollListener 接口,重写
onScrollStateChanged 和 onScroll两个方法,分别用于监听ListView滑动状态的变化,和屏幕滚动
onScrollStateChanged
// 监听滑动状态的变化@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) { // OnScrollListener.SCROLL_STATE_FLING; //屏幕处于甩动状态 // OnScrollListener.SCROLL_STATE_IDLE; //停止滑动状态 // OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;// 手指接触状态 // 记录当前滑动状态}
scrollState 回调顺序如下:
- 第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1):表示正在滚动。当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1
- 第2次:scrollState =SCROLL_STATE_FLING(2) :表示手指做了抛的动作(手指离开屏幕前,用力滑了一下,屏幕产生惯性滑动)。
- 第3次:scrollState =SCROLL_STATE_IDLE(0) :表示屏幕已停止。屏幕停止滚动时为0。
onScroll:监听屏幕滑动,并记录当前页面item显示情况
// 监听屏幕滚动的item的数量@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
onScroll中参数讲解:
- firstVisibleItem:当前窗口中能看见的第一个列表项ID(从0开始)
- visibleItemCount:当前窗口中能看见的列表项的个数(小半个也算)
- totalItemCount:列表项的总数
ListView 上拉刷新控件、加载更多、分页
应用:上拉刷新滑动到底部时加载一页新数据,显示到ListView底部,从而到达分页效果,避免一次性加载全部数据,提升了性能。
原理:
- 自定义控件LoadMoreListView 继承ListView
- 通过监听onScrollListener判断ListView滚动状态,当ListView滑动到底部,且处于正在滑动状态、且没有正在加载,则通过获取新数据
- 利用addAll()方法往list集合末端添加新数据,使得适配器的数据源每新加载一屏数据就发生变化;
利用适配器对象的notifyDataSetChanged()方法。该方法的作用是通知适配器自己及与该数据有关的view,数据已经发生变动,要刷新自己、更新数据。
代码实现
首先需要定义两个记录状态的全局变量
/** 是否正在加载中 */private boolean mIsLoading;/** 当前滑动状态 */private int mCurrentScrollState;
接着ListView通过实现onScrollListener,复写onScrollStateChanged 用于保存当前滑动状态,复写onScrool判断是否该加载更多数据
// 监听滑动状态的变化@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) { // 记录当前滑动状态 mCurrentScrollState = scrollState;}// 监听屏幕滚动的item的数量@Overridepublic 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); }// 监听滑动状态的变化@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) { // 记录当前滑动状态 mCurrentScrollState = scrollState;}// 监听屏幕滚动的item的数量@Overridepublic 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(); }}
ListView 下拉刷新的实现
也可参考去年的一篇文章 PullToRefreshListView的使用
应用
当ListView处于第一个Item在顶部,继续往下拉会出来一个下拉刷新动画提示的Header,Header中的提示图标及文字会根据滑动手势而做出相应变化。
原理
- 自定义控件PullToRefreshListView继承ListView,实现onScrollListener接口
- 构造函数中初始化变量,通过addHeader方法添加顶部下拉视图header,初始化header样式和动画效果
- 借助 ListView组件的OnScrollListener监听事件,去判断何时该加载最新数据,并根据手指不同触屏状态显示对应的header显示内容和动画;
- 加载到最新数据list添加到数据源最前面位置mlist.add(0,list),使适配器加载到新数据在listview顶部
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); } }}
0 0
- ListView (4)滚动事件/上拉刷新/下拉刷新的实现
- 实现ListView上拉/下拉刷新的例子
- Android ListView下拉刷新上拉加载更多的实现
- listview下拉刷新,上拉加载更多的实现方法
- Android实现ListView的下拉刷新、上拉加载更多
- 通过SwipeRefreshLayout实现ListView的上拉加载下拉刷新
- ListView下拉刷新上拉加载的实现与使用
- ListView下拉刷新和上拉加载更多的实现
- 实现ListView的下拉刷新和上拉加载
- 实现ListView的上拉刷新和下拉加载
- 下拉刷新+上拉加载的listview
- listview的上拉刷新,下拉加载
- listview的上拉加载,下拉刷新
- listview 上拉 下拉刷新
- Android ListView 下拉刷新 上拉刷新
- 开源ListView上拉刷新下拉刷新
- 上拉刷新,下拉刷新listview
- 使用下拉刷新的和上拉刷新的ListView
- [Python]基本概念与操作3(针对Python2)
- 第八周 【项目1-实现复数类中的运算符重载】3
- 多个wxGrid消息同时处理时识别的办法
- leetcode Excel Sheet Column Number
- win7电脑如何阻止陌生U盘启动?
- ListView (4)滚动事件/上拉刷新/下拉刷新的实现
- MyEclipse设置编码方式
- I2C驱动
- 4.2
- 2014年终总结–家
- 网易新闻(ListView部分)
- (行为型模式一)模板方法模式
- 多线程实现线程同步——互次对象
- JavaScript技巧45招