Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能 (转)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9255575

最 近项目中需要用到ListView下拉刷新的功能,一开始想图省事,在网上直接找一个现成的,可是尝试了网上多个版本的下拉刷新之后发现效果都不怎么理 想。有些是因为功能不完整或有Bug,有些是因为使用起来太复杂,十全十美的还真没找到。因此我也是放弃了在网上找现成代码的想法,自己花功夫编写了一种 非常简单的下拉刷新实现方案,现在拿出来和大家分享一下。相信在阅读完本篇文章之后,大家都可以在自己的项目中一分钟引入下拉刷新功能。

首 先讲一下实现原理。这里我们将采取的方案是使用组合View的方式,先自定义一个布局继承自LinearLayout,然后在这个布局中加入下拉头和 ListView这两个子元素,并让这两个子元素纵向排列。初始化的时候,让下拉头向上偏移出屏幕,这样我们看到的就只有ListView了。然后对 ListView的touch事件进行监听,如果当前ListView已经滚动到顶部并且手指还在向下拉的话,那就将下拉头显示出来,松手后进行刷新操 作,并将下拉头隐藏。原理示意图如下:

                            

那我们现在就来动手实现一下,新建一个项目起名叫PullToRefreshTest,先在项目中定义一个下拉头的布局文件pull_to_refresh.xml,代码如下所示:

[html] view plaincopy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/pull_to_refresh_head"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="60dip" >  
  6.   
  7.     <LinearLayout  
  8.         android:layout_width="200dip"  
  9.         android:layout_height="60dip"  
  10.         android:layout_centerInParent="true"  
  11.         android:orientation="horizontal" >  
  12.   
  13.         <RelativeLayout  
  14.             android:layout_width="0dip"  
  15.             android:layout_height="60dip"  
  16.             android:layout_weight="3"  
  17.             >  
  18.             <ImageView   
  19.                 android:id="@+id/arrow"  
  20.                 android:layout_width="wrap_content"  
  21.                 android:layout_height="wrap_content"  
  22.                 android:layout_centerInParent="true"  
  23.                 android:src="@drawable/arrow"  
  24.                 />  
  25.             <ProgressBar   
  26.                 android:id="@+id/progress_bar"  
  27.                 android:layout_width="30dip"  
  28.                 android:layout_height="30dip"  
  29.                 android:layout_centerInParent="true"  
  30.                 android:visibility="gone"  
  31.                 />  
  32.         </RelativeLayout>  
  33.   
  34.         <LinearLayout  
  35.             android:layout_width="0dip"  
  36.             android:layout_height="60dip"  
  37.             android:layout_weight="12"  
  38.             android:orientation="vertical" >  
  39.   
  40.             <TextView  
  41.                 android:id="@+id/description"  
  42.                 android:layout_width="fill_parent"  
  43.                 android:layout_height="0dip"  
  44.                 android:layout_weight="1"  
  45.                 android:gravity="center_horizontal|bottom"  
  46.                 android:text="@string/pull_to_refresh" />  
  47.   
  48.             <TextView  
  49.                 android:id="@+id/updated_at"  
  50.                 android:layout_width="fill_parent"  
  51.                 android:layout_height="0dip"  
  52.                 android:layout_weight="1"  
  53.                 android:gravity="center_horizontal|top"  
  54.                 android:text="@string/updated_at" />  
  55.         </LinearLayout>  
  56.     </LinearLayout>  
  57.   
  58. </RelativeLayout>  

在这个布局中,我们包含了一个下拉指示箭头,一个下拉状态文字提示,和一个上次更新的时间。当然,还有一个隐藏的旋转进度条,只有正在刷新的时候我们才会将它显示出来。
布局中所有引用的字符串我们都放在strings.xml中,如下所示:

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.   
  4.     <string name="app_name">PullToRefreshTest</string>  
  5.     <string name="pull_to_refresh">下拉可以刷新</string>  
  6.     <string name="release_to_refresh">释放立即刷新</string>  
  7.     <string name="refreshing">正在刷新…</string>  
  8.     <string name="not_updated_yet">暂未更新过</string>  
  9.     <string name="updated_at">上次更新于%1$s前</string>  
  10.     <string name="updated_just_now">刚刚更新</string>  
  11.     <string name="time_error">时间有问题</string>  
  12.       
  13. </resources>  

然后新建一个RefreshableView继承自LinearLayout,代码如下所示:

