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

来源:互联网 发布:会计金蝶软件 编辑:程序博客网 时间:2024/05/17 01:51

上面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
原创粉丝点击