ListView操作之下拉刷新

最初的下拉加载应该是ios上的效果,现在很多应用如新浪微博等都加入了这个操作。即下拉listview刷新列表,这无疑是一个非常友好的操作。今天就和大家分享下这个操作的实现。

先看下运行效果:

11.jpg

22.jpg






代码参考国外朋友Johan Nilsson的实现,http://johannilsson.com/2011/03/ ... refresh-update.html

主要原理为监听触摸和滑动操作,在listview头部加载一个视图。那要做的其实很简单:1.写好加载到listview头部的view 2.重写listview,实现onTouchEvent方法和onScroll方法,监听滑动状态。计算headview全部显示出来即可实行加载动作,加载完成即刷新列表。重新隐藏headview。

首先写下headview的xml代码:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="fill_parent"
  3.     android:layout_height="fill_parent"
  4.     android:paddingTop="10dip"
  5.     android:paddingBottom="15dip"
  6.     android:gravity="center"
  7.         android:id="@+id/pull_to_refresh_header"
  8.     >
  9.     <ProgressBar 
  10.         android:id="@+id/pull_to_refresh_progress"
  11.         android:indeterminate="true"
  12.         android:layout_width="wrap_content"
  13.         android:layout_height="wrap_content"
  14.         android:layout_marginLeft="30dip"
  15.         android:layout_marginRight="20dip"
  16.         android:layout_marginTop="10dip"
  17.         android:visibility="gone"
  18.         android:layout_centerVertical="true"
  19.         style="?android:attr/progressBarStyleSmall"
  20.         />
  21.     <ImageView
  22.         android:id="@+id/pull_to_refresh_image"
  23.         android:layout_width="wrap_content"
  24.         android:layout_height="wrap_content"
  25.         android:layout_marginLeft="30dip"
  26.         android:layout_marginRight="20dip"
  27.         android:visibility="gone"
  28.         android:layout_gravity="center"
  29.         android:gravity="center"
  30.         android:src="@drawable/ic_pulltorefresh_arrow"
  31.         />
  32.     <TextView
  33.         android:id="@+id/pull_to_refresh_text"
  34.         android:textAppearance="?android:attr/textAppearanceMedium"
  35.         android:textStyle="bold"
  36.         android:paddingTop="5dip"
  37.         android:layout_width="fill_parent"
  38.         android:layout_height="wrap_content"
  39.         android:layout_gravity="center"
  40.         android:gravity="center"
  41.         />
  42.     <TextView
  43.         android:id="@+id/pull_to_refresh_updated_at"
  44.         android:layout_below="@+id/pull_to_refresh_text"
  45.         android:visibility="gone"
  46.         android:textAppearance="?android:attr/textAppearanceSmall"
  47.         android:layout_width="fill_parent"
  48.         android:layout_height="wrap_content"
  49.         android:layout_gravity="center"
  50.         android:gravity="center"
  51.         />
  52. </RelativeLayout>
复制代码

代码比较简单,即headview包括一个进度条一个箭头和两段文字(一个显示加载状态,另一个显示最后刷新时间,本例就不设置了)。

