今天准备写一个关于listview下拉刷新的,我的实现思路:
1:我们知道实现上拉刷新是在listview头部添加一个headerView,下拉刷新是listview底部添加一个view
2:当我们下拉的时候让headerview随着我们的手在屏幕上移动的距离headerview慢慢显示出来,我们如果在布局问题中如果使用过android:layout_marginTop="-100dp"或者android:paddingTop="-100dp",现在我写一个demo,
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/ll_root" android:background="#ff00ff" > <TextView android:id="@+id/tv" android:layout_width="fill_parent" android:layout_height="100dp" android:text="helloworld" android:gravity="center" android:textColor="#000000" android:textSize="30sp" /> </LinearLayout> </LinearLayout>预览效果:
现在写几行代码把它隐藏掉:
LinearLayout ll_root = (LinearLayout) findViewById(R.id.ll_root); <span style="white-space:pre"> </span>ll_root.measure(0, 0); <span style="white-space:pre"> </span>int measuredHeight = ll_root.getMeasuredHeight(); <span style="white-space:pre"> </span>ll_root.setPadding(0, -measuredHeight, 0, 0);现在再预览屏幕就是一片空白了,如果把ll_root.setPadding(0, 0, 0, 0);就显示正常了,使用到我们listview头部headerview是不是可以和这样一样控制它的显示,现在画一个图,对着这个图想通了就好写代码了,
分析了这么多,我们开始新建一个android项目Refreshlistview,写一个RefreshListView类去继承ListView,
首先我们得做一些初始化的工作,先把头部的布局文件和底部加载更多布局写好,并初始化,因此需要再构造函数中写个init()方法了,而且我们一进来,headerview应该让它隐藏,init()方法代码如下:
private void init() { headerView = View.inflate(getContext(), R.layout.layout_header, null); initHeaderView(headerView); headerView.measure(0, 0); measuredHeaderViewHeight = headerView.getMeasuredHeight(); headerView.setPadding(0, -measuredHeaderViewHeight, 0, 0);//一进来让它隐藏 addHeaderView(headerView);//把头部view添加到listview中 initFooterView();//把底部view添加到listview中 }
/** * 初始化底部view */ private void initFooterView() { footerView = View.inflate(getContext(), R.layout.layout_footer, null); footerView.measure(0, 0);//主动通知系统去测量该view; measuredFooterViewHeight = footerView.getMeasuredHeight(); footerView.setPadding(0, -measuredFooterViewHeight, 0, 0); addFooterView(footerView); }
/** * 初始化头部view * @param headerView */ private void initHeaderView(View headerView) { iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow); pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate); tv_state = (TextView) headerView.findViewById(R.id.tv_state); tv_time = (TextView) headerView.findViewById(R.id.tv_time); }
@Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mDowny = ev.getY(); break; case MotionEvent.ACTION_MOVE: int distanceY = (int) (ev.getY()-mDowny);//表示在y轴上移动的距离 int paddingTop = -measuredHeaderViewHeight+distanceY;//算出headerview要显示的距离 headerView.setPadding(0, paddingTop, 0, 0); break; case MotionEvent.ACTION_UP: break; } return super.onTouchEvent(ev); }
比如我按住在4这个位置向下拖动listview,当我再往上拖动的时候,发现你手指到了不是在原来的位置,可能到了7或者8的位置,这是一个问题,还有一个问题就是在move的时候我们是不是设置了headerview向上的距离也就是这行代码:
headerView.setPadding(0, paddingTop, 0, 0);你会发现当你往上一直滑动的时候还会一直执行move中headerView.setPadding(0,paddingTop,0,0),打个比方吧,比如你headerview的高度是100,你往拖动了110,那么这个时候paddingTop是不是-100+110=10,它都超过headerview全部显示的时候了,因为headerview全部显示也就是paddingTop=0的时候,因此你一直执行headerView.setPadding()这个方法,对你功能没任何问题,但是确一直在执行,是不是我们要加个条件进行判断,这样就稍微提高了效率,至于加什么条件,我都说了,所以我们move中是这样写的
if(paddingTop> -measuredHeaderViewHeight){
headerView.setPadding(0, paddingTop, 0, 0);
}
但是还是没有解决手指问题,这是因为listview本身就自带的滑动事件,因此我们需要把listview本身的touch事件不让它起作用,只是我们手指拖动headerview在移动发生变化,我们就需要在刚才的条件中return true了,
if(paddingTop> -measuredHeaderViewHeight){
headerView.setPadding(0, paddingTop, 0, 0);
return true;
}
这样手指滑动错位问题就解决了!现在问题又来了当我们滑动到底部,再往上滑动的时候,发现滑不上去,那是因此return true,把listview的滑动事件给禁止了,因此我们要知道什么时候才让listview滑动事件禁止,是不是只有我们可见条目=0的时候,才把禁止了,第一个可见条目不等于我们就让listview可以滑动,listview给我们提供了一个api方法:getFirstVisiblePosition
因此if条件要写这样:
if(paddingTop> -measuredHeaderViewHeight&&getFirstVisiblePosition()==0){
headerView.setPadding(0, paddingTop, 0, 0);
return true;
}
ok,这样bug也解决了,现在就是什么下拉刷新,松开刷新,正在刷新,这三个状态改怎么弄?
其实观察很简单的,只要你拖动的距离和headerview的高度进行对比,如果高度小于headerview的高度就是下拉刷新状态,大于headerview的高度就是松开刷新,当手指离开屏幕就要正在刷新,但是正在刷新有个问题,比如我手指拖动的距离小于headerview的高度,这个时候放手就是正在刷新了,就是直接隐藏了,因此正在刷新状态是只有在松开刷新状态之后才是正在刷新,要加条件对其控制,
现在就写三个变量表示其对于的刷新三种状态! move中的关键代码:
int distanceY = (int) (ev.getY()-mDowny);//表示在y轴上移动的距离
int paddingTop = -measuredHeaderViewHeight+distanceY;//算出headerview要显示的距离
if(paddingTop> -measuredHeaderViewHeight&&getFirstVisiblePosition()==0){
headerView.setPadding(0, paddingTop, 0, 0);
if(paddingTop>0){
mCurrentState = RELEASE_REFRESH;
}else if(paddingTop<0){
mCurrentState = PULL_REFRESH;
}
return true;
}
这里又有个和那个刷新一样的问题,当时处于下拉刷新状态,我一直拉还是会执行这个赋值方法 mCurrentState = PULL_REFRESH;是不是不需要啊,因为我把mCurrentState 初始化状态就是PULL_REFRESH状态,是不是当松开状态转变成下拉刷新状态我再执行这个方法,
if(paddingTop> -measuredHeaderViewHeight&&getFirstVisiblePosition()==0){ headerView.setPadding(0, paddingTop, 0, 0); if(paddingTop>0&&mCurrentState==PULL_REFRESH){ mCurrentState = RELEASE_REFRESH; }else if(paddingTop<0&&mCurrentState==RELEASE_REFRESH){ mCurrentState = PULL_REFRESH; } return true; }
private void refreshUI(int state) { switch (state) { case PULL_REFRESH: tv_state.setText("下拉刷新"); iv_arrow.startAnimation(downAnimation); break; case RELEASE_REFRESH: tv_state.setText("松开刷新"); iv_arrow.startAnimation(upAnimation); break; case REFRESHING: iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完 iv_arrow.setVisibility(View.INVISIBLE); pb_rotate.setVisibility(View.VISIBLE); tv_state.setText("正在刷新..."); break; } }关于headerview中的进度条旋转动画在这就不讲了,
我们进一步完善这个功能,现在还差一个正在刷新功能了,刚才上面分析了正在刷新的状态是怎么来的,
case MotionEvent.ACTION_UP: if(mCurrentState ==RELEASE_REFRESH){ mCurrentState = REFRESHING; refreshUI(mCurrentState); } if(mCurrentState == PULL_REFRESH){//当是下拉刷新的状态手指离开这个时候是隐藏headerview headerView.setPadding(0, -measuredHeaderViewHeight, 0, 0); } break;
public interface PullRefreshListener{
void onRefresh();
void onLoadMore();
}
因此我们只要在手指(UP)的时候去调用onRefresh(),等数据加载完成再去真正调这个方法,我们现在通过handler去模拟请求网络
refresh_listview.setPullRefreshListener(new PullRefreshListener() { @Override public void onRefresh() { new Handler().postDelayed(new Runnable() {//模拟网络请求 @Override public void run() { datas.add(0,"网络请求成功!"); adapter.notifyDataSetChanged(); refresh_listview.completeRefresh(); } }, 5000); } @Override public void onLoadMore() { } });
public void completeRefresh() { //重置headerView状态 headerView.setPadding(0, -measuredHeaderViewHeight, 0, 0); mCurrentState = PULL_REFRESH;//回到下拉刷新状态 iv_arrow.setVisibility(View.VISIBLE);//回到下拉刷新状态 pb_rotate.setVisibility(View.INVISIBLE);//回到下拉刷新状态 tv_state.setText("下拉刷新"); tv_time.setText("最后刷新:"+getCurrentTime()); }
现在就是滑动到底部自动加载更多,这个功能简单,因为listview给我们提供了滚动监听,所以只要实现下OnScrollListener只要在onScrollStateChanged()方法中做个判断就ok,然后也是模拟网络请求
@Override public void onScrollStateChanged(AbsListView arg0, int scrollState) { if(scrollState==OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){ isLoadingMore = true; footerView.setPadding(0, 0, 0, 0);//显示出footerView setSelection(getCount());//让listview最后一条显示出来 if(pullRefreshListener!=null){ pullRefreshListener.onLoadMore(); } } }