android 自定义listview实现下拉刷新(一)

今天准备写一个关于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);
	}

现在我们就要实现下拉的过程中慢慢的把headerview显示出来了,因此listview需要复写onTouchEvent()方法了,上面分析了当我们手指在屏幕上滑动的距离,得先记录你手指按下的点,然后move的点,因此onTouchEvent()方法就是这样了,

@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;
			 }

因此在这需要加个条件,也是优化了点效率,这个时候就需要根据刷新的状态改变UI了,

	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;

当时松开状态进去到正在刷新,一般正在刷新是去请求服务器,那你知道什么时候加载数据完成么,listview肯定不知道,所以要用到回调了,

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() {
				
			}
		});

当完成了加载就调用completeRefresh()方法,这个时候就要把一些状态重置下了,

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

不过这个时候有个小bug要处理下,当正处于正在刷新状态还是可以拖动的,所以我们在move的时候先判断是否处于正在刷新,如果处于正在刷新就把让move不执行了,break;就行

现在就是滑动到底部自动加载更多,这个功能简单,因为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();
			}
		}
	}

发现市面上有很多时当在加载的时候是不让加载更多的,当加载更多不能进行下拉刷新的,这个就是加个条件去判断就行了,在这就不弄了!写了一上午,手疼的不行了,要休息了!到此结束












  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值