[java] view plaincopy
  1. public class RefreshableView extends LinearLayout implements OnTouchListener {  
  2.   
  3.     /** 
  4.      * 下拉状态 
  5.      */  
  6.     public static final int STATUS_PULL_TO_REFRESH = 0;  
  7.   
  8.     /** 
  9.      * 释放立即刷新状态 
  10.      */  
  11.     public static final int STATUS_RELEASE_TO_REFRESH = 1;  
  12.   
  13.     /** 
  14.      * 正在刷新状态 
  15.      */  
  16.     public static final int STATUS_REFRESHING = 2;  
  17.   
  18.     /** 
  19.      * 刷新完成或未刷新状态 
  20.      */  
  21.     public static final int STATUS_REFRESH_FINISHED = 3;  
  22.   
  23.     /** 
  24.      * 下拉头部回滚的速度 
  25.      */  
  26.     public static final int SCROLL_SPEED = -20;  
  27.   
  28.     /** 
  29.      * 一分钟的毫秒值,用于判断上次的更新时间 
  30.      */  
  31.     public static final long ONE_MINUTE = 60 * 1000;  
  32.   
  33.     /** 
  34.      * 一小时的毫秒值,用于判断上次的更新时间 
  35.      */  
  36.     public static final long ONE_HOUR = 60 * ONE_MINUTE;  
  37.   
  38.     /** 
  39.      * 一天的毫秒值,用于判断上次的更新时间 
  40.      */  
  41.     public static final long ONE_DAY = 24 * ONE_HOUR;  
  42.   
  43.     /** 
  44.      * 一月的毫秒值,用于判断上次的更新时间 
  45.      */  
  46.     public static final long ONE_MONTH = 30 * ONE_DAY;  
  47.   
  48.     /** 
  49.      * 一年的毫秒值,用于判断上次的更新时间 
  50.      */  
  51.     public static final long ONE_YEAR = 12 * ONE_MONTH;  
  52.   
  53.     /** 
  54.      * 上次更新时间的字符串常量,用于作为SharedPreferences的键值 
  55.      */  
  56.     private static final String UPDATED_AT = "updated_at";  
  57.   
  58.     /** 
  59.      * 下拉刷新的回调接口 
  60.      */  
  61.     private PullToRefreshListener mListener;  
  62.   
  63.     /** 
  64.      * 用于存储上次更新时间 
  65.      */  
  66.     private SharedPreferences preferences;  
  67.   
  68.     /** 
  69.      * 下拉头的View 
  70.      */  
  71.     private View header;  
  72.   
  73.     /** 
  74.      * 需要去下拉刷新的ListView 
  75.      */  
  76.     private ListView listView;  
  77.   
  78.     /** 
  79.      * 刷新时显示的进度条 
  80.      */  
  81.     private ProgressBar progressBar;  
  82.   
  83.     /** 
  84.      * 指示下拉和释放的箭头 
  85.      */  
  86.     private ImageView arrow;  
  87.   
  88.     /** 
  89.      * 指示下拉和释放的文字描述 
  90.      */  
  91.     private TextView description;  
  92.   
  93.     /** 
  94.      * 上次更新时间的文字描述 
  95.      */  
  96.     private TextView updateAt;  
  97.   
  98.     /** 
  99.      * 下拉头的布局参数 
  100.      */  
  101.     private MarginLayoutParams headerLayoutParams;  
  102.   
  103.     /** 
  104.      * 上次更新时间的毫秒值 
  105.      */  
  106.     private long lastUpdateTime;  
  107.   
  108.     /** 
  109.      * 为了防止不同界面的下拉刷新在上次更新时间上互相有冲突,使用id来做区分 
  110.      */  
  111.     private int mId = -1;  
  112.   
  113.     /** 
  114.      * 下拉头的高度 
  115.      */  
  116.     private int hideHeaderHeight;  
  117.   
  118.     /** 
  119.      * 当前处理什么状态,可选值有STATUS_PULL_TO_REFRESH, STATUS_RELEASE_TO_REFRESH, 
  120.      * STATUS_REFRESHING 和 STATUS_REFRESH_FINISHED 
  121.      */  
  122.     private int currentStatus = STATUS_REFRESH_FINISHED;;  
  123.   
  124.     /** 
  125.      * 记录上一次的状态是什么,避免进行重复操作 
  126.      */  
  127.     private int lastStatus = currentStatus;  
  128.   
  129.     /** 
  130.      * 手指按下时的屏幕纵坐标 
  131.      */  
  132.     private float yDown;  
  133.   
  134.     /** 
  135.      * 在被判定为滚动之前用户手指可以移动的最大值。 
  136.      */  
  137.     private int touchSlop;  
  138.   
  139.     /** 
  140.      * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次 
  141.      */  
  142.     private boolean loadOnce;  
  143.   
  144.     /** 
  145.      * 当前是否可以下拉,只有ListView滚动到头的时候才允许下拉 
  146.      */  
  147.     private boolean ableToPull;  
  148.   
  149.     /** 
  150.      * 下拉刷新控件的构造函数,会在运行时动态添加一个下拉头的布局。 
  151.      *  
  152.      * @param context 
  153.      * @param attrs 
  154.      */  
  155.     public RefreshableView(Context context, AttributeSet attrs) {  
  156.         super(context, attrs);  
  157.         preferences = PreferenceManager.getDefaultSharedPreferences(context);  
  158.         header = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh, nulltrue);  
  159.         progressBar = (ProgressBar) header.findViewById(R.id.progress_bar);  
  160.         arrow = (ImageView) header.findViewById(R.id.arrow);  
  161.         description = (TextView) header.findViewById(R.id.description);  
  162.         updateAt = (TextView) header.findViewById(R.id.updated_at);  
  163.         touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  
  164.         refreshUpdatedAtValue();  
  165.         setOrientation(VERTICAL);  
  166.         addView(header, 0);  
  167.     }  
  168.   
  169.     /** 
  170.      * 进行一些关键性的初始化操作,比如:将下拉头向上偏移进行隐藏,给ListView注册touch事件。 
  171.      */  
  172.     @Override  
  173.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  174.         super.onLayout(changed, l, t, r, b);  
  175.         if (changed && !loadOnce) {  
  176.             hideHeaderHeight = -header.getHeight();  
  177.             headerLayoutParams = (MarginLayoutParams) header.getLayoutParams();  
  178.             headerLayoutParams.topMargin = hideHeaderHeight;  
  179.             listView = (ListView) getChildAt(1);  
  180.             listView.setOnTouchListener(this);  
  181.             loadOnce = true;  
  182.         }  
  183.     }  
  184.   
  185.     /** 
  186.      * 当ListView被触摸时调用,其中处理了各种下拉刷新的具体逻辑。 
  187.      */  
  188.     @Override  
  189.     public boolean onTouch(View v, MotionEvent event) {  
  190.         setIsAbleToPull(event);  
  191.         if (ableToPull) {  
  192.             switch (event.getAction()) {  
  193.             case MotionEvent.ACTION_DOWN:  
  194.                 yDown = event.getRawY();  
  195.                 break;  
  196.             case MotionEvent.ACTION_MOVE:  
  197.                 float yMove = event.getRawY();  
  198.                 int distance = (int) (yMove - yDown);  
  199.                 // 如果手指是下滑状态,并且下拉头是完全隐藏的,就屏蔽下拉事件  
  200.                 if (distance <= 0 && headerLayoutParams.topMargin <= hideHeaderHeight) {  
  201.                     return false;  
  202.                 }  
  203.                 if (distance < touchSlop) {  
  204.                     return false;  
  205.                 }  
  206.                 if (currentStatus != STATUS_REFRESHING) {  
  207.                     if (headerLayoutParams.topMargin > 0) {  
  208.                         currentStatus = STATUS_RELEASE_TO_REFRESH;  
  209.                     } else {  
  210.                         currentStatus = STATUS_PULL_TO_REFRESH;  
  211.                     }  
  212.                     // 通过偏移下拉头的topMargin值,来实现下拉效果  
  213.                     headerLayoutParams.topMargin = (distance / 2) + hideHeaderHeight;  
  214.                     header.setLayoutParams(headerLayoutParams);  
  215.                 }  
  216.                 break;  
  217.             case MotionEvent.ACTION_UP:  
  218.             default:  
  219.                 if (currentStatus == STATUS_RELEASE_TO_REFRESH) {  
  220.                     // 松手时如果是释放立即刷新状态,就去调用正在刷新的任务  
  221.                     new RefreshingTask().execute();  
  222.                 } else if (currentStatus == STATUS_PULL_TO_REFRESH) {  
  223.                     // 松手时如果是下拉状态,就去调用隐藏下拉头的任务  
  224.                     new HideHeaderTask().execute();  
  225.                 }  
  226.                 break;  
  227.             }  
  228.             // 时刻记得更新下拉头中的信息  
  229.             if (currentStatus == STATUS_PULL_TO_REFRESH  
  230.                     || currentStatus == STATUS_RELEASE_TO_REFRESH) {  
  231.                 updateHeaderView();  
  232.                 // 当前正处于下拉或释放状态,要让ListView失去焦点,否则被点击的那一项会一直处于选中状态  
  233.                 listView.setPressed(false);  
  234.                 listView.setFocusable(false);  
  235.                 listView.setFocusableInTouchMode(false);  
  236.                 lastStatus = currentStatus;  
  237.                 // 当前正处于下拉或释放状态,通过返回true屏蔽掉ListView的滚动事件  
  238.                 return true;  
  239.             }  
  240.         }  
  241.         return false;  
  242.     }  
  243.   
  244.     /** 
  245.      * 给下拉刷新控件注册一个监听器。 
  246.      *  
  247.      * @param listener 
  248.      *            监听器的实现。 
  249.      * @param id 
  250.      *            为了防止不同界面的下拉刷新在上次更新时间上互相有冲突, 请不同界面在注册下拉刷新监听器时一定要传入不同的id。 
  251.      */  
  252.     public void setOnRefreshListener(PullToRefreshListener listener, int id) {  
  253.         mListener = listener;  
  254.         mId = id;  
  255.     }  
  256.   
  257.     /** 
  258.      * 当所有的刷新逻辑完成后,记录调用一下,否则你的ListView将一直处于正在刷新状态。 
  259.      */  
  260.     public void finishRefreshing() {  
  261.         currentStatus = STATUS_REFRESH_FINISHED;  
  262.         preferences.edit().putLong(UPDATED_AT + mId, System.currentTimeMillis()).commit();  
  263.         new HideHeaderTask().execute();  
  264.     }  
  265.   
  266.     /** 
  267.      * 根据当前ListView的滚动状态来设定 {@link #ableToPull} 
  268.      * 的值,每次都需要在onTouch中第一个执行,这样可以判断出当前应该是滚动ListView,还是应该进行下拉。 
  269.      *  
  270.      * @param event 
  271.      */  
  272.     private void setIsAbleToPull(MotionEvent event) {  
  273.         View firstChild = listView.getChildAt(0);  
  274.         if (firstChild != null) {  
  275.             int firstVisiblePos = listView.getFirstVisiblePosition();  
  276.             if (firstVisiblePos == 0 && firstChild.getTop() == 0) {  
  277.                 if (!ableToPull) {  
  278.                     yDown = event.getRawY();  
  279.                 }  
  280.                 // 如果首个元素的上边缘,距离父布局值为0,就说明ListView滚动到了最顶部,此时应该允许下拉刷新  
  281.                 ableToPull = true;  
  282.             } else {  
  283.                 if (headerLayoutParams.topMargin != hideHeaderHeight) {  
  284.                     headerLayoutParams.topMargin = hideHeaderHeight;  
  285.                     header.setLayoutParams(headerLayoutParams);  
  286.                 }  
  287.                 ableToPull = false;  
  288.             }  
  289.         } else {  
  290.             // 如果ListView中没有元素,也应该允许下拉刷新  
  291.             ableToPull = true;  
  292.         }  
  293.     }  
  294.   
  295.     /** 
  296.      * 更新下拉头中的信息。 
  297.      */  
  298.     private void updateHeaderView() {  
  299.         if (lastStatus != currentStatus) {  
  300.             if (currentStatus == STATUS_PULL_TO_REFRESH) {  
  301.                 description.setText(getResources().getString(R.string.pull_to_refresh));  
  302.                 arrow.setVisibility(View.VISIBLE);  
  303.                 progressBar.setVisibility(View.GONE);  
  304.                 rotateArrow();  
  305.             } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {  
  306.                 description.setText(getResources().getString(R.string.release_to_refresh));  
  307.                 arrow.setVisibility(View.VISIBLE);  
  308.                 progressBar.setVisibility(View.GONE);  
  309.                 rotateArrow();  
  310.             } else if (currentStatus == STATUS_REFRESHING) {  
  311.                 description.setText(getResources().getString(R.string.refreshing));  
  312.                 progressBar.setVisibility(View.VISIBLE);  
  313.                 arrow.clearAnimation();  
  314.                 arrow.setVisibility(View.GONE);  
  315.             }  
  316.             refreshUpdatedAtValue();  
  317.         }  
  318.     }  
  319.   
  320.     /** 
  321.      * 根据当前的状态来旋转箭头。 
  322.      */  
  323.     private void rotateArrow() {  
  324.         float pivotX = arrow.getWidth() / 2f;  
  325.         float pivotY = arrow.getHeight() / 2f;  
  326.         float fromDegrees = 0f;  
  327.         float toDegrees = 0f;  
  328.         if (currentStatus == STATUS_PULL_TO_REFRESH) {  
  329.             fromDegrees = 180f;  
  330.             toDegrees = 360f;  
  331.         } else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {  
  332.             fromDegrees = 0f;  
  333.             toDegrees = 180f;  
  334.         }  
  335.         RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);  
  336.         animation.setDuration(100);  
  337.         animation.setFillAfter(true);  
  338.         arrow.startAnimation(animation);  
  339.     }  
  340.   
  341.     /** 
  342.      * 刷新下拉头中上次更新时间的文字描述。 
  343.      */  
  344.     private void refreshUpdatedAtValue() {  
  345.         lastUpdateTime = preferences.getLong(UPDATED_AT + mId, -1);  
  346.         long currentTime = System.currentTimeMillis();  
  347.         long timePassed = currentTime - lastUpdateTime;  
  348.         long timeIntoFormat;  
  349.         String updateAtValue;  
  350.         if (lastUpdateTime == -1) {  
  351.             updateAtValue = getResources().getString(R.string.not_updated_yet);  
  352.         } else if (timePassed < 0) {  
  353.             updateAtValue = getResources().getString(R.string.time_error);  
  354.         } else if (timePassed < ONE_MINUTE) {  
  355.             updateAtValue = getResources().getString(R.string.updated_just_now);  
  356.         } else if (timePassed < ONE_HOUR) {  
  357.             timeIntoFormat = timePassed / ONE_MINUTE;  
  358.             String value = timeIntoFormat + "分钟";  
  359.             updateAtValue = String.format(getResources().getString(R.string.updated_at), value);  
  360.         } else if (timePassed < ONE_DAY) {  
  361.             timeIntoFormat = timePassed / ONE_HOUR;  
  362.             String value = timeIntoFormat + "小时";  
  363.             updateAtValue = String.format(getResources().getString(R.string.updated_at), value);  
  364.         } else if (timePassed < ONE_MONTH) {  
  365.             timeIntoFormat = timePassed / ONE_DAY;  
  366.             String value = timeIntoFormat + "天";  
  367.             updateAtValue = String.format(getResources().getString(R.string.updated_at), value);  
  368.         } else if (timePassed < ONE_YEAR) {  
  369.             timeIntoFormat = timePassed / ONE_MONTH;  
  370.             String value = timeIntoFormat + "个月";  
  371.             updateAtValue = String.format(getResources().getString(R.string.updated_at), value);  
  372.         } else {  
  373.             timeIntoFormat = timePassed / ONE_YEAR;  
  374.             String value = timeIntoFormat + "年";  
  375.             updateAtValue = String.format(getResources().getString(R.string.updated_at), value);  
  376.         }  
  377.         updateAt.setText(updateAtValue);  
  378.     }  
  379.   
  380.     /** 
  381.      * 正在刷新的任务,在此任务中会去回调注册进来的下拉刷新监听器。 
  382.      *  
  383.      * @author guolin 
  384.      */  
  385.     class RefreshingTask extends AsyncTask<Void, Integer, Void> {  
  386.   
  387.         @Override  
  388.         protected Void doInBackground(Void... params) {  
  389.             int topMargin = headerLayoutParams.topMargin;  
  390.             while (true) {  
  391.                 topMargin = topMargin + SCROLL_SPEED;  
  392.                 if (topMargin <= 0) {  
  393.                     topMargin = 0;  
  394.                     break;  
  395.                 }  
  396.                 publishProgress(topMargin);  
  397.                 sleep(10);  
  398.             }  
  399.             currentStatus = STATUS_REFRESHING;  
  400.             publishProgress(0);  
  401.             if (mListener != null) {  
  402.                 mListener.onRefresh();  
  403.             }  
  404.             return null;  
  405.         }  
  406.   
  407.         @Override  
  408.         protected void onProgressUpdate(Integer... topMargin) {  
  409.             updateHeaderView();  
  410.             headerLayoutParams.topMargin = topMargin[0];  
  411.             header.setLayoutParams(headerLayoutParams);  
  412.         }  
  413.   
  414.     }  
  415.   
  416.     /** 
  417.      * 隐藏下拉头的任务,当未进行下拉刷新或下拉刷新完成后,此任务将会使下拉头重新隐藏。 
  418.      *  
  419.      * @author guolin 
  420.      */  
  421.     class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {  
  422.   
  423.         @Override  
  424.         protected Integer doInBackground(Void... params) {  
  425.             int topMargin = headerLayoutParams.topMargin;  
  426.             while (true) {  
  427.                 topMargin = topMargin + SCROLL_SPEED;  
  428.                 if (topMargin <= hideHeaderHeight) {  
  429.                     topMargin = hideHeaderHeight;  
  430.                     break;  
  431.                 }  
  432.                 publishProgress(topMargin);  
  433.                 sleep(10);  
  434.             }  
  435.             return topMargin;  
  436.         }  
  437.   
  438.         @Override  
  439.         protected void onProgressUpdate(Integer... topMargin) {  
  440.             headerLayoutParams.topMargin = topMargin[0];  
  441.             header.setLayoutParams(headerLayoutParams);  
  442.         }  
  443.   
  444.         @Override  
  445.         protected void onPostExecute(Integer topMargin) {  
  446.             headerLayoutParams.topMargin = topMargin;  
  447.             header.setLayoutParams(headerLayoutParams);  
  448.             currentStatus = STATUS_REFRESH_FINISHED;  
  449.         }  
  450.     }  
  451.   
  452.     /** 
  453.      * 使当前线程睡眠指定的毫秒数。 
  454.      *  
  455.      * @param time 
  456.      *            指定当前线程睡眠多久,以毫秒为单位 
  457.      */  
  458.     private void sleep(int time) {  
  459.         try {  
  460.             Thread.sleep(time);  
  461.         } catch (InterruptedException e) {  
  462.             e.printStackTrace();  
  463.         }  
  464.     }  
  465.   
  466.     /** 
  467.      * 下拉刷新的监听器,使用下拉刷新的地方应该注册此监听器来获取刷新回调。 
  468.      *  
  469.      * @author guolin 
  470.      */  
  471.     public interface PullToRefreshListener {  
  472.   
  473.         /** 
  474.          * 刷新时会去回调此方法,在方法内编写具体的刷新逻辑。注意此方法是在子线程中调用的, 你可以不必另开线程来进行耗时操作。 
  475.          */  
  476.         void onRefresh();  
  477.   
  478.     }  
  479.   
  480. }  

