android listivew 下拉回弹刷新

简介:

该效果是一名国外工程师(johannilsson)的代码,拿来研究了下,自己整合了一下,现在拿出来,跟大家一起分享。

再次感谢这位国外工程师(johannilsson),谢谢!

新浪微博,和QQ空间里面,都有那个下拉刷新的效果,另很多人眼前一亮,细细分析,原理原来如此。

在原作者的基础上,写了一些注释,和帮助大家更好的阅读理解,(可能其中有些地方注释不准,欢迎指正,谢谢)

下面,就亮出关键代码:

 ****  自定义listivew   (关键代码)

[java]  view plain copy
  1. public class PullToRefreshListView extends ListView implements OnScrollListener {  
  2.    
  3.     private static final int TAP_TO_REFRESH = 1;     // 初始状态  
  4.     private static final int PULL_TO_REFRESH = 2;    //拉动刷新  
  5.     private static final int RELEASE_TO_REFRESH = 3;  //释放刷新  
  6.     private static final int REFRESHING = 4;    //正在刷新  
  7.    
  8.     private static final String TAG = "PullToRefreshListView";  
  9.     //刷新接口  
  10.     private OnRefreshListener mOnRefreshListener;  
  11.    
  12.     //箭头图片  
  13.     private static  int REFRESHICON = R.drawable.goicon;  
  14.     
  15.     /** 
  16.      * listview 滚动监听器 
  17.      */  
  18.     private OnScrollListener mOnScrollListener;  
  19.      
  20.     //视图索引器  
  21.     private LayoutInflater mInflater;  
  22.     /** 
  23.      * 头部视图  内容  -- start 
  24.      */  
  25.     private RelativeLayout mRefreshView;  
  26.     private TextView mRefreshViewText;  
  27.     private ImageView mRefreshViewImage;  
  28.     private ProgressBar mRefreshViewProgress;  
  29.     private TextView mRefreshViewLastUpdated;  
  30.     /** 
  31.      * 头部视图  内容  -- end 
  32.      */  
  33.     //当前listivew 的滚动状态  
  34.     private int mCurrentScrollState;  
  35.      
  36.     //当前listview 的刷新状态  
  37.     private int mRefreshState;  
  38.    
  39.     //动画效果  
  40.     //变为向下的箭头  
  41.     private RotateAnimation mFlipAnimation;  
  42.     //变为逆向的箭头  
  43.     private RotateAnimation mReverseFlipAnimation;  
  44.     //头视图的高度  
  45.     private int mRefreshViewHeight;  
  46.     //头视图 原始的top padding 属性值  
  47.     private int mRefreshOriginalTopPadding;  
  48.     //  
  49.     private int mLastMotionY;  
  50.     //是否反弹  
  51.     private boolean mBounceHack;  
  52.    
  53.     public PullToRefreshListView(Context context) {  
  54.         super(context);  
  55.         init(context);  
  56.     }  
  57.    
  58.     public PullToRefreshListView(Context context, AttributeSet attrs) {  
  59.         super(context, attrs);  
  60.         init(context);  
  61.     }  
  62.    
  63.     public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {  
  64.         super(context, attrs, defStyle);  
  65.         init(context);  
  66.     }  
  67.    
  68.     private void init(Context context) {  
  69.         // Load all of the animations we need in code rather than through XML  
  70.      //初始化动画  
  71.      //  
  72.         mFlipAnimation = new RotateAnimation(0, -180,  
  73.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
  74.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
  75.         mFlipAnimation.setInterpolator(new LinearInterpolator());  
  76.         mFlipAnimation.setDuration(250);  
  77.         mFlipAnimation.setFillAfter(true);  
  78.   
  79.         mReverseFlipAnimation = new RotateAnimation(-1800,  
  80.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
  81.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
  82.         mReverseFlipAnimation.setInterpolator(new LinearInterpolator());  
  83.         mReverseFlipAnimation.setDuration(250);  
  84.         mReverseFlipAnimation.setFillAfter(true);  
  85.    
  86.         mInflater = (LayoutInflater) context.getSystemService(  
  87.                 Context.LAYOUT_INFLATER_SERVICE);  
  88.    
  89.   mRefreshView = (RelativeLayout) mInflater.inflate(R.layout.pull_to_refresh_header, thisfalse);//(R.layout.pull_to_refresh_header, null);  
  90.     mRefreshViewText =  
  91.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);  
  92.         mRefreshViewImage =  
  93.             (ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);  
  94.         mRefreshViewProgress =  
  95.             (ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);  
  96.         mRefreshViewLastUpdated =  
  97.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);  
  98.    
  99.         mRefreshViewImage.setMinimumHeight(50);  
  100.         mRefreshView.setOnClickListener(new OnClickRefreshListener());  
  101.         mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();  
  102.    
  103.         mRefreshState = TAP_TO_REFRESH;  
  104.    
  105.         addHeaderView(mRefreshView);  
  106.    
  107.         super.setOnScrollListener(this);  
  108.    
  109.         measureView(mRefreshView);  
  110.         mRefreshViewHeight = mRefreshView.getMeasuredHeight();  //获取头文件的测量高度  
  111.     }  
  112.    
  113.     @Override  
  114.     protected void onAttachedToWindow() {  
  115.         setSelection(1);  
  116.     }  
  117.    
  118.     @Override  
  119.     public void setAdapter(ListAdapter adapter) {  
  120.         super.setAdapter(adapter);  
  121.         setSelection(1);  
  122.     }  
  123.    
  124.     /** 
  125.      * Set the listener that will receive notifications every time the list 
  126.      * scrolls. 
  127.      * 
  128.      * @param l The scroll listener. 
  129.      */  
  130.     @Override  
  131.     public void setOnScrollListener(AbsListView.OnScrollListener l) {  
  132.         mOnScrollListener = l;  
  133.     }  
  134.    
  135.     /** 
  136.      * Register a callback to be invoked when this list should be refreshed. 
  137.      * 
  138.      * @param onRefreshListener The callback to run. 
  139.      */  
  140.     public void setOnRefreshListener(OnRefreshListener onRefreshListener) {  
  141.         mOnRefreshListener = onRefreshListener;  
  142.     }  
  143.    
  144.     /** 
  145.      * Set a text to represent when the list was last updated. 
  146.      * @param lastUpdated Last updated at. 
  147.      */  
  148.     public void setLastUpdated(CharSequence lastUpdated) {  
  149.         if (lastUpdated != null) {  
  150.             mRefreshViewLastUpdated.setVisibility(View.VISIBLE);  
  151.             mRefreshViewLastUpdated.setText(lastUpdated);  
  152.         } else {  
  153.             mRefreshViewLastUpdated.setVisibility(View.GONE);  
  154.         }  
  155.     }  
  156.    
  157.     @Override  
  158.     public boolean onTouchEvent(MotionEvent event) {  
  159.      //当前手指的Y值  
  160.         final int y = (int) event.getY();  
  161.          
  162.         //Log.i(TAG, "触屏的Y值"+y);  
  163.         mBounceHack = false;  //不反弹  
  164.          
  165.         switch (event.getAction()) {  
  166.             case MotionEvent.ACTION_UP:  
  167.              //将垂直滚动条设置为可用状态  
  168.                 if (!isVerticalScrollBarEnabled()) {  
  169.                     setVerticalScrollBarEnabled(true);  
  170.                 }  
  171.                  
  172.                 //如果头部刷新条出现,并且不是正在刷新状态  
  173.                 if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {  
  174.                     if ((mRefreshView.getBottom() >= mRefreshViewHeight  
  175.                             || mRefreshView.getTop() >= 0)  
  176.                             && mRefreshState == RELEASE_TO_REFRESH) {   //如果头部视图处于拉离顶部的情况  
  177.                         // Initiate the refresh  
  178.                         mRefreshState = REFRESHING;  //将标量设置为,正在刷新  
  179.                         prepareForRefresh();  //准备刷新  
  180.                         onRefresh();   //刷新  
  181.                     } else if (mRefreshView.getBottom() < mRefreshViewHeight  
  182.                             || mRefreshView.getTop() <= 0) {  
  183.                         // Abort refresh and scroll down below the refresh view  
  184.                      // 停止刷新,并且滚动到头部刷新视图的下一个视图  
  185.                         resetHeader();  
  186.                         setSelection(1);  //定位在第二个列表项  
  187.                     }  
  188.                 }  
  189.                 break;  
  190.             case MotionEvent.ACTION_DOWN:  
  191.                 mLastMotionY = y;  //跟踪手指的Y值  
  192.                 break;  
  193.              
  194.             case MotionEvent.ACTION_MOVE:  
  195.              //更行头视图的toppadding 属性  
  196.                 applyHeaderPadding(event);  
  197.                 break;  
  198.         }  
  199.         return super.onTouchEvent(event);  
  200.     }  
  201.    
  202.     /**** 
  203.      * 不断的头部的top padding 属性 
  204.      * @param ev 
  205.      */  
  206.     private void applyHeaderPadding(MotionEvent ev) {  
  207.         //获取累积的动作数  
  208.         int pointerCount = ev.getHistorySize();  
  209.        // Log.i(TAG, "获取累积的动作数"+pointerCount);  
  210.         for (int p = 0; p < pointerCount; p++) {  
  211.             if (mRefreshState == RELEASE_TO_REFRESH) {    //如果是释放将要刷新状态  
  212.                 if (isVerticalFadingEdgeEnabled()) {    
  213.                     setVerticalScrollBarEnabled(false);  
  214.                 }  
  215.                 //历史累积的高度  
  216.                 int historicalY = (int) ev.getHistoricalY(p);  
  217.                 //Log.i(TAG, "单个动作getHistoricalY值:"+historicalY);  
  218.                 // Calculate the padding to apply, we divide by 1.7 to  
  219.                 // simulate a more resistant effect during pull.  
  220.                 int topPadding = (int) (((historicalY - mLastMotionY)  
  221.                         - mRefreshViewHeight) / 1.7);  
  222.                  
  223.                 mRefreshView.setPadding(  
  224.                         mRefreshView.getPaddingLeft(),  
  225.                         topPadding,  
  226.                         mRefreshView.getPaddingRight(),  
  227.                         mRefreshView.getPaddingBottom());  
  228.             }  
  229.         }  
  230.     }  
  231.    
  232.     /** 
  233.      * Sets the header padding back to original size. 
  234.      * 使头部视图的toppadding 恢复到初始值 
  235.      */  
  236.     private void resetHeaderPadding() {  
  237.         mRefreshView.setPadding(  
  238.                 mRefreshView.getPaddingLeft(),  
  239.                 mRefreshOriginalTopPadding,  
  240.                 mRefreshView.getPaddingRight(),  
  241.                 mRefreshView.getPaddingBottom());  
  242.     }  
  243.    
  244.     /** 
  245.      * Resets the header to the original state.  
  246.      *  初始化头部视图 状态 
  247.      */  
  248.     private void resetHeader() {  
  249.         if (mRefreshState != TAP_TO_REFRESH) {  
  250.             mRefreshState = TAP_TO_REFRESH; //初始刷新状态  
  251.             //使头部视图的toppadding 恢复到初始值  
  252.             resetHeaderPadding();  
  253.             // Set refresh view text to the pull label  
  254.             //将文字初始化  
  255.             mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);  
  256.             // Replace refresh drawable with arrow drawable  
  257.             //设置初始图片  
  258.             mRefreshViewImage.setImageResource(REFRESHICON);  
  259.             // Clear the full rotation animation  
  260.             // 清除动画  
  261.             mRefreshViewImage.clearAnimation();  
  262.             // Hide progress bar and arrow.  
  263.             //隐藏头视图  
  264.             mRefreshViewImage.setVisibility(View.GONE);  
  265.             //隐藏进度条  
  266.             mRefreshViewProgress.setVisibility(View.GONE);  
  267.         }  
  268.     }  
