android UI进阶之实现listview的下拉加载

关于listview的操作五花八门,有下拉刷新,分级显示,分页列表,逐页加载等,以后会陆续和大家分享这些技术,今天讲下下拉加载这个功能的实现。

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

先看下运行效果:


   

 

   


 

代码参考国外朋友Johan Nilsson的实现,http://johannilsson.com/2011/03/13/android-pull-to-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.     
  3.     
  4. import android.content.Context;    
  5. import android.util.AttributeSet;    
  6. import android.view.LayoutInflater;    
  7. import android.view.MotionEvent;    
  8. import android.view.View;    
  9. import android.view.ViewGroup;    
  10. import android.view.animation.LinearInterpolator;    
  11. import android.view.animation.RotateAnimation;    
  12. import android.widget.AbsListView;    
  13. import android.widget.AbsListView.OnScrollListener;    
  14. import android.widget.ImageView;    
  15. import android.widget.ListAdapter;    
  16. import android.widget.ListView;    
  17. import android.widget.ProgressBar;    
  18. import android.widget.RelativeLayout;    
  19. import android.widget.TextView;    
  20.     
  21.      
  22.     
  23. public class PullToRefreshListView extends ListView implements OnScrollListener {    
  24.     
  25.     // 状态    
  26.     private static final int TAP_TO_REFRESH = 1;    
  27.     private static final int PULL_TO_REFRESH = 2;    
  28.     private static final int RELEASE_TO_REFRESH = 3;    
  29.     private static final int REFRESHING = 4;    
  30.     
  31.     
  32.     private OnRefreshListener mOnRefreshListener;    
  33.     
  34.     
  35.     // 监听对listview的滑动动作    
  36.     private OnScrollListener mOnScrollListener;    
  37.     private LayoutInflater mInflater;    
  38.     
  39.     //顶部刷新时出现的控件    
  40.     private RelativeLayout mRefreshView;    
  41.     private TextView mRefreshViewText;    
  42.     private ImageView mRefreshViewImage;    
  43.     private ProgressBar mRefreshViewProgress;    
  44.     private TextView mRefreshViewLastUpdated;    
  45.     
  46.     // 当前滑动状态    
  47.     private int mCurrentScrollState;    
  48.     // 当前刷新状态    
  49.     private int mRefreshState;    
  50.         
  51.     // 箭头动画效果    
  52.     private RotateAnimation mFlipAnimation;    
  53.     private RotateAnimation mReverseFlipAnimation;    
  54.     
  55.     private int mRefreshViewHeight;    
  56.     private int mRefreshOriginalTopPadding;    
  57.     private int mLastMotionY;    
  58.     
  59.     private boolean mBounceHack;    
  60.     
  61.     public PullToRefreshListView(Context context) {    
  62.         super(context);    
  63.         init(context);    
  64.     }    
  65.     
  66.     public PullToRefreshListView(Context context, AttributeSet attrs) {    
  67.         super(context, attrs);    
  68.         init(context);    
  69.     }    
  70.     
  71.     public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {    
  72.         super(context, attrs, defStyle);    
  73.         init(context);    
  74.     }    
  75.     
  76.     /**  
  77.      * 初始化控件和箭头动画(这里直接在代码中初始化动画而不是通过xml)  
  78.      */    
  79.     private void init(Context context) {    
  80.         mFlipAnimation = new RotateAnimation(0, -180,    
  81.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,    
  82.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);    
  83.         mFlipAnimation.setInterpolator(new LinearInterpolator());    
  84.         mFlipAnimation.setDuration(250);    
  85.         mFlipAnimation.setFillAfter(true);    
  86.         mReverseFlipAnimation = new RotateAnimation(-1800,    
  87.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,    
  88.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);    
  89.         mReverseFlipAnimation.setInterpolator(new LinearInterpolator());    
  90.         mReverseFlipAnimation.setDuration(250);    
  91.         mReverseFlipAnimation.setFillAfter(true);    
  92.     
  93.         mInflater = (LayoutInflater) context.getSystemService(    
  94.                 Context.LAYOUT_INFLATER_SERVICE);    
  95.     
  96.         mRefreshView = (RelativeLayout) mInflater.inflate(    
  97.                 R.layout.pull_to_refresh_header, thisfalse);    
  98.         mRefreshViewText =    
  99.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);    
  100.         mRefreshViewImage =    
  101.             (ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);    
  102.         mRefreshViewProgress =    
  103.             (ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);    
  104.         mRefreshViewLastUpdated =    
  105.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);    
  106.     
  107.         mRefreshViewImage.setMinimumHeight(50);    
  108.         mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();    
  109.     
  110.         mRefreshState = TAP_TO_REFRESH;    
  111.             
  112.         //为listview头部增加一个view    
  113.         addHeaderView(mRefreshView);    
  114.     
  115.         super.setOnScrollListener(this);    
  116.     
  117.         measureView(mRefreshView);    
  118.         mRefreshViewHeight = mRefreshView.getMeasuredHeight();    
  119.     }    
  120.     
  121.     @Override    
  122.     protected void onAttachedToWindow() {    
  123.         setSelection(1);    
  124.     }    
  125.     
  126.     @Override    
  127.     public void setAdapter(ListAdapter adapter) {    
  128.         super.setAdapter(adapter);    
  129.     
  130.         setSelection(1);    
  131.     }    
  132.     
  133.     /**  
  134.      * 设置滑动监听器  
  135.      *   
  136.      */    
  137.     @Override    
  138.     public void setOnScrollListener(AbsListView.OnScrollListener l) {    
  139.         mOnScrollListener = l;    
  140.     }    
  141.     
  142.     /**  
  143.      * 注册一个list需要刷新时的回调接口  
  144.      *   
  145.      */    
  146.     public void setOnRefreshListener(OnRefreshListener onRefreshListener) {    
  147.         mOnRefreshListener = onRefreshListener;    
  148.     }    
  149.     
  150.     /**  
  151.      * 设置标签显示何时最后被刷新  
  152.      *   
  153.      * @param lastUpdated  
  154.      *            Last updated at.  
  155.      */    
  156.     public void setLastUpdated(CharSequence lastUpdated) {    
  157.         if (lastUpdated != null) {    
  158.             mRefreshViewLastUpdated.setVisibility(View.VISIBLE);    
  159.             mRefreshViewLastUpdated.setText(lastUpdated);    
  160.         } else {    
  161.             mRefreshViewLastUpdated.setVisibility(View.GONE);    
  162.         }    
  163.     }    
  164.     
  165.     // 实现该方法处理触摸    
  166.     @Override    
  167.     public boolean onTouchEvent(MotionEvent event) {    
  168.         final int y = (int) event.getY();    
  169.         mBounceHack = false;    
  170.     
  171.         switch (event.getAction()) {    
  172.     
  173.             case MotionEvent.ACTION_UP:    
  174.                 if (!isVerticalScrollBarEnabled()) {    
  175.                     setVerticalScrollBarEnabled(true);    
  176.                 }    
  177.                 if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {    
  178.                 // 拖动距离达到刷新需要    
  179.                     if ((mRefreshView.getBottom() >= mRefreshViewHeight    
  180.                             || mRefreshView.getTop() >= 0)    
  181.                             && mRefreshState == RELEASE_TO_REFRESH) {    
  182.                     // 把状态设置为正在刷新    
  183.                         mRefreshState = REFRESHING;    
  184.                     // 准备刷新    
  185.                         prepareForRefresh();    
  186.                     // 刷新    
  187.                         onRefresh();    
  188.                     } else if (mRefreshView.getBottom() < mRefreshViewHeight    
  189.                             || mRefreshView.getTop() <= 0) {    
  190.                     // 中止刷新    
  191.                         resetHeader();    
  192.                         setSelection(1);    
  193.                     }    
  194.                 }    
  195.                 break;    
  196.             case MotionEvent.ACTION_DOWN:    
  197.             // 获得按下y轴位置    
  198.                 mLastMotionY = y;    
  199.                 break;    
  200.             case MotionEvent.ACTION_MOVE:    
  201.             // 计算边距    
  202.                 applyHeaderPadding(event);    
  203.                 break;    
  204.         }    
  205.         return super.onTouchEvent(event);    
  206.     }    
  207.     
  208.     // 获得header的边距    
  209.     private void applyHeaderPadding(MotionEvent ev) {    
  210.     
  211.         int pointerCount = ev.getHistorySize();    
  212.     
  213.         for (int p = 0; p < pointerCount; p++) {    
  214.             if (mRefreshState == RELEASE_TO_REFRESH) {    
  215.                 if (isVerticalFadingEdgeEnabled()) {    
  216.                     setVerticalScrollBarEnabled(false);    
  217.                 }    
  218.     
  219.                 int historicalY = (int) ev.getHistoricalY(p);    
  220.     
  221.                 // 计算申请的边距,除以1.7使得拉动效果更好    
  222.                 int topPadding = (int) (((historicalY - mLastMotionY)    
  223.                         - mRefreshViewHeight) / 1.7);    
  224.     
  225.                 mRefreshView.setPadding(    
  226.                         mRefreshView.getPaddingLeft(),    
  227.                         topPadding,    
  228.                         mRefreshView.getPaddingRight(),    
  229.                         mRefreshView.getPaddingBottom());    
  230.             }    
  231.         }    
  232.     }    
  233.     
  234.     /**  
  235.      * 将head的边距重置为初始的数值  
  236.      */    
  237.     private void resetHeaderPadding() {    
  238.         mRefreshView.setPadding(    
  239.                 mRefreshView.getPaddingLeft(),    
  240.                 mRefreshOriginalTopPadding,    
  241.                 mRefreshView.getPaddingRight(),    
  242.                 mRefreshView.getPaddingBottom());    
  243.     }    
  244.     
  245.     /**  
  246.      * 重置header为之前的状态  
  247.      */    
  248.     private void resetHeader() {    
  249.         if (mRefreshState != TAP_TO_REFRESH) {    
  250.             mRefreshState = TAP_TO_REFRESH;    
  251.     
  252.             resetHeaderPadding();    
  253.     
  254.             // 将刷新图标换成箭头    
  255.             mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);    
  256.             // 清除动画    
  257.             mRefreshViewImage.clearAnimation();    
  258.             // 隐藏图标和进度条    
  259.             mRefreshViewImage.setVisibility(View.GONE);    
  260.             mRefreshViewProgress.setVisibility(View.GONE);    
  261.         }    
  262.     }    
  263.     
  264.     // 估算headview的width和height    
  265.     private void measureView(View child) {    
  266.         ViewGroup.LayoutParams p = child.getLayoutParams();    
  267.         if (p == null) {    
  268.             p = new ViewGroup.LayoutParams(    
  269.                     ViewGroup.LayoutParams.FILL_PARENT,    
  270.                     ViewGroup.LayoutParams.WRAP_CONTENT);    
  271.         }    
  272.     
  273.         int childWidthSpec = ViewGroup.getChildMeasureSpec(0,    
  274.                 0 + 0, p.width);    
  275.         int lpHeight = p.height;    
  276.         int childHeightSpec;    
  277.         if (lpHeight > 0) {    
  278.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);    
  279.         } else {    
  280.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);    
  281.         }    
  282.         child.measure(childWidthSpec, childHeightSpec);    
  283.     }    
  284.     
  285.     @Override    
  286.     public void onScroll(AbsListView view, int firstVisibleItem,    
  287.             int visibleItemCount, int totalItemCount) {    
  288.     
  289.         // 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头    
  290.         if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL    
  291.                 && mRefreshState != REFRESHING) {    
  292.             if (firstVisibleItem == 0) {    
  293.                 mRefreshViewImage.setVisibility(View.VISIBLE);    
  294.                 if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20    
  295.                         || mRefreshView.getTop() >= 0)    
  296.                         && mRefreshState != RELEASE_TO_REFRESH) {    
  297.                     mRefreshViewText.setText("松开加载...");    
  298.                     mRefreshViewImage.clearAnimation();    
  299.                     mRefreshViewImage.startAnimation(mFlipAnimation);    
  300.                     mRefreshState = RELEASE_TO_REFRESH;    
  301.                 } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20    
  302.                         && mRefreshState != PULL_TO_REFRESH) {    
  303.                     mRefreshViewText.setText("下拉刷新...");    
  304.                     if (mRefreshState != TAP_TO_REFRESH) {    
  305.                         mRefreshViewImage.clearAnimation();    
  306.                         mRefreshViewImage.startAnimation(mReverseFlipAnimation);    
  307.                     }    
  308.                     mRefreshState = PULL_TO_REFRESH;    
  309.                 }    
  310.             } else {    
  311.                 mRefreshViewImage.setVisibility(View.GONE);    
  312.                 resetHeader();    
  313.             }    
  314.         } else if (mCurrentScrollState == SCROLL_STATE_FLING    
  315.                 && firstVisibleItem == 0    
  316.                 && mRefreshState != REFRESHING) {    
  317.             setSelection(1);    
  318.             mBounceHack = true;    
  319.         } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {    
  320.             setSelection(1);    
  321.         }    
  322.     
  323.         if (mOnScrollListener != null) {    
  324.             mOnScrollListener.onScroll(view, firstVisibleItem,    
  325.                     visibleItemCount, totalItemCount);    
  326.         }    
  327.     }    
  328.     
  329.     @Override    
  330.     public void onScrollStateChanged(AbsListView view, int scrollState) {    
  331.         mCurrentScrollState = scrollState;    
  332.     
  333.         if (mCurrentScrollState == SCROLL_STATE_IDLE) {    
  334.             mBounceHack = false;    
  335.         }    
  336.     
  337.         if (mOnScrollListener != null) {    
  338.             mOnScrollListener.onScrollStateChanged(view, scrollState);    
  339.         }    
  340.     }    
  341.     
  342.     public void prepareForRefresh() {    
  343.         resetHeaderPadding();// 恢复header的边距    
  344.     
  345.         mRefreshViewImage.setVisibility(View.GONE);    
  346.         // 注意加上,否则仍然显示之前的图片    
  347.         mRefreshViewImage.setImageDrawable(null);    
  348.         mRefreshViewProgress.setVisibility(View.VISIBLE);    
  349.     
  350.         // 设置文字    
  351.         mRefreshViewText.setText("加载中...");    
  352.     
  353.         mRefreshState = REFRESHING;    
  354.     }    
  355.     
  356.     public void onRefresh() {    
  357.     
  358.         if (mOnRefreshListener != null) {    
  359.             mOnRefreshListener.onRefresh();    
  360.         }    
  361.     }    
  362.     
  363.     /**  
  364.      * 重置listview为普通的listview,该方法设置最后更新时间  
  365.      *   
  366.      * @param lastUpdated  
  367.      *            Last updated at.  
  368.      */    
  369.     public void onRefreshComplete(CharSequence lastUpdated) {    
  370.         setLastUpdated(lastUpdated);    
  371.         onRefreshComplete();    
  372.     }    
  373.     
  374.     /**  
  375.      * 重置listview为普通的listview,不设置最后更新时间  
  376.      */    
  377.     public void onRefreshComplete() {            
  378.     
  379.         resetHeader();    
  380.     
  381.         // 如果refreshview在加载结束后可见,下滑到下一个条目    
  382.         if (mRefreshView.getBottom() > 0) {    
  383.             invalidateViews();    
  384.             setSelection(1);    
  385.         }    
  386.     }    
  387.     
  388.     
  389.     
  390.     /**  
  391.      * 刷新监听器接口  
  392.      */    
  393.     public interface OnRefreshListener {    
  394.         /**  
  395.          * list需要被刷新时调用  
  396.          */    
  397.         public void onRefresh();    
  398.     }    
  399. }    

 

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

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

  1. package com.notice.pullrefresh;    
  2.     
  3. import java.util.Arrays;    
  4. import java.util.LinkedList;    
  5.     
  6. import android.app.ListActivity;    
  7. import android.os.AsyncTask;    
  8. import android.os.Bundle;    
  9. import android.widget.ArrayAdapter;    
  10.     
  11. import com.notice.pullrefresh.PullToRefreshListView.OnRefreshListener;    
  12.     
  13.     
  14. public class PullrefreshActivity extends ListActivity {    
  15.     private LinkedList<String> mListItems;    
  16.     ArrayAdapter<String> adapter;    
  17.     
  18.     /** Called when the activity is first created. */    
  19.     @Override    
  20.     public void onCreate(Bundle savedInstanceState) {    
  21.         super.onCreate(savedInstanceState);    
  22.         setContentView(R.layout.pull_to_refresh);    
  23.     
  24.         // list需要刷新时调用    
  25.         ((PullToRefreshListView) getListView())    
  26.                 .setOnRefreshListener(new OnRefreshListener() {    
  27.                     @Override    
  28.                     public void onRefresh() {    
  29.                         // 在这执行后台工作    
  30.                         new GetDataTask().execute();    
  31.                     }    
  32.                 });    
  33.     
  34.     
  35.     
  36.         mListItems = new LinkedList<String>();    
  37.         mListItems.addAll(Arrays.asList(mStrings));    
  38.     
  39.         adapter = new ArrayAdapter<String>(this,    
  40.                 android.R.layout.simple_list_item_1, mListItems);    
  41.     
  42.         setListAdapter(adapter);    
  43.     }    
  44.     
  45.     
  46.     private class GetDataTask extends AsyncTask<Void, Void, String[]> {    
  47.     
  48.         @Override    
  49.         protected String[] doInBackground(Void... params) {    
  50.             // 在这里可以做一些后台工作    
  51.             try {    
  52.                 Thread.sleep(2000);    
  53.             } catch (InterruptedException e) {    
  54.                 e.printStackTrace();    
  55.             }    
  56.             return mStrings;    
  57.         }    
  58.     
  59.         @Override    
  60.         protected void onPostExecute(String[] result) {    
  61.             // 下拉后增加的内容    
  62.             mListItems.addFirst("Added after refresh...");    
  63.     
  64.             // 刷新完成调用该方法复位    
  65.             ((PullToRefreshListView) getListView()).onRefreshComplete();    
  66.     
  67.             super.onPostExecute(result);    
  68.         }    
  69.     }    
  70.     
  71.     private String[] mStrings = { "normal data1""normal data2",    
  72.             "nomal data3""normal data4""norma data5""normal data6" };    
  73. }    

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

今天就和大家分享这些,有问题欢迎留言交流。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值