这 个类是整个下拉刷新功能中最重要的一个类,注释已经写得比较详细了,我再简单解释一下。首先在RefreshableView的构造函数中动态添加了刚刚 定义的pull_to_refresh这个布局作为下拉头,然后在onLayout方法中将下拉头向上偏移出了屏幕,再给ListView注册了 touch事件。之后每当手指在ListView上滑动时,onTouch方法就会执行。在onTouch方法中的第一行就调用了 setIsAbleToPull方法来判断ListView是否滚动到了最顶部,只有滚动到了最顶部才会执行后面的代码,否则就视为正常的 ListView滚动,不做任何处理。当ListView滚动到了最顶部时,如果手指还在向下拖动,就会改变下拉头的偏移值,让下拉头显示出来,下拉的距 离设定为手指移动距离的1/2,这样才会有拉力的感觉。如果下拉的距离足够大,在松手的时候就会执行刷新操作,如果距离不够大,就仅仅重新隐藏下拉头。

具体的刷新操作会在RefreshingTask中进行,其中在doInBackground方法中回调了PullToRefreshListener接口的onRefresh方法,这也是大家在使用RefreshableView时必须要去实现的一个接口,因为具体刷新的逻辑就应该写在onRefresh方法中,后面会演示使用的方法。

另外每次在下拉的时候都还会调用updateHeaderView方法来改变下拉头中的数据,比如箭头方向的旋转,下拉文字描述的改变等。更加深入的理解请大家仔细去阅读RefreshableView中的代码。
现在我们已经把下拉刷新的所有功能都完成了,接下来就要看一看如何在项目中引入下拉刷新了。打开或新建activity_main.xml作为程序主界面的布局,加入如下代码:

[html] view plaincopy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".MainActivity" >  
  6.   
  7.     <com.example.pulltorefreshtest.RefreshableView  
  8.         android:id="@+id/refreshable_view"  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="fill_parent" >  
  11.   
  12.         <ListView  
  13.             android:id="@+id/list_view"  
  14.             android:layout_width="fill_parent"  
  15.             android:layout_height="fill_parent" >  
  16.         </ListView>  
  17.     </com.example.pulltorefreshtest.RefreshableView>  
  18.   
  19. </RelativeLayout>  

可以看到,我们在自定义的RefreshableView中加入了一个ListView,这就意味着给这个ListView加入了下拉刷新的功能,就是这么简单!
然后我们再来看一下程序的主Activity,打开或新建MainActivity,加入如下代码:

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.   
  3.     RefreshableView refreshableView;  
  4.     ListView listView;  
  5.     ArrayAdapter<String> adapter;  
  6.     String[] items = { "A""B""C""D""E""F""G""H""I""J""K""L" };  
  7.   
  8.     @Override  
  9.     protected void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
  12.         setContentView(R.layout.activity_main);  
  13.         refreshableView = (RefreshableView) findViewById(R.id.refreshable_view);  
  14.         listView = (ListView) findViewById(R.id.list_view);  
  15.         adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);  
  16.         listView.setAdapter(adapter);  
  17.         refreshableView.setOnRefreshListener(new PullToRefreshListener() {  
  18.             @Override  
  19.             public void onRefresh() {  
  20.                 try {  
  21.                     Thread.sleep(3000);  
  22.                 } catch (InterruptedException e) {  
  23.                     e.printStackTrace();  
  24.                 }  
  25.                 refreshableView.finishRefreshing();  
  26.             }  
  27.         }, 0);  
  28.     }  
  29.   
  30. }  

 