[java]  view plain copy
  1. //测量视图的高度  
  2. private void measureView(View child) {  
  3.  //获取头部视图属性  
  4.     ViewGroup.LayoutParams p = child.getLayoutParams();  
  5.     if (p == null) {  
  6.         p = new ViewGroup.LayoutParams(  
  7.                 ViewGroup.LayoutParams.FILL_PARENT,  
  8.                 ViewGroup.LayoutParams.WRAP_CONTENT);  
  9.     }  
  10.      
  11.     int childWidthSpec = ViewGroup.getChildMeasureSpec(0,  
  12.             0 + 0, p.width);  
  13.     int lpHeight = p.height;  
  14.     int childHeightSpec;  
  15.      
  16.     //不懂MeasureSpec------------------------------------------------------------------------------------------  
  17.     if (lpHeight > 0) {  //如果视图的高度大于0  
  18.         childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);   
  19.     } else {  
  20.         childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  21.     }  
  22.     child.measure(childWidthSpec, childHeightSpec);  
  23.     //不懂MeasureSpec------------------------------------------------------------------------------------------  
  24. }  
  25.   
  26. /**** 
  27.  * 滑动事件 
  28.  */  
  29. @Override  
  30. public void onScroll(AbsListView view, int firstVisibleItem,  
  31.         int visibleItemCount, int totalItemCount) {  
  32.     // When the refresh view is completely visible, change the text to say  
  33.     // "Release to refresh..." and flip the arrow drawable.  
  34.     if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL   //如果是接触滚动状态,并且不是正在刷新的状态  
  35.             && mRefreshState != REFRESHING) {  
  36.         if (firstVisibleItem == 0) {    //如果显示出来了第一个列表项  
  37.          //显示刷新图片  
  38.             mRefreshViewImage.setVisibility(View.VISIBLE);  
  39.             if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20  
  40.                     || mRefreshView.getTop() >= 0)  
  41.                     && mRefreshState != RELEASE_TO_REFRESH) {  //如果下拉了listiview,则显示上拉刷新动画  
  42.                 mRefreshViewText.setText(R.string.pull_to_refresh_release_label);  
  43.                 mRefreshViewImage.clearAnimation();  
  44.                 mRefreshViewImage.startAnimation(mFlipAnimation);  
  45.                 mRefreshState = RELEASE_TO_REFRESH;  
  46.                  
  47.                 Log.i(TAG, "现在处于下拉状态");  
  48.                  
  49.             } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20  
  50.                     && mRefreshState != PULL_TO_REFRESH) {    //如果没有到达,下拉刷新距离,则回归原来的状态  
  51.                 mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);  
  52.                 if (mRefreshState != TAP_TO_REFRESH) {  
  53.                     mRefreshViewImage.clearAnimation();  
  54.                     mRefreshViewImage.startAnimation(mReverseFlipAnimation);  
  55.                      
  56.                     Log.i(TAG, "现在处于回弹状态");  
  57.                      
  58.                 }  
  59.                 mRefreshState = PULL_TO_REFRESH;  
  60.             }  
  61.         } else {    
  62.             mRefreshViewImage.setVisibility(View.GONE);  //隐藏刷新图片  
  63.             resetHeader();   //初始化,头部  
  64.         }  
  65.     } else if (mCurrentScrollState == SCROLL_STATE_FLING  //如果是自己滚动状态+ 第一个视图已经显示+ 不是刷新状态  
  66.             && firstVisibleItem == 0  
  67.             && mRefreshState != REFRESHING) {  
  68.         setSelection(1);  
  69.         mBounceHack = true;   //状态为回弹  
  70.         Log.i(TAG, "现在处于自由滚动到顶部的状态");  
  71.     } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {  
  72.         setSelection(1);  
  73.         Log.i(TAG, "现在处于自由滚动到顶部的状态");  
  74.     }  
  75.   
  76.     if (mOnScrollListener != null) {  
  77.         mOnScrollListener.onScroll(view, firstVisibleItem,  
  78.                 visibleItemCount, totalItemCount);  
  79.     }  
  80. }  
  81.   
  82.   
  83. //滚动状态改变  
  84. @Override  
  85. public void onScrollStateChanged(AbsListView view, int scrollState) {  
  86.     mCurrentScrollState = scrollState;  
  87.   
  88.     if (mCurrentScrollState == SCROLL_STATE_IDLE) {   //如果滚动停顿  
  89.         mBounceHack = false;  
  90.     }  
  91.   
  92.     if (mOnScrollListener != null) {  
  93.         mOnScrollListener.onScrollStateChanged(view, scrollState);  
  94.     }  
  95. }  
  96.   
  97.   
  98.   
  99. //准备刷新  
  100. public void prepareForRefresh() {  
  101.     resetHeaderPadding();   //初始化,头部文件  
  102.   
  103.     mRefreshViewImage.setVisibility(View.GONE);  
  104.     // We need this hack, otherwise it will keep the previous drawable.  
  105.     mRefreshViewImage.setImageDrawable(null);  
  106.     mRefreshViewProgress.setVisibility(View.VISIBLE);  
  107.   
  108.     // Set refresh view text to the refreshing label  
  109.    mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);  
  110.   
  111.     mRefreshState = REFRESHING;  
  112. }  
  113.   
  114. //刷新  
  115. public void onRefresh() {  
  116.     Log.d(TAG, "执行刷新");  
  117.   
  118.     if (mOnRefreshListener != null) {  
  119.         mOnRefreshListener.onRefresh();  
  120.     }  
  121. }  
  122.   
  123. /** 
  124.  * 刷新完成 的回调函数 
  125.  * Resets the list to a normal state after a refresh. 
  126.  * @param lastUpdated Last updated at. 
  127.  */  
  128. public void onRefreshComplete(CharSequence lastUpdated) {  
  129.     setLastUpdated(lastUpdated);  
  130.     onRefreshComplete();  
  131. }  
  132.   
  133. /** 
  134.  *  刷新完成回调函数 
  135.  * Resets the list to a normal state after a refresh. 
  136.  */  
  137. public void onRefreshComplete() {         
  138.     Log.d(TAG, "onRefreshComplete");  
  139.   
  140.     resetHeader();  
  141.   
  142.     // If refresh view is visible when loading completes, scroll down to  
  143.     // the next item.  
  144.     if (mRefreshView.getBottom() > 0) {  
  145.         invalidateViews();  //重绘视图  
  146.         setSelection(1);  
  147.     }  
  148. }  
  149.   
  150. /** 
  151.  * Invoked when the refresh view is clicked on. This is mainly used when 
  152.  * there's only a few items in the list and it's not possible to drag the 
  153.  * list. 
  154.  */  
  155. private class OnClickRefreshListener implements OnClickListener {  
  156.   
  157.     @Override  
  158.     public void onClick(View v) {  
  159.         if (mRefreshState != REFRESHING) {  
  160.             //准备刷新  
  161.             prepareForRefresh();   
  162.             //刷新    
  163.             onRefresh();  
  164.         }  
  165.     }  
  166.   
  167. }  
  168.   
  169. /** 
  170.  * 刷新方法接口 
  171.  */  
  172. public interface OnRefreshListener {  
  173.      
  174.     public void onRefresh();  
  175. }  

  