而后重写listview,代码如下:

  1. package com.notice.pullrefresh;


  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.view.LayoutInflater;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.view.animation.LinearInterpolator;
  9. import android.view.animation.RotateAnimation;
  10. import android.widget.AbsListView;
  11. import android.widget.AbsListView.OnScrollListener;
  12. import android.widget.ImageView;
  13. import android.widget.ListAdapter;
  14. import android.widget.ListView;
  15. import android.widget.ProgressBar;
  16. import android.widget.RelativeLayout;
  17. import android.widget.TextView;



  18. public class PullToRefreshListView extends ListView implements OnScrollListener {

  19.         // 状态
  20.     private static final int TAP_TO_REFRESH = 1;
  21.     private static final int PULL_TO_REFRESH = 2;
  22.     private static final int RELEASE_TO_REFRESH = 3;
  23.     private static final int REFRESHING = 4;


  24.     private OnRefreshListener mOnRefreshListener;


  25.     // 监听对listview的滑动动作
  26.     private OnScrollListener mOnScrollListener;
  27.     private LayoutInflater mInflater;

  28.     //顶部刷新时出现的控件
  29.     private RelativeLayout mRefreshView;
  30.     private TextView mRefreshViewText;
  31.     private ImageView mRefreshViewImage;
  32.     private ProgressBar mRefreshViewProgress;
  33.     private TextView mRefreshViewLastUpdated;

  34.         // 当前滑动状态
  35.     private int mCurrentScrollState;
  36.         // 当前刷新状态
  37.     private int mRefreshState;
  38.     
  39.         // 箭头动画效果
  40.     private RotateAnimation mFlipAnimation;
  41.     private RotateAnimation mReverseFlipAnimation;

  42.     private int mRefreshViewHeight;
  43.     private int mRefreshOriginalTopPadding;
  44.     private int mLastMotionY;

  45.         private boolean mBounceHack;

  46.     public PullToRefreshListView(Context context) {
  47.         super(context);
  48.         init(context);
  49.     }

  50.     public PullToRefreshListView(Context context, AttributeSet attrs) {
  51.         super(context, attrs);
  52.         init(context);
  53.     }

  54.     public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
  55.         super(context, attrs, defStyle);
  56.         init(context);
  57.     }

  58.     /**
  59.      * 初始化控件和箭头动画(这里直接在代码中初始化动画而不是通过xml)
  60.      */
  61.     private void init(Context context) {
  62.         mFlipAnimation = new RotateAnimation(0, -180,
  63.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,
  64.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  65.         mFlipAnimation.setInterpolator(new LinearInterpolator());
  66.         mFlipAnimation.setDuration(250);
  67.         mFlipAnimation.setFillAfter(true);
  68.         mReverseFlipAnimation = new RotateAnimation(-180, 0,
  69.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,
  70.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);
  71.         mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
  72.         mReverseFlipAnimation.setDuration(250);
  73.         mReverseFlipAnimation.setFillAfter(true);

  74.         mInflater = (LayoutInflater) context.getSystemService(
  75.                 Context.LAYOUT_INFLATER_SERVICE);

  76.                 mRefreshView = (RelativeLayout) mInflater.inflate(
  77.                                 R.layout.pull_to_refresh_header, this, false);
  78.         mRefreshViewText =
  79.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);
  80.         mRefreshViewImage =
  81.             (ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);
  82.         mRefreshViewProgress =
  83.             (ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);
  84.         mRefreshViewLastUpdated =
  85.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);

  86.         mRefreshViewImage.setMinimumHeight(50);
  87.         mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();

  88.         mRefreshState = TAP_TO_REFRESH;
  89.         
  90.         //为listview头部增加一个view
  91.         addHeaderView(mRefreshView);

  92.         super.setOnScrollListener(this);

  93.                 measureView(mRefreshView);
  94.         mRefreshViewHeight = mRefreshView.getMeasuredHeight();
  95.     }

  96.     @Override
  97.     protected void onAttachedToWindow() {
  98.         setSelection(1);
  99.     }

  100.     @Override
  101.     public void setAdapter(ListAdapter adapter) {
  102.         super.setAdapter(adapter);

  103.         setSelection(1);
  104.     }

  105.     /**
  106.      * 设置滑动监听器
  107.      * 
  108.      */
  109.     @Override
  110.     public void setOnScrollListener(AbsListView.OnScrollListener l) {
  111.         mOnScrollListener = l;
  112.     }

  113.     /**
  114.      * 注册一个list需要刷新时的回调接口
  115.      * 
  116.      */
  117.     public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
  118.         mOnRefreshListener = onRefreshListener;
  119.     }

  120.     /**
  121.          * 设置标签显示何时最后被刷新
  122.          * 
  123.          * @param lastUpdated
  124.          *            Last updated at.
  125.          */
  126.     public void setLastUpdated(CharSequence lastUpdated) {
  127.         if (lastUpdated != null) {
  128.             mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
  129.             mRefreshViewLastUpdated.setText(lastUpdated);
  130.         } else {
  131.             mRefreshViewLastUpdated.setVisibility(View.GONE);
  132.         }
  133.     }

  134.         // 实现该方法处理触摸
  135.     @Override
  136.     public boolean onTouchEvent(MotionEvent event) {
  137.         final int y = (int) event.getY();
  138.         mBounceHack = false;

  139.         switch (event.getAction()) {

  140.             case MotionEvent.ACTION_UP:
  141.                 if (!isVerticalScrollBarEnabled()) {
  142.                     setVerticalScrollBarEnabled(true);
  143.                 }
  144.                 if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
  145.                                 // 拖动距离达到刷新需要
  146.                     if ((mRefreshView.getBottom() >= mRefreshViewHeight
  147.                             || mRefreshView.getTop() >= 0)
  148.                             && mRefreshState == RELEASE_TO_REFRESH) {
  149.                                         // 把状态设置为正在刷新
  150.                         mRefreshState = REFRESHING;
  151.                                         // 准备刷新
  152.                         prepareForRefresh();
  153.                                         // 刷新
  154.                         onRefresh();
  155.                     } else if (mRefreshView.getBottom() < mRefreshViewHeight
  156.                             || mRefreshView.getTop() <= 0) {
  157.                                         // 中止刷新
  158.                         resetHeader();
  159.                         setSelection(1);
  160.                     }
  161.                 }
  162.                 break;
  163.             case MotionEvent.ACTION_DOWN:
  164.                         // 获得按下y轴位置
  165.                 mLastMotionY = y;
  166.                 break;
  167.             case MotionEvent.ACTION_MOVE:
  168.                         // 计算边距
  169.                 applyHeaderPadding(event);
  170.                 break;
  171.         }
  172.         return super.onTouchEvent(event);
  173.     }

  174.         // 获得header的边距
  175.     private void applyHeaderPadding(MotionEvent ev) {

  176.         int pointerCount = ev.getHistorySize();

  177.         for (int p = 0; p < pointerCount; p++) {
  178.             if (mRefreshState == RELEASE_TO_REFRESH) {
  179.                 if (isVerticalFadingEdgeEnabled()) {
  180.                     setVerticalScrollBarEnabled(false);
  181.                 }

  182.                 int historicalY = (int) ev.getHistoricalY(p);

  183.                                 // 计算申请的边距,除以1.7使得拉动效果更好
  184.                 int topPadding = (int) (((historicalY - mLastMotionY)
  185.                         - mRefreshViewHeight) / 1.7);

  186.                 mRefreshView.setPadding(
  187.                         mRefreshView.getPaddingLeft(),
  188.                         topPadding,
  189.                         mRefreshView.getPaddingRight(),
  190.                         mRefreshView.getPaddingBottom());
  191.             }
  192.         }
  193.     }

  194.     /**
  195.          * 将head的边距重置为初始的数值
  196.          */
  197.     private void resetHeaderPadding() {
  198.         mRefreshView.setPadding(
  199.                 mRefreshView.getPaddingLeft(),
  200.                 mRefreshOriginalTopPadding,
  201.                 mRefreshView.getPaddingRight(),
  202.                 mRefreshView.getPaddingBottom());
  203.     }

  204.     /**
  205.          * 重置header为之前的状态
  206.          */
  207.     private void resetHeader() {
  208.         if (mRefreshState != TAP_TO_REFRESH) {
  209.             mRefreshState = TAP_TO_REFRESH;

  210.             resetHeaderPadding();

  211.                         // 将刷新图标换成箭头
  212.             mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);
  213.                         // 清除动画
  214.             mRefreshViewImage.clearAnimation();
  215.                         // 隐藏图标和进度条
  216.             mRefreshViewImage.setVisibility(View.GONE);
  217.             mRefreshViewProgress.setVisibility(View.GONE);
  218.         }
  219.     }

  220.         // 估算headview的width和height
  221.     private void measureView(View child) {
  222.         ViewGroup.LayoutParams p = child.getLayoutParams();
  223.         if (p == null) {
  224.             p = new ViewGroup.LayoutParams(
  225.                     ViewGroup.LayoutParams.FILL_PARENT,
  226.                     ViewGroup.LayoutParams.WRAP_CONTENT);
  227.         }

  228.         int childWidthSpec = ViewGroup.getChildMeasureSpec(0,
  229.                 0 + 0, p.width);
  230.         int lpHeight = p.height;
  231.         int childHeightSpec;
  232.         if (lpHeight > 0) {
  233.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
  234.         } else {
  235.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
  236.         }
  237.         child.measure(childWidthSpec, childHeightSpec);
  238.     }

  239.     @Override
  240.     public void onScroll(AbsListView view, int firstVisibleItem,
  241.             int visibleItemCount, int totalItemCount) {

  242.                 // 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头
  243.         if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
  244.                 && mRefreshState != REFRESHING) {
  245.             if (firstVisibleItem == 0) {
  246.                 mRefreshViewImage.setVisibility(View.VISIBLE);
  247.                 if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20
  248.                         || mRefreshView.getTop() >= 0)
  249.                         && mRefreshState != RELEASE_TO_REFRESH) {
  250.                                         mRefreshViewText.setText("松开加载...");
  251.                     mRefreshViewImage.clearAnimation();
  252.                     mRefreshViewImage.startAnimation(mFlipAnimation);
  253.                     mRefreshState = RELEASE_TO_REFRESH;
  254.                 } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
  255.                         && mRefreshState != PULL_TO_REFRESH) {
  256.                                         mRefreshViewText.setText("下拉刷新...");
  257.                     if (mRefreshState != TAP_TO_REFRESH) {
  258.                         mRefreshViewImage.clearAnimation();
  259.                         mRefreshViewImage.startAnimation(mReverseFlipAnimation);
  260.                     }
  261.                     mRefreshState = PULL_TO_REFRESH;
  262.                 }
  263.             } else {
  264.                 mRefreshViewImage.setVisibility(View.GONE);
  265.                 resetHeader();
  266.             }
  267.         } else if (mCurrentScrollState == SCROLL_STATE_FLING
  268.                 && firstVisibleItem == 0
  269.                 && mRefreshState != REFRESHING) {
  270.             setSelection(1);
  271.                         mBounceHack = true;
  272.                 } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
  273.             setSelection(1);
  274.         }

  275.         if (mOnScrollListener != null) {
  276.             mOnScrollListener.onScroll(view, firstVisibleItem,
  277.                     visibleItemCount, totalItemCount);
  278.         }
  279.     }

  280.     @Override
  281.     public void onScrollStateChanged(AbsListView view, int scrollState) {
  282.         mCurrentScrollState = scrollState;

  283.         if (mCurrentScrollState == SCROLL_STATE_IDLE) {
  284.             mBounceHack = false;
  285.         }

  286.         if (mOnScrollListener != null) {
  287.             mOnScrollListener.onScrollStateChanged(view, scrollState);
  288.         }
  289.     }

  290.     public void prepareForRefresh() {
  291.                 resetHeaderPadding();// 恢复header的边距

  292.         mRefreshViewImage.setVisibility(View.GONE);
  293.                 // 注意加上,否则仍然显示之前的图片
  294.         mRefreshViewImage.setImageDrawable(null);
  295.         mRefreshViewProgress.setVisibility(View.VISIBLE);

  296.                 // 设置文字
  297.                 mRefreshViewText.setText("加载中...");

  298.         mRefreshState = REFRESHING;
  299.     }

  300.     public void onRefresh() {

  301.         if (mOnRefreshListener != null) {
  302.             mOnRefreshListener.onRefresh();
  303.         }
  304.     }

  305.     /**
  306.          * 重置listview为普通的listview,该方法设置最后更新时间
  307.          * 
  308.          * @param lastUpdated
  309.          *            Last updated at.
  310.          */
  311.     public void onRefreshComplete(CharSequence lastUpdated) {
  312.         setLastUpdated(lastUpdated);
  313.         onRefreshComplete();
  314.     }

  315.     /**
  316.          * 重置listview为普通的listview,不设置最后更新时间
  317.          */
  318.     public void onRefreshComplete() {        

  319.         resetHeader();

  320.                 // 如果refreshview在加载结束后可见,下滑到下一个条目
  321.         if (mRefreshView.getBottom() > 0) {
  322.             invalidateViews();
  323.             setSelection(1);
  324.         }
  325.     }



  326.     /**
  327.          * 刷新监听器接口
  328.          */
  329.     public interface OnRefreshListener {
  330.         /**
  331.                  * list需要被刷新时调用
  332.                  */
  333.         public void onRefresh();
  334.     }
  335. }
