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底部,从而到达分页效果,避免一次性加载全部数据,提升了性能。

原理:

  1. 自定义控件LoadMoreListView 继承ListView
  2. 通过监听onScrollListener判断ListView滚动状态,当ListView滑动到底部,且处于正在滑动状态、且没有正在加载,则通过获取新数据
  3. 利用addAll()方法往list集合末端添加新数据,使得适配器的数据源每新加载一屏数据就发生变化;
  4. 利用适配器对象的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中的提示图标及文字会根据滑动手势而做出相应变化。
下拉刷新演示

原理

  1. 自定义控件PullToRefreshListView继承ListView,实现onScrollListener接口
  2. 构造函数中初始化变量,通过addHeader方法添加顶部下拉视图header,初始化header样式和动画效果
  3. 借助 ListView组件的OnScrollListener监听事件,去判断何时该加载最新数据,并根据手指不同触屏状态显示对应的header显示内容和动画;
  4. 加载到最新数据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
原创粉丝点击