ListView下拉刷新实现(类似陌陌的箭头转动)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
header_view.xml
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
ProgressBar样式及背景
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
用到的资源:
down_arrow.png
progress_refresh.png