复制代码

相信我注释已经写的比较详细了,主要注意onTouchEvent和onScroll方法,在这里面计算头部边距,从而通过用户的手势实现“下拉刷新”到“松开加载”以及“加载”三个状态的切换。其中还有一系列和header有关的方法,用来设置header的显示以及取得header的边距。于此同时,代码留出了接口以供调用。

那么现在写一个测试Activity来试验下效果:

  1. package com.notice.pullrefresh;

  2. import java.util.Arrays;
  3. import java.util.LinkedList;

  4. import android.app.ListActivity;
  5. import android.os.AsyncTask;
  6. import android.os.Bundle;
  7. import android.widget.ArrayAdapter;

  8. import com.notice.pullrefresh.PullToRefreshListView.OnRefreshListener;


  9. public class PullrefreshActivity extends ListActivity {
  10.         private LinkedList<String> mListItems;
  11.         ArrayAdapter<String> adapter;

  12.         /** Called when the activity is first created. */
  13.     @Override
  14.     public void onCreate(Bundle savedInstanceState) {
  15.         super.onCreate(savedInstanceState);
  16.                 setContentView(R.layout.pull_to_refresh);

  17.                 // list需要刷新时调用
  18.                 ((PullToRefreshListView) getListView())
  19.                                 .setOnRefreshListener(new OnRefreshListener() {
  20.                                         @Override
  21.                                         public void onRefresh() {
  22.                                                 // 在这执行后台工作
  23.                                                 new GetDataTask().execute();
  24.                                         }
  25.                                 });



  26.                 mListItems = new LinkedList<String>();
  27.                 mListItems.addAll(Arrays.asList(mStrings));

  28.                 adapter = new ArrayAdapter<String>(this,
  29.                                 android.R.layout.simple_list_item_1, mListItems);

  30.                 setListAdapter(adapter);
  31.         }


  32.         private class GetDataTask extends AsyncTask<Void, Void, String[]> {

  33.                 @Override
  34.                 protected String[] doInBackground(Void... params) {
  35.                         // 在这里可以做一些后台工作
  36.                         try {
  37.                                 Thread.sleep(2000);
  38.                         } catch (InterruptedException e) {
  39.                                 e.printStackTrace();
  40.                         }
  41.                         return mStrings;
  42.                 }

  43.                 @Override
  44.                 protected void onPostExecute(String[] result) {
  45.                         // 下拉后增加的内容
  46.                         mListItems.addFirst("Added after refresh...");

  47.                         // 刷新完成调用该方法复位
  48.                         ((PullToRefreshListView) getListView()).onRefreshComplete();

  49.                         super.onPostExecute(result);
  50.                 }
  51.         }

  52.         private String[] mStrings = { "normal data1", "normal data2",
  53.                         "nomal data3", "normal data4", "norma data5", "normal data6" };
  54. }
复制代码

代码通过asyncTask实现一个异步操作,并通过设置onRefreshListener监听器调用onRefresh方法实现下拉时刷新,并在刷新完成后调用onRefreshComplete做复位处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值