---------


------


 

* 如果你还是没有弄明白的话,那就点击下面的链接,来下载整个demo项目: 

 http://download.csdn.net/detail/zjl5211314/3775209

 原作者:johannilsson

 选自:https://github.com/johannilsson/android-pulltorefresh

相关文章
|
9月前
|
Android开发
Android RecyclerView的notify方法和动画的刷新详解(二)
Android RecyclerView的notify方法和动画的刷新详解
158 0
|
3小时前
|
XML Java Android开发
Android Studio App开发之利用图片加载框架Glide实现刷新验证码功能(附源码 简单易懂)
Android Studio App开发之利用图片加载框架Glide实现刷新验证码功能(附源码 简单易懂)
35 0
|
9月前
|
XML Android开发 数据格式
Android RecyclerView的notify方法和动画的刷新详解(一)
Android RecyclerView的notify方法和动画的刷新详解
80 0
|
10月前
|
Android开发
Android中下拉通知栏,Activity会走哪些生命周期?
我们就可以做一个总结:当前Activity中,下拉通知栏,是不走任何生命周期的。
156 0
|
Java Shell API
Android源码(6.0和8.1) 屏蔽状态栏下拉和屏蔽导航栏显示
Android源码(6.0和8.1) 屏蔽状态栏下拉和屏蔽导航栏显示
412 0
|
缓存 Android开发 UED
一文读懂系列Android屏幕刷新机制
对一些大中型项目来说可能就不一样了:**他们涉及业务较多,设备种类较多,往往一个app内部集成了十几个子业务甚至上百个,这对应用性能要求就更加严格了,app的体验也会间接导致用户的留存问题**。 所以学习屏幕绘制这类理论性较强的知识也是非常有必要的。
|
Android开发
Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景
Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景
664 0
Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景
|
Android开发
安卓使用RecycleView+SmartRefreshLayout+CommonAdapter实现最简单上拉刷新,下拉加载
安卓使用RecycleView+SmartRefreshLayout+CommonAdapter实现最简单上拉刷新,下拉加载
242 0
安卓使用RecycleView+SmartRefreshLayout+CommonAdapter实现最简单上拉刷新,下拉加载
|
Android开发
关于安卓使用Recyclerview的刷新
关于安卓使用Recyclerview的刷新
129 0
|
XML 传感器 前端开发
Android——MVVM架构实现数据刷新
效果图 示例结构图 代码解析 导入dataBinding 实体类 显示图片 实体类全部代码 xml视图 VM 接收数据 发送数据 建立接口,回调数据 制造数据 绑定视图与数据层
205 0
Android——MVVM架构实现数据刷新