上面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();
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2016/12/20 修改了一些bug,同时将ListView代码中的header和footer进行分离封装.
源码下载
添加了如下效果示例: