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.IDLE106 && 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() {175 176 @Override177 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 @Override185 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 @Override202 public void onAnimationStart(Animator animation) {203 isAnimated = true;204 }205 206 @Override207 public void onAnimationRepeat(Animator animation) {208 }209 210 @Override211 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 @Override241 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_LOADING275 }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
1 26 7 18 19 28 29 30 31
ProgressBar样式及背景
1 28
用到的资源:
down_arrow.png
progress_refresh.png