首页 > 代码库 > 【安卓笔记】下拉刷新组件的使用及实现

【安卓笔记】下拉刷新组件的使用及实现

项目中如果需要实现下拉刷新一般有以下几个选择:
1.使用开源库Android-pullToRefresh。
2.使用support.v4包提供的SwipeRefreshLayout。
3.自己实现一个。

下面分别简单介绍:
注:以listView下拉刷新为例.
方案1:使用开源库Android-pullToRefresh
1.下载Android-PullToRefresh开源库(https://github.com/chrisbanes/Android-PullToRefresh)
2.将library工程导入到eclipse中
3.创建一个新工程,并在properties->android选项中引用library库
4.编写代码

该库提供了PullToRefreshListView,我们用它替换ListView,它在使用上跟ListView一致。
但是其提供了一个监听下拉更新的接口,我们通过setOnRefreshListener注册该监听器,然后在onRefresh回调方法中异步更新数据即可,更新完成后需调用onRefreshComplete方法完成更新。
示例:
页面布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.flushlistview.MainActivity" >
    <com.handmark.pulltorefresh.library.PullToRefreshListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="#19000000"
        android:dividerHeight="4dp" >
    </com.handmark.pulltorefresh.library.PullToRefreshListView>
</RelativeLayout>

listView的item布局:
<?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="match_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000"
        android:textSize="20sp" />
</LinearLayout>
界面逻辑:
package com.example.flushlistview;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
/**
 * @author Rowandjj
 *
 *使用pull-to-refresh库实现下拉刷新操作
 */
public class MainActivity extends Activity
{
	private PullToRefreshListView lv;
	private ArrayAdapter<String> adapter;
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	    lv = (PullToRefreshListView) findViewById(R.id.lv);
		List<String> list = new ArrayList<String>();
		list.add("张三");
		list.add("李四");
		list.add("王五");
		list.add("赵六");
		list.add("啊啊");
		list.add("呵呵");
		list.add("嘻嘻");
		list.add("嘿嘿");
		adapter = new ArrayAdapter<String>(this,
				R.layout.item, R.id.tv, list);
		lv.setAdapter(adapter);
		//实现刷新接口
		lv.setOnRefreshListener(new OnRefreshListener<ListView>()
		{
			@Override
			public void onRefresh(PullToRefreshBase<ListView> refreshView)
			{
				Date date = new Date(System.currentTimeMillis());
				SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日hh时mm分ss秒",Locale.CHINA);
				String updateTime = format.format(date);
				
				refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(updateTime);
				//异步刷新
				new UpdateTask().execute();
			}
			
		});
	}
	private class UpdateTask extends AsyncTask<Void, Void,List<String>>
	{
		@Override
		protected List<String> doInBackground(Void... params)
		{
			try
			{
				Thread.sleep(2000);
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			List<String> newData = http://www.mamicode.com/new ArrayList();>效果:
技术分享

方案2:使用support.v4包提供的SwipeRefreshLayout
SwipeRefreshLayout是support.v4包提供的一个类,可以实现具有Material Design效果的下拉刷新。
使用方式上也很简单,只要将ListView/RecyclerView或者其他View放置在此layout之中,然后调用setOnRefreshListener注册数据更新接口,并在onRefresh方法中处理数据更新事件即可。当数据更新完毕,调用setRefreshing并将参数置为false即可。
注:
1. SwipeRefreshLayout只能包裹一个子View。
2.如果你的support.v4包中没有这个类,那么需要更新之。

示例:
页面布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.swiperefreshlayoutdemo.MainActivity" >
    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/activity_main_swipe_refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <ListView
            android:id="@+id/activity_main_listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </ListView>
    </android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
item布局:
<?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:orientation="vertical" >
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="40dip"
        android:textColor="#000"
        android:gravity="center_vertical"
        android:textSize="18sp" />
</LinearLayout>
页面逻辑:
package com.example.swiperefreshlayoutdemo;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends Activity
{
	private ListView mListView;
	private SwipeRefreshLayout mRefreshLayout;
	private ArrayAdapter<String> mAdapter;
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mListView = (ListView) findViewById(R.id.activity_main_listview);
		mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.activity_main_swipe_refresh_layout);
		List<String> data = http://www.mamicode.com/new ArrayList();>效果:
技术分享
方案3:自己实现一个
要想实现一个下拉刷新效果需要对android的事件分发、事件回调机制有清晰的认识。
这里我直接继承自ListView,然后通过实现OnScrollListener监听屏幕滚动事件,因为只有ListView滑动到首部才能下滑刷新,另外,需要重写onTouchEvent方法,根据手指位置动态更新布局。
我们通过分析传统的下拉刷新,发现有四种状态,1.正常态、2.下拉刷新态、3.释放刷新态、4.刷新态
当手指按下时,首先判断当前listView的第一个item是否可见,如果不可见那么不做任何处理,否则记下当前位置。
手指移动时,计算竖直方向的偏移量,然后根据偏移量改变当前状态,并根据当前状态更新布局。这里的布局指的是listView的headerView,当然,默认情况下,这个view是隐藏的,我们可以设置其padding为负的headerview的高度。
手指松开时,判断当前状态,如果是刷新态,那么调用回调接口处理更新逻辑。

先贴出代码:
package com.example.mypulltorefreshlistview.ui;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.mypulltorefreshlistview.R;
public class PullToRefreshListView extends ListView implements OnScrollListener
{
	private static final String TAG = "PullToRefreshListView";
	/**
	 *顶部布局 
	 */
	private View mHeaderView;
	
	/**
	 * header的高度
	 */
	private int mHeaderHeight;
	
	/**
	 * 当前页面已经滑到顶部
	 */
	private boolean flag;
	
	/**
	 * 初始滑动时的y坐标
	 */
	private int mStartY;
	
	/**
	 * 正常状态
	 */
	public static final int STATE_NORMAL = 0;
	/**
	 * 下拉刷新状态
	 */
	public static final int STATE_PULL_TO_REFRESH = 1;
	/**
	 * 释放刷新状态
	 */
	public static final int STATE_RELEASE_TO_REFRESH = 2;
	/**
	 * 正在刷新状态
	 */
	public static final int STATE_REFRESH = 3;
	
	/**
	 * 当前状态
	 */
	private int mCurrentState;
	
	
	private static final int DEFAULT_LENGTH = 70;
	
	private OnRefreshListener mRefreshListener;
	
	public PullToRefreshListView(Context context, AttributeSet attrs,
			int defStyle)
	{
		super(context, attrs, defStyle);
		init(context);
	}
	public PullToRefreshListView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init(context);
	}
	public PullToRefreshListView(Context context)
	{
		super(context);
		init(context);
	}
	public void setOnRefreshListener(OnRefreshListener listener)
	{
		this.mRefreshListener = listener;
	}
	/**
	 * 初始化操作
	 * @param context
	 */
	private void init(Context context)
	{
		//添加headerview
		mHeaderView = LayoutInflater.from(context).inflate(R.layout.header_layout,null);
		this.addHeaderView(mHeaderView);
		//设置滚动监听器
		this.setOnScrollListener(this);
		
		//通过设置padding将hider隐藏
		//注:因为此时无法获得header的高度,所以放入MessageQueue中
		post(new HideHeaderAction());
	}
	
	
	/**
	 * 设置header的padding
	 * @param topPadding
	 */
	private void setHeaderTopPadding(int topPadding)
	{
		if(mHeaderView != null)
		{
			mHeaderView.setPadding(mHeaderView.getPaddingLeft(),topPadding,mHeaderView.getPaddingRight(),mHeaderView.getPaddingBottom());
		}
	}
	
	
	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount)
	{
		if(firstVisibleItem == 0)
			flag = true;
		else
			flag = false;
	}
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState)
	{
	}
	@Override
	public boolean onTouchEvent(MotionEvent ev)
	{
		int action = ev.getAction();
		switch (action)
		{
		case MotionEvent.ACTION_DOWN:
			if(flag)
			{
				mStartY = (int) ev.getY();
			}
			break;
		case MotionEvent.ACTION_MOVE:
			performMove(ev);
			break;
		case MotionEvent.ACTION_UP:
			if(mCurrentState == STATE_RELEASE_TO_REFRESH)
			{
				mCurrentState = STATE_REFRESH;
				updateUIByState();
				//TODO 加载新数据
				if(mRefreshListener == null)
				{
					throw new RuntimeException("you must call setOnRefreshListener before...");
				}else
				{
					mRefreshListener.onRefresh(this);
				}
			}else if(mCurrentState == STATE_PULL_TO_REFRESH)
			{
				mCurrentState = STATE_NORMAL;
				flag = false;
				updateUIByState();
			}
			
			break;
		}
		return super.onTouchEvent(ev);
	}
	private void updateUIByState()
	{
		TextView tip = (TextView) findViewById(R.id.tip);
		ImageView arrow = (ImageView) findViewById(R.id.arrow);
		ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress);
		Animation anim1 = AnimationUtils.loadAnimation(getContext(),R.anim.rotate_1);
		Animation anim2 = AnimationUtils.loadAnimation(getContext(),R.anim.rotate_2);
		
		switch (mCurrentState)
		{
		case STATE_NORMAL:
			setHeaderTopPadding(-mHeaderHeight);
			break;
		case STATE_PULL_TO_REFRESH:
			arrow.clearAnimation();
			arrow.setAnimation(anim1);
			arrow.setVisibility(View.VISIBLE);
			progressBar.setVisibility(View.GONE);
			tip.setText("下拉可以刷新...");
			break;
		case STATE_RELEASE_TO_REFRESH:
			arrow.clearAnimation();
			arrow.setAnimation(anim2);
			arrow.setVisibility(View.VISIBLE);
			progressBar.setVisibility(View.GONE);
			tip.setText("松开可以刷新...");
			break;
		case STATE_REFRESH:
			arrow.clearAnimation();
			setHeaderTopPadding(mHeaderHeight);
			arrow.setVisibility(View.GONE);
			progressBar.setVisibility(View.VISIBLE);
			tip.setText("正在刷新...");
			break;
		default:
			break;
		}
	}
	
	private void performMove(MotionEvent ev)
	{
		if(!flag)
		{
			return;
		}
		int currY = (int) ev.getY();
		int deltaY = currY-mStartY;
		if(deltaY > mHeaderHeight+DEFAULT_LENGTH)
			deltaY = mHeaderHeight+DEFAULT_LENGTH;
		
		switch (mCurrentState)
		{
		case STATE_NORMAL:
			if(deltaY > 0)
			{
				mCurrentState = STATE_PULL_TO_REFRESH;
			}
			break;
		case STATE_PULL_TO_REFRESH:
			setHeaderTopPadding(deltaY-mHeaderHeight);
			updateUIByState();
			if(deltaY >= mHeaderHeight+DEFAULT_LENGTH)
			{
				mCurrentState = STATE_RELEASE_TO_REFRESH;
			}else if(deltaY <= 0)
			{
				mCurrentState = STATE_NORMAL;
			}
			break;
		case STATE_RELEASE_TO_REFRESH:
//			setHeaderTopPadding(deltaY-mHeaderHeight);
			updateUIByState();
			if(deltaY < mHeaderHeight+DEFAULT_LENGTH)
			{
				mCurrentState = STATE_PULL_TO_REFRESH;
			}else if(deltaY <= 0)
			{
				mCurrentState = STATE_NORMAL;
			}
			break;
		}
		
	}
	
	/**
	 * 更新完毕时调用
	 */
	public void refreshComplete()
	{
		TextView lastUpdateTime = (TextView) findViewById(R.id.last_update_time);
		SimpleDateFormat format = new SimpleDateFormat("MM-dd hh:mm",Locale.CHINA);
		String updateTime = format.format(new Date(System.currentTimeMillis()));
		lastUpdateTime.setText("更新于 "+updateTime);
		mCurrentState = STATE_NORMAL;
		updateUIByState();
	}
	
	public interface OnRefreshListener
	{
		public void onRefresh(PullToRefreshListView listView);
	}
	
	private class HideHeaderAction implements Runnable
	{
		@Override
		public void run()
		{
			//获取header的高度
			mHeaderHeight = mHeaderView.getMeasuredHeight();//view在被渲染前无法获得其宽高
			Log.d(TAG,"Headerheight:"+mHeaderHeight);
			//通过设置header的padding来隐藏header
			setHeaderTopPadding(-mHeaderHeight);
		}
	}
}
使用上跟上面差不多,也是需要添加监听器,然后处理回调方法,数据更新完调用refreshComplete。
当然,这个只是一个简单的demo,效果可能不是很好,仅供参考~









【安卓笔记】下拉刷新组件的使用及实现