可 以看到,我们通过调用RefreshableView的setOnRefreshListener方法注册了一个监听器,当ListView正在刷新时就 会回调监听器的onRefresh方法,刷新的具体逻辑就在这里处理。而且这个方法已经自动开启了线程,可以直接在onRefresh方法中进行耗时操 作,比如向服务器请求最新数据等,在这里我就简单让线程睡眠3秒钟。另外在onRefresh方法的最后,一定要调用RefreshableView中的 finishRefreshing方法,这个方法是用来通知RefreshableView刷新结束了,不然我们的ListView将一直处于正在刷新的 状态。
不知道大家有没有注意到,setOnRefreshListener这个方法其实是有两个参数的,我们刚刚也是传入了一个不起眼的0。那这 第二个参数是用来做什么的呢?由于RefreshableView比较智能,它会自动帮我们记录上次刷新完成的时间,然后下拉的时候会在下拉头中显示距上 次刷新已过了多久。这是一个非常好用的功能,让我们不用再自己手动去记录和计算时间了,但是却存在一个问题。如果当前我们的项目中有三个地方都使用到了下 拉刷新的功能,现在在一处进行了刷新,其它两处的时间也都会跟着改变!因为刷新完成的时间是记录在配置文件中的,由于在一处刷新更改了配置文件,导致在其 它两处读取到的配置文件时间已经是更改过的了。那解决方案是什么?就是每个用到下拉刷新的地方,给setOnRefreshListener方法的第二个 参数中传入不同的id就行了。这样各处的上次刷新完成时间都是单独记录的,相互之间就不会再有影响。
好了,全部的代码都在这里了,让我们来运行一下,看看效果吧。

                                                 
效果看起来还是非常不错的。我们最后再来总结一下,在项目中引入ListView下拉刷新功能只需三步:

