Android(自定义控件)下拉刷新上拉加载,所有View通用.(直接拿来用)

我做了一个大集合的demo,实现了ListView、GridView、ExpandableListView、ScrollView、WebView、ImageView、TextView的下拉刷新和上拉加载。后面会提供demo的下载地址。(csdn上的demo有小bug,最新代码已上传到github:https://github.com/jingchenUSTC/PullToRefreshAndLoad)

    依照惯例,下面将会是一大波效果图:

demo首页也是可下拉的ListView,在底下可以加入table:

                                            

ListView:

                

GridView:

                

ExpandableListView:

                

ScrollView:

                

WebView:

                

ImageView:

                

TextView:

                

    很不错吧?最后的ImageView和TextView是最简单的,直接在下面的接口方法里返回true。

    增加上拉加载很简单,和管理下拉头一样,再多管理一个上拉头,也不费事;至于把它改成通用的就需要统一一下View的行为了,为此,我定义了这样一个接口:

[java] view plaincopy
  1. package com.jingchen.pulltorefresh.pullableview;  
  2.   
  3. public interface Pullable  
  4. {  
  5.     /** 
  6.      * 判断是否可以下拉,如果不需要下拉功能可以直接return false 
  7.      *  
  8.      * @return true如果可以下拉否则返回false 
  9.      */  
  10.     boolean canPullDown();  
  11.   
  12.     /** 
  13.      * 判断是否可以上拉,如果不需要上拉功能可以直接return false 
  14.      *  
  15.      * @return true如果可以上拉否则返回false 
  16.      */  
  17.     boolean canPullUp();  
  18. }  
从接口名就可以看出它是一个提供判断是否可拉的方法的接口。这个接口的两个方法,canPullDown()是判断何时可以下拉的方法,canPullUp()则是判断何时可以上拉,我在demo中的判断是滑到顶部的时候可以下拉,滑到底部的时候可以上拉。所有需要上拉和下拉的View都需要实现这个接口。后面会给出一些View的实现。先来看看改进后的自定义的布局PullToRefreshLayout,增加了一个上拉头,下拉头和上拉头之间的View是实现了Pullable接口的pullableView。相比前面的版本,这里有改动的需要注意的地方如下:

1、增加了上拉头,相应的也增加了控制变量。

2、拉动时消除content_view事件防止误触发不再使用反射,直接设置                       event.setAction(MotionEvent.ACTION_CANCEL)。

3、消除了拉动过程中的多点触碰导致的剧变。

4、不再设置content_view的onTouListener,让使用者可以更加自由的设置监听器。

   这个PullToRefreshLayout只负责管理三个控件,如果一个View需要有上拉下拉功能则只需实现接口就行了。下面看PullToRefreshLayout的代码,注释写了好多:

[java] view plaincopy
  1. package com.jingchen.pulltorefresh;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. import android.content.Context;  
  7. import android.graphics.Canvas;  
  8. import android.graphics.LinearGradient;  
  9. import android.graphics.Paint;  
  10. import android.graphics.Paint.Style;  
  11. import android.graphics.RectF;  
  12. import android.graphics.Shader.TileMode;  
  13. import android.os.Handler;  
  14. import android.os.Message;  
  15. import android.util.AttributeSet;  
  16. import android.util.Log;  
  17. import android.view.MotionEvent;  
  18. import android.view.View;  
  19. import android.view.ViewGroup;  
  20. import android.view.animation.AnimationUtils;  
  21. import android.view.animation.LinearInterpolator;  
  22. import android.view.animation.RotateAnimation;  
  23. import android.widget.RelativeLayout;  
  24. import android.widget.TextView;  
  25.   
  26. import com.jingchen.pulltorefresh.pullableview.Pullable;  
  27.   
  28. /** 
  29.  * 自定义的布局,用来管理三个子控件,其中一个是下拉头,一个是包含内容的pullableView(可以是实现Pullable接口的的任何View), 
  30.  * 还有一个上拉头 
  31.  *  
  32.  * @author 陈靖 
  33.  */  
  34. public class PullToRefreshLayout extends RelativeLayout  
  35. {  
  36.     public static final String TAG = "PullToRefreshLayout";  
  37.     // 初始状态  
  38.     public static final int INIT = 0;  
  39.     // 释放刷新  
  40.     public static final int RELEASE_TO_REFRESH = 1;  
  41.     // 正在刷新  
  42.     public static final int REFRESHING = 2;  
  43.     // 释放加载  
  44.     public static final int RELEASE_TO_LOAD = 3;  
  45.     // 正在加载  
  46.     public static final int LOADING = 4;  
  47.     // 操作完毕  
  48.     public static final int DONE = 5;  
  49.     // 当前状态  
  50.     private int state = INIT;  
  51.     // 刷新回调接口  
  52.     private OnRefreshListener mListener;  
  53.     // 刷新成功  
  54.     public static final int SUCCEED = 0;  
  55.     // 刷新失败  
  56.     public static final int FAIL = 1;  
  57.     // 按下Y坐标,上一个事件点Y坐标  
  58.     private float downY, lastY;  
  59.   
  60.     // 下拉的距离。注意:pullDownY和pullUpY不可能同时不为0  
  61.     public float pullDownY = 0;  
  62.     // 上拉的距离  
  63.     private float pullUpY = 0;  
  64.   
  65.     // 释放刷新的距离  
  66.     private float refreshDist = 200;  
  67.     // 释放加载的距离  
  68.     private float loadmoreDist = 200;  
  69.   
  70.     private MyTimer timer;  
  71.     // 回滚速度  
  72.     public float MOVE_SPEED = 8;  
  73.     // 第一次执行布局  
  74.     private boolean isLayout = false;  
  75.     // 在刷新过程中滑动操作  
  76.     private boolean isTouch = false;  
  77.     // 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化  
  78.     private float radio = 2;  
  79.   
  80.     // 下拉箭头的转180°动画  
  81.     private RotateAnimation rotateAnimation;  
  82.     // 均匀旋转动画  
  83.     private RotateAnimation refreshingAnimation;  
  84.   
  85.     // 下拉头  
  86.     private View refreshView;  
  87.     // 下拉的箭头  
  88.     private View pullView;  
  89.     // 正在刷新的图标  
  90.     private View refreshingView;  
  91.     // 刷新结果图标  
  92.     private View refreshStateImageView;  
  93.     // 刷新结果:成功或失败  
  94.     private TextView refreshStateTextView;  
  95.   
  96.     // 上拉头  
  97.     private View loadmoreView;  
  98.     // 上拉的箭头  
  99.     private View pullUpView;  
  100.     // 正在加载的图标  
  101.     private View loadingView;  
  102.     // 加载结果图标  
  103.     private View loadStateImageView;  
  104.     // 加载结果:成功或失败  
  105.     private TextView loadStateTextView;  
  106.   
  107.     // 实现了Pullable接口的View  
  108.     private View pullableView;  
  109.     // 过滤多点触碰  
  110.     private int mEvents;  
  111.     // 这两个变量用来控制pull的方向,如果不加控制,当情况满足可上拉又可下拉时没法下拉  
  112.     private boolean canPullDown = true;  
  113.     private boolean canPullUp = true;  
  114.   
  115.     /** 
  116.      * 执行自动回滚的handler 
  117.      */  
  118.     Handler updateHandler = new Handler()  
  119.     {  
  120.   
  121.         @Override  
  122.         public void handleMessage(Message msg)  
  123.         {  
  124.             // 回弹速度随下拉距离moveDeltaY增大而增大  
  125.             MOVE_SPEED = (float) (8 + 5 * Math.tan(Math.PI / 2  
  126.                     / getMeasuredHeight() * (pullDownY + Math.abs(pullUpY))));  
  127.             if (!isTouch)  
  128.             {  
  129.                 // 正在刷新,且没有往上推的话则悬停,显示"正在刷新..."  
  130.                 if (state == REFRESHING && pullDownY <= refreshDist)  
  131.                 {  
  132.                     pullDownY = refreshDist;  
  133.                     timer.cancel();  
  134.                 } else if (state == LOADING && -pullUpY <= loadmoreDist)  
  135.                 {  
  136.                     pullUpY = -loadmoreDist;  
  137.                     timer.cancel();  
  138.                 }  
  139.   
  140.             }  
  141.             if (pullDownY > 0)  
  142.                 pullDownY -= MOVE_SPEED;  
  143.             else if (pullUpY < 0)  
  144.                 pullUpY += MOVE_SPEED;  
  145.             if (pullDownY < 0)  
  146.             {  
  147.                 // 已完成回弹  
  148.                 pullDownY = 0;  
  149.                 pullView.clearAnimation();  
  150.                 // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态  
  151.                 if (state != REFRESHING && state != LOADING)  
  152.                     changeState(INIT);  
  153.                 timer.cancel();  
  154.             }  
  155.             if (pullUpY > 0)  
  156.             {  
  157.                 // 已完成回弹  
  158.                 pullUpY = 0;  
  159.                 pullUpView.clearAnimation();  
  160.                 // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态  
  161.                 if (state != REFRESHING && state != LOADING)  
  162.                     changeState(INIT);  
  163.                 timer.cancel();  
  164.             }  
  165.             // 刷新布局,会自动调用onLayout  
  166.             requestLayout();  
  167.         }  
  168.   
  169.     };  
  170.   
  171.     public void setOnRefreshListener(OnRefreshListener listener)  
  172.     {  
  173.         mListener = listener;  
  174.     }  
  175.   
  176.     public PullToRefreshLayout(Context context)  
  177.     {  
  178.         super(context);  
  179.         initView(context);  
  180.     }  
  181.   
  182.     public PullToRefreshLayout(Context context, AttributeSet attrs)  
  183.     {  
  184.         super(context, attrs);  
  185.         initView(context);  
  186.     }  
  187.   
  188.     public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle)  
  189.     {  
  190.         super(context, attrs, defStyle);  
  191.         initView(context);  
  192.     }  
  193.   
  194.     private void initView(Context context)  
  195.     {  
  196.         timer = new MyTimer(updateHandler);  
  197.         rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(  
  198.                 context, R.anim.reverse_anim);  
  199.         refreshingAnimation = (RotateAnimation) AnimationUtils.loadAnimation(  
  200.                 context, R.anim.rotating);  
  201.         // 添加匀速转动动画  
  202.         LinearInterpolator lir = new LinearInterpolator();  
  203.         rotateAnimation.setInterpolator(lir);  
  204.         refreshingAnimation.setInterpolator(lir);  
  205.     }  
  206.   
  207.     private void hide()  
  208.     {  
  209.         timer.schedule(5);  
  210.     }  
  211.   
  212.     /** 
  213.      * 完成刷新操作,显示刷新结果。注意:刷新完成后一定要调用这个方法 
  214.      */  
  215.     /** 
  216.      * @param refreshResult 
  217.      *            PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失败 
  218.      */  
  219.     public void refreshFinish(int refreshResult)  
  220.     {  
  221.         refreshingView.clearAnimation();  
  222.         refreshingView.setVisibility(View.GONE);  
  223.         switch (refreshResult)  
  224.         {  
  225.         case SUCCEED:  
  226.             // 刷新成功  
  227.             refreshStateImageView.setVisibility(View.VISIBLE);  
  228.             refreshStateTextView.setText(R.string.refresh_succeed);  
  229.             refreshStateImageView  
  230.                     .setBackgroundResource(R.drawable.refresh_succeed);  
  231.             break;  
  232.         case FAIL:  
  233.         default:  
  234.             // 刷新失败  
  235.             refreshStateImageView.setVisibility(View.VISIBLE);  
  236.             refreshStateTextView.setText(R.string.refresh_fail);  
  237.             refreshStateImageView  
  238.                     .setBackgroundResource(R.drawable.refresh_failed);  
  239.             break;  
  240.         }  
  241.         // 刷新结果停留1秒  
  242.         new Handler()  
  243.         {  
  244.             @Override  
  245.             public void handleMessage(Message msg)  
  246.             {  
  247.                 changeState(DONE);  
  248.                 hide();  
  249.             }  
  250.         }.sendEmptyMessageDelayed(01000);  
  251.     }  
  252.   
  253.     /** 
  254.      * 加载完毕,显示加载结果。注意:加载完成后一定要调用这个方法 
  255.      *  
  256.      * @param refreshResult 
  257.      *            PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失败 
  258.      */  
  259.     public void loadmoreFinish(int refreshResult)  
  260.     {  
  261.         loadingView.clearAnimation();  
  262.         loadingView.setVisibility(View.GONE);  
  263.         switch (refreshResult)  
  264.         {  
  265.         case SUCCEED:  
  266.             // 加载成功  
  267.             loadStateImageView.setVisibility(View.VISIBLE);  
  268.             loadStateTextView.setText(R.string.load_succeed);  
  269.             loadStateImageView.setBackgroundResource(R.drawable.load_succeed);  
  270.             break;  
  271.         case FAIL:  
  272.         default:  
  273.             // 加载失败  
  274.             loadStateImageView.setVisibility(View.VISIBLE);  
  275.             loadStateTextView.setText(R.string.load_fail);  
  276.             loadStateImageView.setBackgroundResource(R.drawable.load_failed);  
  277.             break;  
  278.         }  
  279.         // 刷新结果停留1秒  
  280.         new Handler()  
  281.         {  
  282.             @Override  
  283.             public void handleMessage(Message msg)  
  284.             {  
  285.                 changeState(DONE);  
  286.                 hide();  
  287.             }  
  288.         }.sendEmptyMessageDelayed(01000);  
  289.     }  
  290.   
  291.     private void changeState(int to)  
  292.     {  
  293.         state = to;  
  294.         switch (state)  
  295.         {  
  296.         case INIT:  
  297.             // 下拉布局初始状态  
  298.             refreshStateImageView.setVisibility(View.GONE);  
  299.             refreshStateTextView.setText(R.string.pull_to_refresh);  
  300.             pullView.clearAnimation();  
  301.             pullView.setVisibility(View.VISIBLE);  
  302.             // 上拉布局初始状态  
  303.             loadStateImageView.setVisibility(View.GONE);  
  304.             loadStateTextView.setText(R.string.pullup_to_load);  
  305.             pullUpView.clearAnimation();  
  306.             pullUpView.setVisibility(View.VISIBLE);  
  307.             break;  
  308.         case RELEASE_TO_REFRESH:  
  309.             // 释放刷新状态  
  310.             refreshStateTextView.setText(R.string.release_to_refresh);  
  311.             pullView.startAnimation(rotateAnimation);  
  312.             break;  
  313.         case REFRESHING:  
  314.             // 正在刷新状态  
  315.             pullView.clearAnimation();  
  316.             refreshingView.setVisibility(View.VISIBLE);  
  317.             pullView.setVisibility(View.INVISIBLE);  
  318.             refreshingView.startAnimation(refreshingAnimation);  
  319.             refreshStateTextView.setText(R.string.refreshing);  
  320.             break;  
  321.         case RELEASE_TO_LOAD:  
  322.             // 释放加载状态  
  323.             loadStateTextView.setText(R.string.release_to_load);  
  324.             pullUpView.startAnimation(rotateAnimation);  
  325.             break;  
  326.         case LOADING:  
  327.             // 正在加载状态  
  328.             pullUpView.clearAnimation();  
  329.             loadingView.setVisibility(View.VISIBLE);  
  330.             pullUpView.setVisibility(View.INVISIBLE);  
  331.             loadingView.startAnimation(refreshingAnimation);  
  332.             loadStateTextView.setText(R.string.loading);  
  333.             break;  
  334.         case DONE:  
  335.             // 刷新或加载完毕,啥都不做  
  336.             break;  
  337.         }  
  338.     }  
  339.   
  340.     /** 
  341.      * 不限制上拉或下拉 
  342.      */  
  343.     private void releasePull()  
  344.     {  
  345.         canPullDown = true;  
  346.         canPullUp = true;  
  347.     }  
  348.   
  349.     /* 
  350.      * (非 Javadoc)由父控件决定是否分发事件,防止事件冲突 
  351.      *  
  352.      * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent) 
  353.      */  
  354.     @Override  
  355.     public boolean dispatchTouchEvent(MotionEvent ev)  
  356.     {  
  357.         switch (ev.getActionMasked())  
  358.         {  
  359.         case MotionEvent.ACTION_DOWN:  
  360.             downY = ev.getY();  
  361.             lastY = downY;  
  362.             timer.cancel();  
  363.             mEvents = 0;  
  364.             releasePull();  
  365.             break;  
  366.         case MotionEvent.ACTION_POINTER_DOWN:  
  367.         case MotionEvent.ACTION_POINTER_UP:  
  368.             // 过滤多点触碰  
  369.             mEvents = -1;  
  370.             break;  
  371.         case MotionEvent.ACTION_MOVE:  
  372.             if (mEvents == 0)  
  373.             {  
  374.                 if (((Pullable) pullableView).canPullDown() && canPullDown  
  375.                         && state != LOADING)  
  376.                 {  
  377.                     // 可以下拉,正在加载时不能下拉  
  378.                     // 对实际滑动距离做缩小,造成用力拉的感觉  
  379.                     pullDownY = pullDownY + (ev.getY() - lastY) / radio;  
  380.                     if (pullDownY < 0)  
  381.                     {  
  382.                         pullDownY = 0;  
  383.                         canPullDown = false;  
  384.                         canPullUp = true;  
  385.                     }  
  386.                     if (pullDownY > getMeasuredHeight())  
  387.                         pullDownY = getMeasuredHeight();  
  388.                     if (state == REFRESHING)  
  389.                     {  
  390.                         // 正在刷新的时候触摸移动  
  391.                         isTouch = true;  
  392.                     }  
  393.                 } else if (((Pullable) pullableView).canPullUp() && canPullUp  
  394.                         && state != REFRESHING)  
  395.                 {  
  396.                     // 可以上拉,正在刷新时不能上拉  
  397.                     pullUpY = pullUpY + (ev.getY() - lastY) / radio;  
  398.                     if (pullUpY > 0)  
  399.                     {  
  400.                         pullUpY = 0;  
  401.                         canPullDown = true;  
  402.                         canPullUp = false;  
  403.                     }  
  404.                     if (pullUpY < -getMeasuredHeight())  
  405.                         pullUpY = -getMeasuredHeight();  
  406.                     if (state == LOADING)  
  407.                     {  
  408.                         // 正在加载的时候触摸移动  
  409.                         isTouch = true;  
  410.                     }  
  411.                 } else  
  412.                     releasePull();  
  413.             } else  
  414.                 mEvents = 0;  
  415.             lastY = ev.getY();  
  416.             // 根据下拉距离改变比例  
  417.             radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight()  
  418.                     * (pullDownY + Math.abs(pullUpY))));  
  419.             requestLayout();  
  420.             if (pullDownY <= refreshDist  
  421. <span style="white-space:pre">                    </span>&& (state == RELEASE_TO_REFRESH || state == DONE)) {  
  422. <span style="white-space:pre">                </span>// 如果下拉距离没达到刷新的距离且当前状态是释放刷新,改变状态为下拉刷新  
  423. <span style="white-space:pre">                </span>changeState(INIT);  
  424. <span style="white-space:pre">            </span>}  
  425. <span style="white-space:pre">            </span>if (pullDownY >= refreshDist && (state == INIT || state == DONE)) {  
  426. <span style="white-space:pre">                </span>// 如果下拉距离达到刷新的距离且当前状态是初始状态刷新,改变状态为释放刷新  
  427. <span style="white-space:pre">                </span>changeState(RELEASE_TO_REFRESH);  
  428. <span style="white-space:pre">            </span>}  
  429. <span style="white-space:pre">            </span>// 下面是判断上拉加载的,同上,注意pullUpY是负值  
  430. <span style="white-space:pre">            </span>if (-pullUpY <= loadmoreDist  
  431. <span style="white-space:pre">                    </span>&& (state == RELEASE_TO_LOAD || state == DONE)) {  
  432. <span style="white-space:pre">                </span>changeState(INIT);  
  433. <span style="white-space:pre">            </span>}  
  434. <span style="white-space:pre">            </span>if (-pullUpY >= loadmoreDist && (state == INIT || state == DONE)) {  
  435. <span style="white-space:pre">                </span>changeState(RELEASE_TO_LOAD);  
  436. <span style="white-space:pre">            </span>}  
  437.             // 因为刷新和加载操作不能同时进行,所以pullDownY和pullUpY不会同时不为0,因此这里用(pullDownY +  
  438.             // Math.abs(pullUpY))就可以不对当前状态作区分了  
  439.             if ((pullDownY + Math.abs(pullUpY)) > 8)  
  440.             {  
  441.                 // 防止下拉过程中误触发长按事件和点击事件  
  442.                 ev.setAction(MotionEvent.ACTION_CANCEL);  
  443.             }  
  444.             break;  
  445.         case MotionEvent.ACTION_UP:  
  446.             if (pullDownY > refreshDist || -pullUpY > loadmoreDist)  
  447.                 // 正在刷新时往下拉(正在加载时往上拉),释放后下拉头(上拉头)不隐藏  
  448.                 isTouch = false;  
  449.             if (state == RELEASE_TO_REFRESH)  
  450.             {  
  451.                 changeState(REFRESHING);  
  452.                 // 刷新操作  
  453.                 if (mListener != null)  
  454.                     mListener.onRefresh(this);  
  455.             } else if (state == RELEASE_TO_LOAD)  
  456.             {  
  457.                 changeState(LOADING);  
  458.                 // 加载操作  
  459.                 if (mListener != null)  
  460.                     mListener.onLoadMore(this);  
  461.             }  
  462.             hide();  
  463.         default:  
  464.             break;  
  465.         }  
  466.         // 事件分发交给父类  
  467.         super.dispatchTouchEvent(ev);  
  468.         return true;  
  469.     }  
  470.   
  471.     private void initView()  
  472.     {  
  473.         // 初始化下拉布局  
  474.         pullView = refreshView.findViewById(R.id.pull_icon);  
  475.         refreshStateTextView = (TextView) refreshView  
  476.                 .findViewById(R.id.state_tv);  
  477.         refreshingView = refreshView.findViewById(R.id.refreshing_icon);  
  478.         refreshStateImageView = refreshView.findViewById(R.id.state_iv);  
  479.         // 初始化上拉布局  
  480.         pullUpView = loadmoreView.findViewById(R.id.pullup_icon);  
  481.         loadStateTextView = (TextView) loadmoreView  
  482.                 .findViewById(R.id.loadstate_tv);  
  483.         loadingView = loadmoreView.findViewById(R.id.loading_icon);  
  484.         loadStateImageView = loadmoreView.findViewById(R.id.loadstate_iv);  
  485.     }  
  486.   
  487.     @Override  
  488.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
  489.     {  
  490.         if (!isLayout)  
  491.         {  
  492.             // 这里是第一次进来的时候做一些初始化  
  493.             refreshView = getChildAt(0);  
  494.             pullableView = getChildAt(1);  
  495.             loadmoreView = getChildAt(2);  
  496.             isLayout = true;  
  497.             initView();  
  498.             refreshDist = ((ViewGroup) refreshView).getChildAt(0)  
  499.                     .getMeasuredHeight();  
  500.             loadmoreDist = ((ViewGroup) loadmoreView).getChildAt(0)  
  501.                     .getMeasuredHeight();  
  502.         }  
  503.         // 改变子控件的布局,这里直接用(pullDownY + pullUpY)作为偏移量,这样就可以不对当前状态作区分  
  504.         refreshView.layout(0,  
  505.                 (int) (pullDownY + pullUpY) - refreshView.getMeasuredHeight(),  
  506.                 refreshView.getMeasuredWidth(), (int) (pullDownY + pullUpY));  
  507.         pullableView.layout(0, (int) (pullDownY + pullUpY),  
  508.                 pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY)  
  509.                         + pullableView.getMeasuredHeight());  
  510.         loadmoreView.layout(0,  
  511.                 (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight(),  
  512.                 loadmoreView.getMeasuredWidth(),  
  513.                 (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight()  
  514.                         + loadmoreView.getMeasuredHeight());  
  515.     }  
  516.   
  517.     class MyTimer  
  518.     {  
  519.         private Handler handler;  
  520.         private Timer timer;  
  521.         private MyTask mTask;  
  522.   
  523.         public MyTimer(Handler handler)  
  524.         {  
  525.             this.handler = handler;  
  526.             timer = new Timer();  
  527.         }  
  528.   
  529.         public void schedule(long period)  
  530.         {  
  531.             if (mTask != null)  
  532.             {  
  533.                 mTask.cancel();  
  534.                 mTask = null;  
  535.             }  
  536.             mTask = new MyTask(handler);  
  537.             timer.schedule(mTask, 0, period);  
  538.         }  
  539.   
  540.         public void cancel()  
  541.         {  
  542.             if (mTask != null)  
  543.             {  
  544.                 mTask.cancel();  
  545.                 mTask = null;  
  546.             }  
  547.         }  
  548.   
  549.         class MyTask extends TimerTask  
  550.         {  
  551.             private Handler handler;  
  552.   
  553.             public MyTask(Handler handler)  
  554.             {  
  555.                 this.handler = handler;  
  556.             }  
  557.   
  558.             @Override  
  559.             public void run()  
  560.             {  
  561.                 handler.obtainMessage().sendToTarget();  
  562.             }  
  563.   
  564.         }  
  565.     }  
  566.   
  567.     /** 
  568.      * 刷新加载回调接口 
  569.      *  
  570.      * @author chenjing 
  571.      *  
  572.      */  
  573.     public interface OnRefreshListener  
  574.     {  
  575.         /** 
  576.          * 刷新操作 
  577.          */  
  578.         void onRefresh(PullToRefreshLayout pullToRefreshLayout);  
  579.   
  580.         /** 
  581.          * 加载操作 
  582.          */  
  583.         void onLoadMore(PullToRefreshLayout pullToRefreshLayout);  
  584.     }  
  585.   
  586. }  
