ListView下拉刷新实现(类似陌陌的箭头转动)

  1 import android.animation.Animator;
  2 import android.animation.Animator.AnimatorListener;
  3 import android.animation.TypeEvaluator;
  4 import android.animation.ValueAnimator;
  5 import android.animation.ValueAnimator.AnimatorUpdateListener;
  6 import android.content.Context;
  7 import android.util.AttributeSet;
  8 import android.view.LayoutInflater;
  9 import android.view.MotionEvent;
 10 import android.view.View;
 11 import android.view.ViewGroup;
 12 import android.view.animation.DecelerateInterpolator;
 13 import android.widget.ImageView;
 14 import android.widget.LinearLayout;
 15 import android.widget.ListView;
 16 import android.widget.ProgressBar;
 17 
 18 import com.customview.R;
 19 
 20 public class DownRefreshListView extends ListView {
 21 
 22     public DownRefreshListView(Context context) {
 23         super(context);
 24         init();
 25     }
 26 
 27     public DownRefreshListView(Context context, AttributeSet attrs) {
 28         super(context, attrs);
 29         init();
 30     }
 31 
 32     public DownRefreshListView(Context context, AttributeSet attrs, int defStyle) {
 33         super(context, attrs, defStyle);
 34         init();
 35     }
 36 
 37     private void init() {
 38         // Listview的headerview(注意headerview的Layout布局要为LinearLayout)
 39         mHeaderView = (LinearLayout) LayoutInflater.from(getContext()).inflate(
 40                 R.layout.header_view, null);
 41         // 指示箭头
 42         mArrowImage = (ImageView) mHeaderView.findViewById(R.id.headerArrow);
 43         // 指示进度
 44         mProgressbar = (ProgressBar) mHeaderView
 45                 .findViewById(R.id.headerProgressbar);
 46         // 测量
 47         measureView(mHeaderView);
 48         // 获取测量后的headerview高度
 49         mHeaderHeight = mHeaderView.getMeasuredHeight();
 50         // 设置headerview的顶部缩进
 51         mHeaderView.setPadding(mHeaderView.getPaddingLeft(), -mHeaderHeight,
 52                 mHeaderView.getPaddingRight(), mHeaderView.getPaddingBottom());
 53         // 添加headerview
 54         this.addHeaderView(mHeaderView, null, false);
 55         this.setDivider(null);
 56         this.setVerticalScrollBarEnabled(false);
 57     }
 58 
 59     private void measureView(View v) {
 60         ViewGroup.LayoutParams p = v.getLayoutParams();
 61         if (p == null) {
 62             p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
 63                     ViewGroup.LayoutParams.WRAP_CONTENT);
 64 
 65         }
 66         int viewWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width);
 67         int viewHeightSpec = ViewGroup.getChildMeasureSpec(0, 0, p.height);
 68         v.measure(viewWidthSpec, viewHeightSpec);
 69     }
 70 
 71     @Override
 72     public boolean onTouchEvent(MotionEvent ev) {
 73         // 正在refresh data时,直接返回
 74         if (mState == State.REFRESH_LOADING) {
 75             return super.onTouchEvent(ev);
 76         }
 77         int action = ev.getAction();
 78         switch (action) {
 79         case MotionEvent.ACTION_DOWN:
 80             mLastY = ev.getY();
 81             // 当前可视位置为顶部
 82             if (mState == State.IDLE && getFirstVisiblePosition() == 0
 83                     && !hasStarted) {
 84                 mState = State.PULL_TO_REFRESH;
 85                 hasStarted = true;
 86                 return true;
 87             }
 88             // 允许动画播放时,用户点击,终止动画,同时继续下拉动作
 89             if ((mState == State.PULL_TO_REFRESH || mState == State.ENOUGH_TO_REFRESH)
 90                     && hasStarted && isAnimated) {
 91                 isCancelManually = true;
 92                 // 调用cancel方法,亦会调用animator listener的onAnimationEnd方法
 93                 mValueAnimator.cancel();
 94                 return true;
 95             }
 96             break;
 97         case MotionEvent.ACTION_MOVE:
 98             mCurrentY = ev.getY();
 99             float deltaY = mCurrentY - mLastY;
100             mLastY = mCurrentY;
101             // 已经开始,且滑动距离大于认定最小滑动距离
102             if (hasStarted && Math.abs(deltaY) > mCustomScaledTouchSlop) {
103                 mScrollStated = true;
104             }
105             if (mScrollStated && mState != State.IDLE
106                     && mState != State.REFRESH_LOADING) {
107                 // 向下滑动
108                 if (deltaY > 0) {
109                     // 设置headerview的顶部缩进
110                     int paddingTop = mHeaderView.getPaddingTop()
111                             + (int) (deltaY / 3);
112                     mHeaderView.setPadding(0, paddingTop, 0, 0);
113                     // headerview完全可见时,当前状态更动为释放刷新
114                     if (paddingTop >= 0) {
115                         mState = State.ENOUGH_TO_REFRESH;
116                     } else {
117                         mState = State.PULL_TO_REFRESH;
118                     }
119                     // 大于二分之一可见时,转动箭头
120                     if (paddingTop > -mHeaderHeight * 0.5f) {
121                         float degree = (paddingTop + mHeaderHeight * 0.5f)
122                                 / (mHeaderHeight * 0.5f) * 180;
123                         mArrowImage.setRotation(Math.min(degree, 180));
124                     }
125                 } else {
126                     // 向上滑动
127                     int paddingTop = mHeaderView.getPaddingTop()
128                             + (int) (deltaY / 3);
129                     // 当前headerview完全可见,优先减少headerview缩进距离
130                     if (paddingTop > 0) {
131                         mState = State.ENOUGH_TO_REFRESH;
132                     } else if (paddingTop > -mHeaderHeight) {
133                         // 当前headerview不完全可见,箭头转动角度回滚
134                         float degree = (paddingTop + mHeaderHeight * 0.5f)
135                                 / (mHeaderHeight * 0.5f) * 180;
136                         mArrowImage.setRotation(Math.max(0, degree));
137                         // 当前headerview不完全可见,设置当前状态为下拉刷新状态
138                         mState = State.PULL_TO_REFRESH;
139                     } else {
140                         // 当前headerview不可见
141                         mHeaderView.setPadding(0, -mHeaderHeight, 0, 0);
142                         mState = State.IDLE;
143                         return super.onTouchEvent(ev);
144                     }
145                     mHeaderView.setPadding(0, paddingTop, 0, 0);
146                 }
147                 return true;
148             }
149             break;
150         case MotionEvent.ACTION_UP:
151             if (mState == State.PULL_TO_REFRESH) {
152                 // 下拉刷新状态下,手指弹起
153                 smoothAnimate(300, mHeaderView.getPaddingTop(), -mHeaderHeight);
154                 if (mScrollStated) {
155                     mScrollStated = false;
156                     return true;
157                 }
158             } else if (mState == State.ENOUGH_TO_REFRESH) {
159                 // 释放刷新状态下,手指弹起
160                 smoothAnimate(300, mHeaderView.getPaddingTop(), 0);
161                 return true;
162             } else {
163                 mState = State.IDLE;
164                 hasStarted = false;
165                 mScrollStated = false;
166             }
167             break;
168         }
169         return super.onTouchEvent(ev);
170     }
171 
172     /** 平滑动画,使headerview的顶部缩进回到指定状态. */
173     private void smoothAnimate(int duration, int startValue, int endValue) {
174         mValueAnimator = ValueAnimator.ofObject(new TypeEvaluator<Integer>() {
175 
176             @Override
177             public Integer evaluate(float fraction, Integer startValue,
178                     Integer endValue) {
179                 return (int) (startValue + fraction * (endValue - startValue));
180             }
181         }, startValue, endValue);
182         mValueAnimator.addUpdateListener(new AnimatorUpdateListener() {
183 
184             @Override
185             public void onAnimationUpdate(ValueAnimator animation) {
186                 int middleValue = (Integer) animation.getAnimatedValue();
187                 // headerview顶部缩进回滚
188                 mHeaderView.setPadding(0, middleValue, 0, 0);
189                 // headerview指示箭头转动角度回滚
190                 float degree = (mHeaderView.getPaddingTop() + 0.5f * mHeaderHeight)
191                         / (mHeaderHeight * 0.5f) * 180;
192                 if (degree > 180) {
193                     degree = 180;
194                 }
195                 mArrowImage.setRotation(Math.max(0, degree));
196             }
197         });
198         // 动画监听
199         mValueAnimator.addListener(new AnimatorListener() {
200 
201             @Override
202             public void onAnimationStart(Animator animation) {
203                 isAnimated = true;
204             }
205 
206             @Override
207             public void onAnimationRepeat(Animator animation) {
208             }
209 
210             @Override
211             public void onAnimationEnd(Animator animation) {
212                 isAnimated = false;
213                 // 手动终止动画时,不改变当前状态,允许继续下拉
214                 if (isCancelManually) {
215                     isCancelManually = false;
216                     return;
217                 }
218                 if (isRefreshComplete) {
219                     isRefreshComplete = false;
220                     mProgressbar.setVisibility(View.GONE);
221                     mArrowImage.setVisibility(View.VISIBLE);
222                     mArrowImage.setRotation(0);
223                 }
224                 if (mState == State.PULL_TO_REFRESH) {
225                     mState = State.IDLE;
226                     hasStarted = false;
227                 }
228                 if (mState == State.ENOUGH_TO_REFRESH) {
229                     mState = State.REFRESH_LOADING;
230                     mArrowImage.setVisibility(View.GONE);
231                     mProgressbar.setVisibility(View.VISIBLE);
232                     hasStarted = false;
233                     mScrollStated = false;
234                     if (mRefreshExecutor != null) {
235                         mRefreshExecutor.refreshData();
236                     }
237                 }
238             }
239 
240             @Override
241             public void onAnimationCancel(Animator animation) {
242                 isAnimated = false;
243             }
244         });
245         mValueAnimator.setDuration(duration);
246         mValueAnimator.setInterpolator(new DecelerateInterpolator());
247         mValueAnimator.start();
248     }
249 
250     /** 刷新完成后,由刷新任务执行者,调用刷新完成 */
251     public void refreshComplete() {
252         mState = State.IDLE;
253         isRefreshComplete = true;
254         smoothAnimate(500, mHeaderView.getPaddingTop(), -mHeaderHeight);
255     }
256 
257     public void setRefreshExecutor(RefreshExecutor executor) {
258         mRefreshExecutor = executor;
259     }
260 
261     /** 刷新数据回调接口. */
262     public interface RefreshExecutor {
263         public void refreshData();
264     }
265 
266     public enum State {
267         /** 空闲状态. */
268         IDLE,
269         /** 下拉刷新状态. */
270         PULL_TO_REFRESH,
271         /** 释放可刷新状态. */
272         ENOUGH_TO_REFRESH,
273         /** 正在刷新状态. */
274         REFRESH_LOADING
275     }
276 
277     private LinearLayout mHeaderView;
278     private ImageView mArrowImage;
279     private ProgressBar mProgressbar;
280     private int mHeaderHeight;
281     /** 当前状态. */
282     private State mState = State.IDLE;
283     /** 是否开始下拉. */
284     private boolean hasStarted = false;
285     /** 是否开始滚动. */
286     private boolean mScrollStated = false;
287     /** 下拉刷新回调接口. */
288     private RefreshExecutor mRefreshExecutor;
289     /** 是否手动停止动画播放. */
290     private boolean isRefreshComplete = false;
291 
292     /** 属性动画. */
293     private ValueAnimator mValueAnimator;
294     /** 动画是否已经播放. */
295     private boolean isAnimated = false;
296     /** 是否手动停止动画播放. */
297     private boolean isCancelManually = false;
298 
299     /** 自定义最小点击滚动距离. */
300     private final int mCustomScaledTouchSlop = 6;
301     /** 上一次Y坐标. */
302     private float mLastY;
303     /** 当前Y坐标. */
304     private float mCurrentY;
305 }
View Code

 header_view.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="wrap_content"
 5     android:orientation="vertical" >
 6 
 7     <ProgressBar
 8         android:id="@+id/headerProgressbar"
 9         android:layout_width="30dp"