1. 在Activity的布局文件中加入自定义的RefreshableView,并让ListView包含在其中。

2. 在Activity中调用RefreshableView的setOnRefreshListener方法注册回调接口。

3. 在onRefresh方法的最后,记得调用RefreshableView的finishRefreshing方法,通知刷新结束。

从此以后,在项目的任何地方,一分钟引入下拉刷新功能妥妥的。

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里

原文摘自:http://blog.csdn.net/guolin_blog/article/details/9255575

转载于:https://www.cnblogs.com/YangBinChina/p/3555877.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

红黑树的实现

目录1、红黑树原理1、红黑树性质2、变换规则&#xff08;从插入结点的角度来讲&#xff09;1.变色2.左旋3.右旋3、删除结点需要注意的地方2、代码1、定义结点以及构造函数2、定义红黑树类以及声明它的方法3、左旋4、右旋5、插入操作6、修正操作7、删除操作3、参考链接1、红黑树…

118 - ZOJ Monthly, July 2012

http://acm.zju.edu.cn/onlinejudge/showContestProblems.do?contestId339 都是赛后做的。。。弱爆了 A题是找由2和5组成的数字的个数 直接打个表就行了 只是比赛的时候不知道怎么打表啊。。 View Code #include<cstdio> #include<cstring> #include<algorith…

