上面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(); } }
<?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>
<?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(); }