ListView (4)滚动事件/上拉刷新/下拉刷新的实现

本篇笔记整理了ListView上拉加载更多及下拉刷新的实现,两者实现都需要用到 OnScrollListener 的事件监听,转载请注明出处

ListView的 滚动事件监听

实现滚动监听,首先需要通过实现OnScrollListener 接口,重写
onScrollStateChanged 和 onScroll两个方法,分别用于监听ListView滑动状态的变化,和屏幕滚动

onScrollStateChanged

// 监听滑动状态的变化
@Override
public 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的数量
@Override
public 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判断是否该加载更多数据

// 监听滑动状态的变化
@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();
    }

}

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);
        }

    }

}

你可能感兴趣的:(ListView,事件,滑动)