edp1.2和edp1.4_EDP​​的完整形式是什么?

edp1.2和edp1.4EDP​​&#xff1a;电子数据处理 (EDP: Electronic Data Processing) EDP is an abbreviation of Electronic Data Processing. It alludes to the functioning of operations of commercial data, documents processing of storing, with the use of a compute…

css链接样式_CSS中的样式链接

css链接样式CSS样式链接 (CSS Styling Links) The links in CSS can be styled in various ways to make our website more presentable and attractive. The links can also be styled depending on their states e.g. visited, active, hover, etc. CSS中的链接可以通过各种方…

css中的媒体查询_CSS中的媒体查询

css中的媒体查询CSS | 媒体查询 (CSS | Media Queries) Creating a web page is not an easy task as it requires loads of content and data so that it becomes strongly responsive to the users. To do that various contents are even added e.g.: resources, informativ…

SharePoint2013安装组件时AppFabric时出现1603错误,解决方法:

采用PowerShell命令批量下载必备组件: 下载完成后&#xff0c;采用批处理命令安装必备组件。 注&#xff1a;SPS2013安装必备组件及批处理下载地址&#xff1a; 需要将必备组件放在安装文件的PrerequisiteInstallerFiles文件夹中&#xff0c;将PreReq2013.bat放在安装文件根目录…

《MySQL——数据表设计三大范式》

目录数据表设计范式第一范式第二范式第三范式数据表设计范式 第一范式 数据表中的所有字段都是不可分割的原子值。 字段值还可以继续拆分的&#xff0c;就不满足第一范式&#xff0c;如下&#xff1a; 下面这个&#xff0c;更加贴合第一范式&#xff1a; 范式设计得越详细&…