上面就是整个布局的代码,并不是很难。

下面看各个View对Pullable接口的实现,ListView和GridView还有ExpandableListView的判断方法是一样的:

PullableListView:

[java] view plaincopy
  1. package com.jingchen.pulltorefresh.pullableview;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.util.Log;  
  6. import android.widget.ListView;  
  7.   
  8. public class PullableListView extends ListView implements Pullable  
  9. {  
  10.   
  11.     public PullableListView(Context context)  
  12.     {  
  13.         super(context);  
  14.     }  
  15.   
  16.     public PullableListView(Context context, AttributeSet attrs)  
  17.     {  
  18.         super(context, attrs);  
  19.     }  
  20.   
  21.     public PullableListView(Context context, AttributeSet attrs, int defStyle)  
  22.     {  
  23.         super(context, attrs, defStyle);  
  24.     }  
  25.   
  26.     @Override  
  27.     public boolean canPullDown()  
  28.     {  
  29.         if (getCount() == 0)  
  30.         {  
  31.             // 没有item的时候也可以下拉刷新  
  32.             return true;  
  33.         } else if (getFirstVisiblePosition() == 0  
  34.                 && getChildAt(0).getTop() >= 0)  
  35.         {  
  36.             // 滑到ListView的顶部了  
  37.             return true;  
  38.         } else  
  39.             return false;  
  40.     }  
  41.   
  42.     @Override  
  43.     public boolean canPullUp()  
  44.     {  
  45.         if (getCount() == 0)  
  46.         {  
  47.             // 没有item的时候也可以上拉加载  
  48.             return true;  
  49.         } else