自定义带下拉刷新和滚动加载的ListView控件原理分析和实现

上面2张gif效果图,就是要实现的效果

1.下拉刷新

下拉刷新是用于刷新列表第一页数据用的,有3种状态,分别是:

1)下拉刷新

2)松手后刷新

3)正在刷新

原理介绍:

a.关于下拉刷新头部控件的实现

可以通过ListView的addHeaderView的方法,将一个View添加到ListView的头部,该HeaderView就是我们要实现了下拉刷新的头部控件,改控件的样式有可以自定义.

b.关于下拉刷新头部控件的移动实现

控制HeaderView随手指的滑动而在屏幕上划出的效果,这就要用到事件分发和拦截的知识点了.首先要获取手指的滑动距离,可以通过自定义一个ListVIew的子类,然后重写onTouchevent方法,在ACTION_DOWN的时候通过MotionEvent的getY()方法获取手指按下的Y坐标,在ACTION_MOVE再不断更新获取Y坐标,然后通过计算这两个点的Y坐标距离,就可以知道我们在屏幕上滑动的距离了,这个距离就是用来控制HeaderView的滑动的.那么如何控制呢?这里用到了一个很巧妙的方法,就是通过paddingTop的属性来实现,在滑动的过程中,不断的改变HeaderView的paddingTop值,就可以实现HeaderView的移动效果了.那么接下来的问题就是什么时刻才需要划出HeaderView,什么时候又需要滑动ListView而不是HeaderView呢?这个问题将是至关重要的问题,因为HeaderView和ListView同一时刻,只能有一个控件可以获取到触摸事件,不能同时获取,如果HeaderView获取到了触摸事件,那么滑动ListView的时候,ListView的Item将没有滑动效果,此时滑动的效果是划出HeaderView;相反如果ListView获取到了触摸事件,那么ListView的Item可以滑动,而HeaderView将不能滑动.根据用户的习惯,通常下拉刷新的时候,都是在ListView列表的第一个Item可见的时候才有可能执行到下拉刷新,所以我们可以在ACTION_MOVE的时候,判断当前ListView的第一个Item是否可见,如果可见则消费此次事件,通过return true就可以拦截和消费此次事件了,这样父类ListView将接收不到此次触摸事件了,也就滑动不到ListView的item了;至于ListView的Item在什么时候才可以滑动呢,当HeaderView完全隐藏的时候,即paddingTop的值等于HeaderView的高度的负数,这时候我们在ACTION_MOVE的时候return false,不去拦截触摸事件,让父类ListView去接收到该触摸事件,就可以滑动ListView的Item了.

c.下拉刷新头部控件的3种状态间切换的实现

下拉刷新:此时paddingTop的值在HeaderView高度的负值~0直接,当paddingTop=高度的负值的时候,HeaderView是完全隐藏的,当paddingTop=0的时候HeaderView是刚刚好完全显示的.

松手后刷新:此时paddingTop的值是大于0的.0刚好是临界值,只要大于0,马上就要刷新HeaderView的UI了,例如改变HeaderView显示的文字为"松手后刷新",同理,只要paddingTop的值小于0的时候,也要马上刷新HeaderView的UI.

正在刷新:这种情况只有在经历了"松手后刷新"的提示后才可能出来,即用户在看到手松后刷新的提示后松手的,这个时候就要改变HeaderView的UI了,例如改变文字显示为"正在刷新",以及显示加载圈等等,反之如果用户是在看到了"下拉刷新"提示就松手了,这个时候是不会显示正在刷新的,而是马上把HeaderVIew隐藏起来.这里的逻辑判断可以在ACTION_UP的时候去处理.

d.关于数据的刷新

通常ListView的数据是通过集合来管理的,当用户下拉列表处于正在刷新的状态时,需要通知调用者去执行下拉刷新的逻辑,这个可以通过接口回调的方式实现,调用者在下拉刷新的回调方法中去执行刷新的逻辑,即将当前数据集合清空,重新请求网络获取第一页数据,获取完后再添加到数据集合中,然后通过在UI线程中调用adapter的notifyDataSetChanged方法通知ListView刷新界面,调用者的逻辑处理完毕后,我们还需要通知ListView的HeaderView去隐藏,这个时候就需要在自定义的ListView子类中去暴露出一个公共方法,让调用者在执行完刷新操作的时候去执行该方法来隐藏HeaderView.


