listview下拉刷新和上拉加载更多的多种实现方案
listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明。
方案一:添加头布局和脚布局
android系统为listview提供了addfootview和addheadview两个API。这样可以直接自定义一个View,以添加视图的形式实现下来刷新和上拉加载。
实现步骤
1、创建一个类继承ListView:class PullToRefreshListView extends ListView;
2、在构造方法中添加HeadView:addHeaderView(headView);
3、获取HeadView的高。测量控件的高可以有两方法getMeasuredHeight和getHeight,getMeasuredHeight()在onMeasure方法执行之后才能获取到;getHeight() 在onLayout方法执行之后才能获取到值;
4、显示和隐藏headView,通过setpadding实现,当向下滑,且第一条可见item是第0条的时候才需要设置HeadView的paddingTop来显示HeadView。
显示:headView.setPadding(0,0,0,0);
隐藏:headView.setPadding(0,-headViewHeight,0,0);
5、下拉刷新三种状态的判断,移动的时候,当paddingTop < 0 的时候,说明HeadView没有完全显示出来,进入下拉刷新状态;移动的时候,当paddingTop >= 0 的时候, 说明HeadView已经完全显示出来了,进入松开以新状态;手指抬起的时候,且当前状态是松开刷新状态的时候,进入正在刷新状态; 当已经是“正在刷新”状态时, 则不允许再做”下拉刷新”和”松开刷新”的操作了,在Move事件中加入判断,如果已经是正在刷新状态了,则不处理下拉的操作了。
6、下拉箭头的转动。下拉刷新是向下,松开刷新时向上。旋转动画通过属性动画实现。隐藏箭头的时候要清除动画:iv_arrow.clearAnimation(); 如果不隐藏动画效果,设置View.GONE之后还是看得见的。
7、HeadView显示时,当手指松开时的处理,松开时如果是“正在刷新”状态,则把headVie完全显示;松开时如果是“下拉刷新”状态,则把HeadView完全隐藏。
8、增加FooterView:addFooterView(footerView)。当ListView处于空闲状态,并且最后一条可见item是ListView中的最后一条数据时显示footview, footerView显示出来后,ListView不会自动上滑把FooterView显示出来的,所以需要手动设置:setSelection(getCount() - 1);即选中最后一条。
9、增加回调监听器。当ListView处于刷新状态的时候会调用onRefreshing()方法;当ListView处于加载更多的时候会调用onLoadMore()。加载完成后通知控件加载完成。
具体实现:
import com.itheima.pulltorefreshlistview.R; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; 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; public class PullToRefreshListView extends ListView { private View headerView; private float downY; private int headerViewHeight; /** 状态:下拉刷新 */ private static final int STATE_PULL_TO_REFRESH = 0; /** 状态:松开刷新 */ private static final int STATE_RELEASE_REFRESH = 1; /** 状态:正在刷新 */ private static final int STATE_REFRESHING = 2; /** 当前状态 */ private int currentState = STATE_PULL_TO_REFRESH; // 默认是下拉刷新状态 private ImageView iv_arrow; private ProgressBar progress_bar; private TextView tv_state; private RotateAnimation upAnim; private RotateAnimation downAnim; private OnRefreshingListener mOnRefreshingListener; private View footerView; private int footerViewHeight; /** 正在加载更多 */ private boolean loadingMore; public PullToRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); initHeaderView(); initFooterView(); } private void initHeaderView() { headerView = View.inflate(getContext(), R.layout.header_view, null); iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow); progress_bar = (ProgressBar) headerView.findViewById(R.id.progress_bar); showRefreshingProgressBar(false); tv_state = (TextView) headerView.findViewById(R.id.tv_state); headerView.measure(0, 0); // 主动触发测量,mesure内部会调用onMeasure headerViewHeight = headerView.getMeasuredHeight(); hideHeaderView(); super.addHeaderView(headerView); upAnim = createRotateAnim(0f, -180f); downAnim = createRotateAnim(-180f, -360f); } private void initFooterView() { footerView = View.inflate(getContext(), R.layout.footer_view, null); footerView.measure(0, 0);// 主动触发测量,mesure内部会调用onMeasure footerViewHeight = footerView.getMeasuredHeight(); hideFooterView(); super.addFooterView(footerView); super.setOnScrollListener(new OnScrollListener() { // 当ListView滚动的状态发生改变的时候会调用这个方法 @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == OnScrollListener.SCROLL_STATE_IDLE // ListView处于空闲状态 && getLastVisiblePosition() == getCount() - 1 // 界面上可见的最后一条item是ListView中最后的一条item && loadingMore == false // 如果当前没有去做正在加载更多的事情 ) { loadingMore = true; showFooterView(); setSelection(getCount() - 1); if (mOnRefreshingListener != null) { mOnRefreshingListener.onLoadMore(); } } } // 当ListView滚动的时候会调用这个方法 @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); } private void hideFooterView() { int paddingTop = -footerViewHeight; setFooterViewPaddingTop(paddingTop); } private void showFooterView() { int paddingTop = 0; setFooterViewPaddingTop(paddingTop); } private void setFooterViewPaddingTop(int paddingTop) { footerView.setPadding(0, paddingTop, 0, 0); } /** * 设置显示进度的圈圈 * @param showProgressBar 如果是true,则显示ProgressBar,否则的话显示箭头 */ private void showRefreshingProgressBar(boolean showProgressBar) { progress_bar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); iv_arrow.setVisibility(!showProgressBar ? View.VISIBLE : View.GONE); if (showProgressBar) { iv_arrow.clearAnimation(); // 有动画的View要清除动画才能真正的隐藏 } } /** * 创建旋转动画 * @param fromDegrees 从哪个角度开始转 * @param toDegrees 转到哪个角度 * @return */ private RotateAnimation createRotateAnim(float fromDegrees, float toDegrees) { int pivotXType = RotateAnimation.RELATIVE_TO_SELF; // 旋转点的参照物 int pivotYType = RotateAnimation.RELATIVE_TO_SELF; // 旋转点的参照物 float pivotXValue = 0.5f; // 旋转点x方向的位置 float pivotYValue = 0.5f; // 旋转点y方向的位置 RotateAnimation ra = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue); ra.setDuration(300); ra.setFillAfter(true); // 让动画停留在结束位置 return ra; } /** 隐藏HeaderView */ private void hideHeaderView() { int paddingTop = -headerViewHeight; setHeaderViewPaddingTop(paddingTop); } /** 显示HeaderView */ private void showHeaderView() { int paddingTop = 0; setHeaderViewPaddingTop(paddingTop); } /** * 设置HeaderView的paddingTop * @param paddingTop */ private void setHeaderViewPaddingTop(int paddingTop) { headerView.setPadding(0, paddingTop, 0, 0); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downY = ev.getY(); break; case MotionEvent.ACTION_MOVE: if (currentState == STATE_REFRESHING) { // 如果当前已经是“正在刷新“的状态了,则不用去处理下拉刷新了 return super.onTouchEvent(ev); } int fingerMoveDistanceY = (int) (ev.getY() - downY); // 手指移动的距离 // 如果是向下滑动,并且界面上可见的第一条item是ListView的索引为0的item时我们才处理下拉刷新的操作 if (fingerMoveDistanceY > 0 && getFirstVisiblePosition() == 0) { int paddingTop = -headerViewHeight + fingerMoveDistanceY; setHeaderViewPaddingTop(paddingTop); if (paddingTop < 0 && currentState != STATE_PULL_TO_REFRESH) { // 如果paddingTop小于0,说明HeaderView没有完全显示出来,则进入下拉刷新的状态 currentState = STATE_PULL_TO_REFRESH; tv_state.setText("下拉刷新"); iv_arrow.startAnimation(downAnim); showRefreshingProgressBar(false); // 让箭头转一下 } else if (paddingTop >= 0 && currentState != STATE_RELEASE_REFRESH) { // 如果paddingTop>=0,说明HeaderView已经完全显示出来,则进入松开刷新的状态 currentState = STATE_RELEASE_REFRESH; tv_state.setText("松开刷新"); iv_arrow.startAnimation(upAnim); showRefreshingProgressBar(false); } return true; } break; case MotionEvent.ACTION_UP: if (currentState == STATE_RELEASE_REFRESH) { // 如果当前状态是松开刷新,并且抬起了手,则进入正在刷新状态 currentState = STATE_REFRESHING; tv_state.setText("正在刷新"); showRefreshingProgressBar(true); showHeaderView(); if (mOnRefreshingListener != null) { mOnRefreshingListener.onRefreshing(); } } else if (currentState == STATE_PULL_TO_REFRESH) { // 如果抬起手时是下拉刷新状态,则把HeaderView完成隐藏 hideHeaderView(); } break; } return super.onTouchEvent(ev); } public void setOnRefreshingListener(OnRefreshingListener mOnRefreshingListener) { this.mOnRefreshingListener = mOnRefreshingListener; } /** ListView刷新的监听器 */ public interface OnRefreshingListener { /** 当ListView可以刷新数据的时候会调用这个方法 */ void onRefreshing(); /** 当ListView可以加载更多 的时候会调用这个方法 */ void onLoadMore(); } /** 联网刷新数据的操作已经完成了 */ public void onRefreshComplete() { hideHeaderView(); currentState = STATE_PULL_TO_REFRESH; showRefreshingProgressBar(false); } /** 加载更多新数据的操作已经完成了 */ public void onLoadmoreComplete() { hideFooterView(); loadingMore = false; } }
调用listview:
listView = (PullToRefreshListView) findViewById(R.id.list_view); listView.setAdapter(adapter); listView.setOnRefreshingListener(new OnRefreshingListener() { @Override public void onRefreshing() { reloadData(); } @Override public void onLoadMore() { oadMore(); } });
方案二: listview的多种样式显示
设置listview的适配器的时候可以实现两个方法: getViewTypeCount()和getItemViewType(),前者指定条目的种类,后者返回具体的类型,这样可以根据不同的类型设计相关的样式,包括上拉加载更多,和下拉刷新,两者类似,因此这里仅仅给出加载更多的写法。具体实现如下:
1、重写getViewTypeCount()和getItemViewType(),这里包括普通的item条目和加载更多的条目,所以getViewTypeCount()返回值为2;
@Override public int getViewTypeCount() { return super.getViewTypeCount() + 1; } @Override public int getItemViewType(int position) { if (position == getCount() - 1) { return 0; } else { return addViewType(position); //构造一个方法出来,方便子类修改,添加更多的样式 } } public int addViewType(int position) { return 1; }
2、在getview()中针对不同的类型添加布局:
@Override public View getView(int position, View convertView, ViewGroup parent) { BaseHoldle holdle; if (convertView == null) { if (getItemViewType(position) == 0) { //type为0 表示应该加载加载更多的视图 holdle = getLoadmoreHoldle(); } else { //否则为普通视图 holdle = getSpecialBaseHoldle(position); } } else { holdle = (BaseHoldle) convertView.getTag(); } if (getItemViewType(position) == 0) { //加载更多视图,请求网络获取数据 if (havemore()) { holdle.setDataAndRefreshHoldleView(LoadmoreHoldle.LOADMORE_LODING); triggleLoadMoreData(); } else { holdle.setDataAndRefreshHoldleView(LoadmoreHoldle.LOADMORE_NONE); } } else { //普通视图视图,请求网络获取数据 T data = (T) mdata.get(position); holdle.setDataAndRefreshHoldleView(data); } mHoldleView = holdle.mHoldleView; mHoldleView.setScaleX(0.6f); mHoldleView.setScaleY(0.6f); ViewCompat.animate(mHoldleView).scaleX(1).scaleY(1).setDuration(400).setInterpolator(new OvershootInterpolator(4)).start(); return mHoldleView; }
3、具体的加载更多视图的实现
private BaseHoldle getLoadmoreHoldle() { if (mLoadmoreHoldle == null) { mLoadmoreHoldle = new LoadmoreHoldle(); } return mLoadmoreHoldle; } public class LoadmoreHoldle extends BaseHoldle { @Bind(R.id.item_loadmore_container_loading) LinearLayout itemloadmorecontainerloading; @Bind(R.id.item_loadmore_container_retry) LinearLayout itemloadmorecontainerretry; @Bind(R.id.item_loadmore_tv_retry) TextView item_loadmore_tv_retry; public static final int LOADMORE_LODING = 0; public static final int LOADMORE_ERROR = 1; public static final int LOADMORE_NONE = 2; private int mCurretState; @Override public void refreshHoldleView(Object data) { itemloadmorecontainerloading.setVisibility(View.GONE); itemloadmorecontainerretry.setVisibility(View.GONE); mCurretState = (int) data; switch (mCurretState) { case LOADMORE_LODING: itemloadmorecontainerloading.setVisibility(View.VISIBLE); break; case LOADMORE_ERROR: itemloadmorecontainerretry.setVisibility(View.VISIBLE); break; case LOADMORE_NONE: break; } } @Override public View ininViewHoldle() { View view = View.inflate(UiUtils.getContext(), R.layout.itemloadmore, null); ButterKnife.bind(this, view); return view; } } //holder基类,提取公共的方法 public abstract class BaseHoldle<T> { public View mHoldleView; public T mdata; public BaseHoldle() { mHoldleView = ininViewHoldle(); mHoldleView.setTag(this); } public void setDataAndRefreshHoldleView(T mdata) { this.mdata = mdata; refreshHoldleView(mdata); } public abstract void refreshHoldleView(T data); public abstract View ininViewHoldle(); }
方案三: SwipeRefreshLayout实现下来刷新
SwipeRefreshLayout对下不兼容,且只有下拉刷新功能没有上拉加载更多的功能。当时作为Andriod5.0之后的新特性,使用起来方便,可以直接调用系统的API。使用方法也较为简单。具体实现如下:
首先声明控件,设置颜色:
refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh); refreshLayout.setOnRefreshListener(this); refreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright, android.R.color.holo_green_light,android.R.color.holo_orange_light, android.R.color.holo_red_light); refreshLayout.setProgressBackgroundColor(R.color.refresh_bg); refreshLayout.setProgressBackgroundColor(R.color.refresh_bg);
写一个类实现SwipeRefreshLayout.OnRefreshListener,重写onRefresh()方法:
@Override public void onRefresh() { refreshLayout.postDelayed(new Runnable() { @Override public void run() { //请求网络,获取数据 refreshLayout.setRefreshing(false); } },3000); }