三道简单树型dp+01背包~~hdu1561,poj1947,zoj3626

以前学树型dp就是随便的看了几道题&#xff0c;没有特别注意树型dp中的小分类的总结&#xff0c;直到上次浙大月赛一道很简单的树型dp都不会&#xff0c;才意识到自己太水了&#xff5e;&#xff5e;come on&#xff01; hdu1561&#xff1a;题目给出了很多棵有根树&#xff0c…

css 字体图标更改颜色_在CSS中更改字体

css 字体图标更改颜色CSS字体属性 (CSS font properties ) Font properties in CSS is used to define the font family, boldness, size, and the style of a text. CSS中的字体属性用于定义字体系列 &#xff0c; 粗体 &#xff0c; 大小和文本样式 。 Syntax: 句法&#xf…

C++基础知识点整理

基本语法 1、static关键字的作用 1、全局静态变量 加了static关键字的全局变量只能在本文件中使用。 存储在静态存储区&#xff0c;整个程序运行期间都存在。 2、局部静态变量 作用域仍为局部作用域。 不过离开作用域之后&#xff0c;并没有销毁&#xff0c;而是贮存程序中&a…

组合问题 已知组合数_组合和问题

组合问题 已知组合数Description: 描述&#xff1a; This is a standard interview problem to make some combination of the numbers whose sum equals to a given number using backtracking. 这是一个标准的面试问题&#xff0c;它使用回溯功能将总和等于给定数字的数字进…

可变参数模板、右值引用带来的移动语义完美转发、lambda表达式的理解

可变参数模板 可变参数模板对参数进行了高度泛化&#xff0c;可以表示任意数目、任意类型的参数&#xff1a; 语法为&#xff1a;在class或者typename后面带上省略号。 Template<class ... T> void func(T ... args) {// }T:模板参数包&#xff0c;args叫做函数参数包 …

python 子图大小_Python | 图的大小

python 子图大小In some cases, the automatic figure size generated by the matplotlib.pyplot is not visually good or there could be some non-acceptable ratio in the figure. So, rather than allowing a pyplot to decide the figure size, we can manually define t…

《设计模式整理》

目录常见设计模式如何保证单例模式只有一个实例单例模式中的懒汉与饿汉模式OOP设计模式的五项原则单例模式中的懒汉加载&#xff0c;如果并发访问该怎么做常见设计模式 单例模式&#xff1a; 单例模式主要解决了一个全局使用的类频繁的创建和销毁的问题。 单例模式下确保某一个…

JSON学习资料整理

1.什么是JSON JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于JavaScript的一个子集。 JSON采用完全独立于语言的文本格式&#xff0c;但是也使用了类似于C语言家族的习惯&#xff08;包括C, C, C#, Java, JavaScript, Perl, Python等&#xff09;。这些…

OSI七层模型及其数据的封装和解封过程

OSI(Open System Interconnection)参考模型把网络分为七层: 1.物理层(Physical Layer) 物理层主要传输原始的比特流,集线器(Hub)是本层的典型设备; 2.数据链路层(Data Link Layer) 数据链路层负责在两个相邻节点间无差错的传送以帧为单位的数据,本层的典型设备是交换机(Switch)…

rss聚合模式案例_RSS的完整形式是什么?

rss聚合模式案例RSS&#xff1a;真正简单的联合 (RSS: Really Simple Syndication) RSS is an abbreviation of Really Simple Syndication. It is also called Rich Site Summary. It is quality attainment for the syndication of collection of web content and used to di…

《MySQL——38道查询练习(无连接查询)》

目录一、准备数据1、创建数据库2、创建学生表3、创建教师表4、创建课程表5、创建成绩表6、添加数据二、查询练习1、查询 student 表的所有行2、查询 student 表中的 name、sex 和 class 字段的所有行3、查询 teacher 表中不重复的 department 列4、查询 score 表中成绩在60-80之…

XPth和XSLT的一些简单用法

&#xff08;目的在于让大家知道有这个东西的存在&#xff09; XPath:即XML Path语言(Xpath)表达式使用路径表示法(像在URL中使用一样)来为XML文档的各部分寻址&#xff01; 关于XPath如何使用了&#xff0c;我们来看看&#xff01;当然这里面的代码只是入门&#xff0c;更深层…

isc dhcp_ISC的完整形式是什么?

isc dhcpISC&#xff1a;印度学校证书 (ISC: Indian School Certificate) ISC is an abbreviation of the Indian School Certificate. It alludes to the 12th class examination or higher secondary examination conducted by the Council for the Indian School Certificat…