2.滚动加载

滚动加载是用于实现分页加载的效果的,滚动加载有4种状态,分别是:

1)没有更多数据了

2)松手后加载更多

3)正在加载...

4)加载失败,重新加载

原理介绍:

a.关于加载更多底部控件的实现

同样可以通过ListView的addFooterView的方法来实现,将一个FooterView添加到ListView的列表底部.

b.如何控制FooterView的显示和隐藏

有2种方式,一种是通过上面的方式通过设置paddingTop值来实现,另一种更为简单的方式,就是通过设置Visibility属性来控制其显示和隐藏,这种方式用起来简便很多,那么请思考下,为什么上面设置HeaderView的时候不介绍这种方式呢,因为View的setVisibility方法设置显示和隐藏都是瞬间生效的,并不符合HeaderView的缓慢划出效果,而FooterView就不同了,FooterView就是要这种瞬间生效的效果.

c.加载更多的底部控件的状态切换

通过setOnScrollListener方法监听ListView滚动的3种状态,具体如下:

SCROLL_STATE_IDLE:闲置状态,就是手指松开
SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
SCROLL_STATE_FLING:快速滑动后松开

改三种状态可以在OnScrollListener的onScrollStateChanged(AbsListView view, int scrollState)回调方法中获取到.默认显示"没有更多数据",当ListView的item还在滑动(其他2种状态)且下一个要加载的页面是有足够多数据的情况下,需要修改FooterView的显示文本为"松手后加载更多",当用户滚动到ListView的最后一个Item可见的时候,用户就会看到这个提示了,此时如果用户松开手指,即处于SCROLL_STATE_IDLE状态的时候,就改变FooterView的状态为"正在加载...",如果此时由于网络问题,加载失败了,则改变FooterView的状态为"加载失败,重新加载",当用户点击重新加载后,继续切换为"正在加载..."状态,当用户加载成功后,展示下一页的数据,继续加载的时候,如果下一页的数据不够分页的话,那么用户将看到"没有更多数据了"的提示,依次类推.

d.加载更多的数据处理

这里需要注意的是,加载更多是为了展示下一页的数据,那么前面的数据也是要显示的,所以后面的数据是通过addAll的方式添加到数据集合中的.


3.额外补充说明

1.addHeaderView和addFooterView必须在setAdapter之前调用
2.getMeasuredHeight()和getHeight()的区别:
getMeasuredHeight():获取测量完的高度,只要在onMeasure方法执行完,就可以用它获取到宽高,在自定义控件内部多使用这个;使用headerView.measure(0,0)方法可以主动通知系统去测量headerView,然后就可以直接使用它获取headerView宽高(这个只能用于布局创建的view,通过代码创建的View除外);
getHeight():必须在onLayout方法执行完后,才能获得宽高.可以通过下面的方式监听View的布局完成.

headerView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
             //当view的位置确定后回调,回调完后记得移除监听,因为只要监听一次就可以获取宽高信息了.
             headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
             //直接可以获取宽高
             int headerViewHeight = headerView.getHeight();
        }
    });

3.通过ListView的setSelection(position)方法可以将对应位置的item放置到屏幕顶端


好了上面将了一大堆原理的东西,下面来看看代码实现:

自定义ListView的子类:

package mchenys.net.csdn.blog.myrefreshlistview.view;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.Date;

import mchenys.net.csdn.blog.myrefreshlistview.R;

/**
 * Created by mChenys on 2015/12/6.
 */
public class RefreshListView extends ListView {
    //ListView头部的3种状态
    private final int STATE_PULL_REFRESH = 0; //下拉可刷新
    private final int STATE_RELEASE_REFRESH = 1;//释放后刷新
    private final int STATE_REFRESHING = 2;//正在刷新
    private int mCurrHeaderState = STATE_PULL_REFRESH;//当前默认处于下拉可刷新状态