10         android:layout_height="30dp"
11         android:layout_gravity="center"
12         android:layout_marginBottom="3dp"
13         android:layout_marginTop="3dp"
14         android:indeterminateBehavior="repeat"
15         android:indeterminateDrawable="@drawable/custom_progress_drawable"
16         android:indeterminateDuration="1000"
17         android:visibility="gone" />
18 
19     <ImageView
20         android:id="@+id/headerArrow"
21         android:layout_width="30dp"
22         android:layout_height="30dp"
23         android:layout_gravity="center"
24         android:layout_marginBottom="3dp"
25         android:layout_marginTop="3dp"
26         android:contentDescription="@null"
27         android:src="@drawable/down_arrow" >
28     </ImageView>
29 
30 
31 </LinearLayout>
View Code

ProgressBar样式及背景

1 <?xml version="1.0" encoding="utf-8"?>
2 <rotate xmlns:android="http://schemas.android.com/apk/res/android"
3     android:drawable="@drawable/progress_refresh"
4     android:fromDegrees="0"
5     android:pivotX="50%"
6     android:pivotY="50%"
7     android:toDegrees="360" >
8 </rotate>
View Code

 

用到的资源:

down_arrow.png

progress_refresh.png

posted @ 2014-06-10 17:10  ProfessionalButcher  阅读(420)  评论(0编辑  收藏  举报