    //ListView底部的4种状态
    private final int STATE_NO_MORE = -1; //暂时只有那么多数据
    private final int STATE_RELEASE_MORE = -2;//释放后加载更多
    private final int STATE_MORE_LOADING = -3;//正在加载更多
    private final int STATE_MORE_FAILURE = -4;//加载更多失败
    private int mCurrFooterState = STATE_NO_MORE;

    //HeaderView相关
    private View mHeaderView;  //整个头部控件
    private ImageView mIvArrow; //箭头
    private ProgressBar mPbLoading;//下拉刷新的加载圈
    private TextView mTvState;//状态信息
    private TextView mTvTime;//最后一次刷新的时间
    private int mHeaderViewHeight; //头部控件的高度

    //FooterView相关
    private View mFooterView; //整个加载更多布局
    private ProgressBar mPbMore; //加载更多的加载圈
    private TextView mTvMoreTip;//加载更多的提示

    //ListView按下时的y坐标
    private int mDownY;
    //ListView下拉刷新时间相关的变量
    private SharedPreferences mSharedPreferences;
    private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //ListView头部的paddingTop的移动因子,避免下拉刷新时移动的范围太大
    private float factor = 0.55f;
    //箭头滚动的相关动画
    private RotateAnimation mDownAnimation, mUpAnimation;
    //标记当前是否是加载更多
    private boolean isLoadMore;


    public RefreshListView(Context context) {
        this(context, null);
    }

    public RefreshListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 相关初始化
     */
    private void init() {
        initRotateAnimation();
        initHeaderView();
        initFooterView();
        initListener();
    }

    /**
     * 初始化箭头的滚动动画
     */
    private void initRotateAnimation() {
        //箭头向上动画
        mUpAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        mUpAnimation.setDuration(300);
        mUpAnimation.setFillAfter(true);
        //箭头向下动画
        mDownAnimation = new RotateAnimation(-180, -360, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        mDownAnimation.setDuration(300);
        mDownAnimation.setFillAfter(true);
    }

    /**
     * 初始化头部相关控件
     */
    private void initHeaderView() {
        mSharedPreferences = getContext().getSharedPreferences("sp_refresh_time", Context.MODE_PRIVATE);
        mHeaderView = View.inflate(getContext(), R.layout.layout_header, null);
        mIvArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arrow);
        mPbLoading = (ProgressBar) mHeaderView.findViewById(R.id.pb_rotate);
        mTvState = (TextView) mHeaderView.findViewById(R.id.tv_state);
        mTvTime = (TextView) mHeaderView.findViewById(R.id.tv_time);
        //默认隐藏HeaderView,通过设置paddingTop来是实现
        mHeaderView.measure(0, 0);//主动通知系统去测量该view,此方法只适用于有布局的View
        mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        setViewTop(mHeaderView, -mHeaderViewHeight);//设置一个负数的toppading就可以实现隐藏了.
        //将headerView添加到ListView头部上
        this.addHeaderView(mHeaderView);
        //进来时显示上一次的刷新时间
        mTvTime.setText(getRefreshTime());
    }

    /**
     * 初始化FooterView
     */
    private void initFooterView() {
        mFooterView = View.inflate(getContext(), R.layout.layout_footer, null);
        mPbMore = (ProgressBar) mFooterView.findViewById(R.id.pb_load_more);
        mTvMoreTip = (TextView) mFooterView.findViewById(R.id.tv_more_tip);
        mFooterView.setVisibility(View.GONE);
        this.addFooterView(mFooterView);
        mTvMoreTip.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mCurrFooterState != STATE_MORE_FAILURE) {
                    return;
                }
                //点击后重新加载
                mCurrFooterState = STATE_MORE_LOADING;
                refreshFooterViewByState();
                if (null != onRefreshListener) {
                    //点击后,通知调用者重新加载
                    onRefreshListener.onReloadMore();
                }
            }
        });
    }


    /**
     * 设置控件的paddingTop
     *
     * @param tragetView 目标控件
     * @param paddingTop 要设置的top内边距值
     */
    private void setViewTop(View tragetView, Integer paddingTop) {
        if (null != tragetView && null != paddingTop) {
            tragetView.setPadding(0, paddingTop, 0, 0);
        }
    }

    /**
     * 通过重写onTouchEvent来实现HeaderView的显示和状态切换
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mCurrHeaderState == STATE_REFRESHING) {
                    //如果当前已经处于正在刷新状态了,那么不拦截事件
                    break;
                }
                //获取滑动过程中的滑动距离,大于0表示向下滑动,小于0表示向上滑动
                int detalY = (int) (ev.getY() - mDownY);
                //更新HeaderView的paddingTop值
                int paddingTop = -mHeaderViewHeight + detalY;
                if (paddingTop > -mHeaderViewHeight && getFirstVisiblePosition() == 0) {
                    //如果当前paddingTop是大于隐藏时的高度,且ListView第一个可见Item的positon=0,则需要拦截ListView的滚动事件,这个时候ListView则不能处理滚动事件了.
                    //通过更新头部的paddingTop值来划出头部view
                    setViewTop(mHeaderView, (int) (paddingTop * factor));//factor是一个0.55f的移动因子
                    //更新状态(下拉刷新和释放后刷新之间切换)
                    if (paddingTop < 0 && mCurrHeaderState == STATE_RELEASE_REFRESH) {
                        //如果当前是"释放后刷新"状态->"下拉刷新"状态,当paddingTop<0时就要更改状态为"下拉刷新"状态
                        mCurrHeaderState = STATE_PULL_REFRESH;
                        //通过该方法更新HeaderView的各种状态
                        refreshHeaderViewByState();
                    } else if (paddingTop >= 0 && mCurrHeaderState == STATE_PULL_REFRESH) {
                        //如果当前是"下拉刷新"状态->"释放后刷新"状态,当paddingTop>=0时就要更改状态为"释放后刷新"了.
                        mCurrHeaderState = STATE_RELEASE_REFRESH;
                        //通过该方法更新HeaderView的各种状态
                        refreshHeaderViewByState();
                    }
                    return true;//返回true表示消费此次事件,则父控件ListView将不处理此次事件
                }
                break;
            case MotionEvent.ACTION_UP:
                //up事件需要处理当前为下拉刷新和正在刷新的2种情况
                if (mCurrHeaderState == STATE_PULL_REFRESH) {
                    //如果up事件时headerView没有完全拉出来,此时需要将其隐藏
                    setViewTop(mHeaderView, -mHeaderViewHeight);
                } else if (mCurrHeaderState == STATE_RELEASE_REFRESH) {
                    //如果up事件时,当前是正在刷新状态,那么此时需要将headerView刚刚好显示出来
                    setViewTop(mHeaderView, 0);
                    //更新状态
                    mCurrHeaderState = STATE_REFRESHING;
                    refreshHeaderViewByState();

                    //同时需要通知调用者去处理刷新逻辑
                    if (null != onRefreshListener) {
                        onRefreshListener.onRefresh();
                    }
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 通过该方法更新HeaderView的各种状态
     */
    private void refreshHeaderViewByState() {
        switch (mCurrHeaderState) {
            case STATE_PULL_REFRESH:
                //"下拉刷新"状态,需要显示箭头,隐藏加载圈,显示下拉刷新文字和最后刷新的时间,同时启动箭头动画
                mIvArrow.setVisibility(View.VISIBLE);
                mPbLoading.setVisibility(View.INVISIBLE);
                mTvTime.setVisibility(View.VISIBLE);
                mTvState.setText("下拉刷新");
                mTvTime.setText(getRefreshTime());
                //显示箭头朝下
                mIvArrow.startAnimation(mDownAnimation);
                break;
            case STATE_RELEASE_REFRESH:
                //"释放后刷新"状态,需要显示箭头,隐藏加载圈,显示释放后刷新文字和最后刷新的时间,同时启动箭头动画
                mIvArrow.setVisibility(View.VISIBLE);
                mPbLoading.setVisibility(View.INVISIBLE);
                mTvTime.setVisibility(View.VISIBLE);
                mTvState.setText("释放后刷新");
                //启动一个箭头的由下到上逆时针旋转的动画
                mIvArrow.startAnimation(mUpAnimation);
                break;
            case STATE_REFRESHING:
                //"正在刷新"状态,需要显示加载圈,隐藏箭头,显示正在刷新文字,隐藏最后刷新的时间
                mIvArrow.clearAnimation();//避免向上的旋转动画有可能没有执行完
                mPbLoading.setVisibility(View.VISIBLE);
                mIvArrow.setVisibility(View.INVISIBLE);
                mTvTime.setVisibility(View.GONE);
                mTvState.setText("正在刷新...");
                break;
        }
    }

    /**
     * 通过此方法获取最后一次刷新的时间
     *
     * @return
     */
    private String getRefreshTime() {
        return "上次刷新时间:" + mSharedPreferences.getString("key_refresh_time", mDateFormat.format(new Date()));
    }

    /**
     * 通过此方法保存下拉刷新的时间
     */
    private void saveRefreshTime() {
        mSharedPreferences.edit().putString("key_refresh_time", mDateFormat.format(new Date())).commit();
    }

    /**
     * 下拉刷新和滚动加载的监听回调方法
     */
    public interface OnRefreshListener {
        //下拉刷新
        void onRefresh();

        //滚动加载更多
        void onLoadMore();

        void onReloadMore();


    }

    private OnRefreshListener onRefreshListener;

    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
        this.onRefreshListener = onRefreshListener;
    }

    /**
     * 下拉刷新/加载更多成功后需要重置状态,由调用者调用,注意:必须要在UI线程调用
     */
    public void onRefreshComplete() {
        if (!isLoadMore) {
            //下拉刷新完毕
            setViewTop(mHeaderView, -mHeaderViewHeight);//隐藏headView
            saveRefreshTime();//保存此次刷新的时间
            //重置状态
            mCurrHeaderState = STATE_PULL_REFRESH;
            refreshHeaderViewByState();
            isNoMoreData = false;
        } else {
            //加载更多完毕,重置状态
            isLoadMore = false;
            isNoMoreData = false;
        }
        //无论是下拉刷新成功还是滚动加载成功,都需要将mCurrFooterState设置为默认状态
        mCurrFooterState = STATE_NO_MORE;
        refreshFooterViewByState();
    }

    /**
     * 初始化ListView的滚动监听
     */
    private void initListener() {
        this.setOnScrollListener(new OnScrollListener() {
            /**
             * scrollState的三种状态
             * SCROLL_STATE_IDLE:闲置状态,就是手指松开
             * SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
             * SCROLL_STATE_FLING:快速滑动后松开
             */
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                switch (scrollState) {
                    case SCROLL_STATE_IDLE://闲置状态,就是手指松开
                        if (getLastVisiblePosition() == getCount() - 1 && !isLoadMore && mCurrFooterState == STATE_RELEASE_MORE
                                && mCurrFooterState != STATE_NO_MORE
                                && mCurrFooterState != STATE_MORE_FAILURE) {
                            //如果当前滚动停止了,且最后一个item的position=最后一个位置,则需要显示加载更多的布局
                            isLoadMore = true;//标记为加载更多
                            mCurrFooterState = STATE_MORE_LOADING;
                            //显示"正在加载更多..."
                            refreshFooterViewByState();
                            //让listView显示在最有一条item的位置
                            setSelection(getCount());//让listview最后一条显示出来
                            //通知调用者去处理加载更多的逻辑
                            if (null != onRefreshListener) {
                                onRefreshListener.onLoadMore();
                            }
                        }
                        break;
                    default: //其他状态
                        if (mCurrFooterState == STATE_NO_MORE && !isNoMoreData) {
                            //isNoMoreData = false 表示还有更多的数据,显示释放后刷新
                            mCurrFooterState = STATE_RELEASE_MORE;
                            refreshFooterViewByState();
                        }
                        break;
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }

    /**
     * 通过状态刷新FootView的状态
     */
    private void refreshFooterViewByState() {
        switch (mCurrFooterState) {
            case STATE_NO_MORE: //没有更多数据
                mTvMoreTip.setText("没有更多数据了");
                mFooterView.setVisibility(View.VISIBLE);
                mPbMore.setVisibility(View.GONE);
                break;
            case STATE_RELEASE_MORE: //释放后加载更多
                mTvMoreTip.setText("释放后加载更多");
                mFooterView.setVisibility(View.VISIBLE);
                mPbMore.setVisibility(View.GONE);
                break;
            case STATE_MORE_LOADING://正在加载更多
                mTvMoreTip.setText("正在加载更多...");
                mFooterView.setVisibility(View.VISIBLE);
                mPbMore.setVisibility(View.VISIBLE);
                break;
            case STATE_MORE_FAILURE: //加载更多失败
                mTvMoreTip.setText("加载失败,重新加载");
                mFooterView.setVisibility(View.VISIBLE);
                mPbMore.setVisibility(View.GONE);
                break;
        }
    }

    /**
     * 加载更多失败,由调用者调用
     */
    public void onLoadMoreFailure() {
        isLoadMore = false;
        mCurrFooterState = STATE_MORE_FAILURE;
        refreshFooterViewByState();
    }

    /**
     * 没有更多数据可加载时,由调用者调用.
     */
    private boolean isNoMoreData = false;

    public void onNoMoreData() {
        isLoadMore = false;
        isNoMoreData = true;
        mCurrFooterState = STATE_NO_MORE;
        refreshFooterViewByState();
    }

}


HeaderView的头部资源文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="horizontal">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dp">

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:background="@drawable/indicator_arrow" />

        <ProgressBar
            android:id="@+id/pb_rotate"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerInParent="true"
            android:indeterminateDrawable="@drawable/indeterminate_drawable"
            android:indeterminateDuration="1000"
            android:visibility="invisible" />

    </RelativeLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉刷新"
            android:textColor="#aa000000"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="最后刷新:"
            android:textColor="@android:color/darker_gray"
            android:textSize="14sp" />

    </LinearLayout>

</LinearLayout>


FooterView的底部资源文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="horizontal">

    <ProgressBar
        android:id="@+id/pb_load_more"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dp"
        android:indeterminate="true"
        android:indeterminateDrawable="@drawable/indeterminate_drawable"
        android:indeterminateDuration="1000" />

    <TextView
        android:id="@+id/tv_more_tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="10dp"
        android:text="加载更多..."
        android:textColor="#aa000000"
        android:textSize="20sp" />
</LinearLayout>

测试类:

package mchenys.net.csdn.blog.myrefreshlistview;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import mchenys.net.csdn.blog.myrefreshlistview.view.RefreshListView;

public class MainActivity extends AppCompatActivity {
    private RefreshListView mListView;
    private BaseAdapter mAdapter;
    private List<String> mData = new ArrayList<>();
    private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private int mCurrPageNo = 1;//模仿当前的页码
    private int pageSize = 20;//一页20条数据
    //处理ListView刷新的Handler
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0) {
                //下拉刷新成功和加载更多成功
                mListView.onRefreshComplete();//通知ListView刷新完成
                mAdapter.notifyDataSetChanged();
            } else if (msg.what == 1) {
                //加载更多成功,当前没有更多数据
                mListView.onNoMoreData();
                mAdapter.notifyDataSetChanged();
            } else {
                //加载更多失败
                mListView.onLoadMoreFailure();
                mAdapter.notifyDataSetChanged();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        init();
    }

    private void init() {
        initData();
        initView();
        initListener();
    }

    /**
     * 初始化初始数据
     */
    private void initData() {
        for (int i = 0; i < pageSize; i++) {
            mData.add("初始数据-" + i);
        }
    }

    /**
     * 初始化View
     */
    private void initView() {
        mListView = new RefreshListView(this);
        mAdapter = new mAdapter();
        mListView.setAdapter(mAdapter);
        setContentView(mListView);
    }

    /**
     * 初始化监听
     */
    private void initListener() {
        mListView.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //下拉刷新逻辑
                loadNewData(false);
            }

            @Override
            public void onLoadMore() {
                //加载更多
                loadNewData(true);
            }

            @Override
            public void onReloadMore() {
                //重新加载更多
                loadNewData(true);
                System.out.println("-------重新加载更多--------");
            }
        });
    }

    /**
     * 处理下拉刷新和滚动加载新数据的方法
     *
     * @param isLoadMore 是否是加载更多
     */
    public void loadNewData(final boolean isLoadMore) {
        final List<String> tempList = new ArrayList<String>();
        new Thread() {
            public void run() {
                SystemClock.sleep(3000);//模拟请求服务器的一个时间长度
                tempList.clear();
                if (!isLoadMore) {
                    //模拟下拉刷新成功
                    mCurrPageNo = 1;
                    mData.clear();
                    for (int i = 0; i < pageSize; i++) {
                        tempList.add("第一页新数据-" + i + " at:" + mDateFormat.format(new Date()));
                    }
                    System.out.println("---------模拟下拉刷新成功--------");

                } else {
                    mCurrPageNo++;//以下只模拟3页数据
                    if (mCurrPageNo == 2) {
                        for (int i = 0; i < pageSize; i++) {
                            //模拟数据完全加载更多成功
                            tempList.add("第二页数据-" + i + " at:" + mDateFormat.format(new Date()));
                        }
                        System.out.println("---------模拟数据完全加载更多成功--------");

                    } else if (mCurrPageNo == 3) {
                        //模拟数据不够20条
                        for (int i = 0; i < 5; i++) {
                            tempList.add("第三页数据-" + i + " at:" + mDateFormat.format(new Date()));
                        }
                        System.out.println("---------模拟数据不够20条--------");

                    } else {
                        //模拟网络失败
                        System.out.println("---------模拟网络失败--------");
                    }
                }
                mData.addAll(tempList);
                if (mCurrPageNo == 1 || tempList.size() == pageSize) {
                    //在UI线程更新UI
                    mHandler.sendEmptyMessage(0);
                } else if (tempList.size() > 0 && tempList.size() < pageSize) {
                    mHandler.sendEmptyMessage(1);
                } else {
                    mHandler.sendEmptyMessage(2);
                }
            }
        }.start();
    }

    /**
     * 适配器
     */
    private class mAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public String getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (null == convertView) {
                TextView textView = new TextView(MainActivity.this);
                textView.setPadding(10, 10, 10, 10);
                textView.setTextSize(18);
                textView.setGravity(Gravity.CENTER_VERTICAL);
                convertView = textView;
            }
            ((TextView) convertView).setText(getItem(position));
            return convertView;
        }
    }
}


源码中额外补充了一些修改.


源码下载


2016/01/29 以下新增了代码,实现了HeaderView下拉刷新成功后平滑收起的效果:

/**
 * 下拉刷新/加载更多成功后需要重置状态,由调用者调用,注意:必须要在UI线程调用
 */
public void onRefreshComplete() {
    mHandler.postDelayed(new Runnable() { //这里做了延时1s的处理,为了是在网络较快的时候可以看到动画(视需求而定)
        @Override
        public void run() {
            if (!isLoadMore) {
                //下拉刷新完毕
                //setViewTop(mHeaderView, -mHeaderViewHeight);//隐藏headView,瞬时完成的
                setHeaderViewBackSmooth();//带动画平滑的收起
                saveRefreshTime();//保存此次刷新的时间
                //重置状态
                mCurrHeaderState = STATE_PULL_REFRESH;
                refreshHeaderViewByState();
                isNoMoreData = false;
            } else {
                //加载更多完毕,重置状态
                isLoadMore = false;
                isNoMoreData = false;
                adapter.notifyDataSetChanged();
            }
            //无论是下拉刷新成功还是滚动加载成功,都需要将mCurrFooterState设置为默认状态
            mCurrFooterState = STATE_RELEASE_MORE;
            refreshFooterViewByState();
        }
    }, 1000);
}
/**
 * 实现HeaderView隐藏的时候平滑的收起效果
 */
private void setHeaderViewBackSmooth() {
    ValueAnimator animator = ValueAnimator.ofInt(mHeaderView.getPaddingTop(), -mHeaderViewHeight);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //不断的修改paddingTop的值,达到平滑收起的效果
            int paddingTop = (int) animation.getAnimatedValue();
            setViewTop(mHeaderView,paddingTop);
        }
    });
    animator.setDuration(500);
    animator.start();
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2016/12/20 修改了一些bug,同时将ListView代码中的header和footer进行分离封装.


源码下载


添加了如下效果示例:




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值