今天遇到一个下拉刷新的需求,但是和以往不同的是,不是顶部刷新,而是先有普通头部,然后下拉刷新样式头部,要求下拉刷新时第一头部不变,为实现此效果,特总结整理下相关知识点。
1.一个完整的过程:原始-下拉-释放-刷新-原始
2.移动时:下拉-释放、下拉-原始;释放-下拉、释放-原始(向上推);释放-刷新(弹起);
3.手势弹起时:原始、下拉、释放、刷新(不用考虑)
4.是么时候可以执行下拉刷新?
通过OnScrollListener监听ListView滑动到了顶部,即firstVisibleItem=0时;当然onScrollStateChanged可以判断是否显示加载更多,这里就不讨论了。
1 @Override2 public void onScrollStateChanged(AbsListView view, int scrollState) {3 4 }5 @Override6 public void onScroll(AbsListView view, int firstVisibleItem,7 int visibleItemCount, int totalItemCount) {8 mFirstItemIndex = firstVisibleItem;9 }
5.需要手动计算headView的尺寸,因为宽度是充满屏幕,可以直接获取,高度不确定,所以需要手动去计算
1 private void measureView(View child) { 2 android.view.ViewGroup.LayoutParams params = child.getLayoutParams(); 3 System.out.println("params = " + params); 4 if(params == null) { 5 params = new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 6 } 7 System.out.println("lpWidth = " + params.width); 8 int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0+0, params.width); 9 System.out.println("childWidthSpec = " + childWidthSpec);10 int lpHeight = params.height;11 System.out.println("lpHeight = " + lpHeight);12 int childHeightSpec;13 if(lpHeight > 0) {14 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);15 } else {16 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.UNSPECIFIED);17 }18 System.out.println("childHeightSpec = " + childHeightSpec);19 child.measure(childWidthSpec, childHeightSpec);20 }
6.在onTouchEvent事件里面对滑动做监听,改变当前状态
1 @Override 2 public boolean onTouchEvent(MotionEvent ev) { 3 if(mISRefreshable) { 4 switch (ev.getAction()) { 5 case MotionEvent.ACTION_DOWN: 6 if(mFirstItemIndex == 0 && !mIsRecored) { 7 mIsRecored = true; 8 mStartY = (int) ev.getY(); 9 }10 break;11 12 case MotionEvent.ACTION_UP:13 if(mState != REFRESHING) {14 if(mState == DONE) {15 16 }17 if(mState == PULL_TO_REFRESH) {18 mState = DONE;19 changeHeaderViewByState();20 }21 if(mState == RELEASE_TO_REFRESH) {22 mState = REFRESHING;23 changeHeaderViewByState();24 onRefresh();25 }26 }27 mIsRecored = false;28 break;29 30 case MotionEvent.ACTION_MOVE:31 int tempY = (int) ev.getY();32 if(!mIsRecored && mFirstItemIndex == 0) {33 mIsRecored = true;34 mStartY = tempY;35 }36 if(mState != REFRESHING && mIsRecored) {37 if(mState == RELEASE_TO_REFRESH) { //释放状态:向上推,38 setSelection(0);39 if((tempY - mStartY)/RADIO < mHeadContentHeight && (tempY - mStartY) > 0) {40 mState = PULL_TO_REFRESH;41 changeHeaderViewByState();42 } else if(tempY - mStartY <= 0) {43 mState = DONE;44 changeHeaderViewByState();45 }46 }47 48 if(mState == PULL_TO_REFRESH) {49 setSelection(0);50 if((tempY - mStartY)/RADIO >= mHeadContentHeight) {51 mState = RELEASE_TO_REFRESH;52 changeHeaderViewByState();53 }54 } else if(tempY - mStartY <= 0) {55 mState = DONE;56 changeHeaderViewByState();57 }58 if(mState == DONE) {59 if(tempY - mStartY > 0) {60 mState = PULL_TO_REFRESH;61 changeHeaderViewByState();62 }63 }64 65 if(mState == PULL_TO_REFRESH) {66 mHeadView.setPadding(0, -1 * mHeadContentHeight + (tempY - mStartY)/RADIO, 0, 0);67 }68 if(mState == RELEASE_TO_REFRESH) {69 mHeadView.setPadding(0, (tempY - mStartY)/RADIO - mHeadContentHeight, 0, 0);70 }71 }72 break;73 74 default:75 break;76 }77 }78 return super.onTouchEvent(ev);79 }
最后:贴上完整代码,可以直接拷贝使用哦!
布局文件: pull_to_refresh_header.xml
自定义类:PullToRefreshListView
1 package com.soufun.app.activity.adpater; 2 3 4 import android.content.Context; 5 import android.util.AttributeSet; 6 import android.view.LayoutInflater; 7 import android.view.MotionEvent; 8 import android.view.View; 9 import android.view.ViewGroup; 10 import android.view.animation.LinearInterpolator; 11 import android.view.animation.RotateAnimation; 12 import android.widget.AbsListView; 13 import android.widget.AbsListView.OnScrollListener; 14 import android.widget.ImageView; 15 import android.widget.LinearLayout; 16 import android.widget.ListView; 17 import android.widget.ProgressBar; 18 import android.widget.TextView; 19 20 import com.soufun.app.R; 21 22 import java.util.Date; 23 24 public class PullToRefreshListView extends ListView implements OnScrollListener { 25 26 //释放刷新 27 private final static int RELEASE_TO_REFRESH = 0; 28 //下拉刷新 29 private final static int PULL_TO_REFRESH = 1; 30 //正在刷新 31 private final static int REFRESHING = 2; 32 //刷新完成 33 private final static int DONE = 3; 34 35 // 实际的padding的距离与界面上偏移距离的比例 36 private final static int RADIO = 3; 37 38 private LayoutInflater mInflater; 39 private LinearLayout mHeadView; 40 private TextView mTipsTextView; 41 private TextView mLastUpdatedTextView; 42 private ImageView mArrowImageView; 43 private ProgressBar mProgressBar; 44 45 private RotateAnimation mAnimation; 46 private RotateAnimation mReverseAnimation; 47 48 // 用于保证startY的值在一个完整的touch事件中只被记录一次 49 private boolean mIsRecored; 50 51 private int mHeadContentHeight; 52 private int mStartY; 53 private int mFirstItemIndex; 54 private int mState; 55 56 private boolean mISRefreshable; 57 private OnRefreshListener mRefreshListener; 58 59 public PullToRefreshListView(Context context, AttributeSet attrs) { 60 super(context, attrs); 61 init(context); 62 } 63 64 private void init(Context context) { 65 66 mInflater = LayoutInflater.from(context); 67 68 mHeadView = (LinearLayout) mInflater.inflate(R.layout.pull_to_refresh_header, null); 69 70 mArrowImageView = (ImageView) mHeadView.findViewById(R.id.head_arrowImageView); 71 mProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressBar); 72 mTipsTextView = (TextView) mHeadView.findViewById(R.id.head_tipsTextView); 73 mLastUpdatedTextView = (TextView) mHeadView.findViewById(R.id.head_lastUpdatedTextView); 74 75 measureView(mHeadView); 76 77 mHeadContentHeight = mHeadView.getMeasuredHeight(); 78 System.out.println("mHeadContentHeight = " + mHeadContentHeight); 79 mHeadView.setPadding(0, -1 * mHeadContentHeight, 0, 0); 80 81 mHeadView.invalidate(); 82 83 addHeaderView(mHeadView, null, false); 84 85 setOnScrollListener(this); 86 87 mAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); 88 mAnimation.setInterpolator(new LinearInterpolator()); 89 mAnimation.setDuration(250); 90 mAnimation.setFillAfter(true); 91 92 mReverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); 93 mReverseAnimation.setInterpolator(new LinearInterpolator()); 94 mReverseAnimation.setDuration(250); 95 mReverseAnimation.setFillAfter(true); 96 97 mState = DONE; 98 mISRefreshable = false; 99 }100 101 private void measureView(View child) {102 android.view.ViewGroup.LayoutParams params = child.getLayoutParams();103 System.out.println("params = " + params);104 if(params == null) {105 params = new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);106 }107 System.out.println("lpWidth = " + params.width);108 int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0+0, params.width);109 System.out.println("childWidthSpec = " + childWidthSpec);110 int lpHeight = params.height;111 System.out.println("lpHeight = " + lpHeight);112 int childHeightSpec;113 if(lpHeight > 0) {114 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);115 } else {116 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.UNSPECIFIED);117 }118 System.out.println("childHeightSpec = " + childHeightSpec);119 child.measure(childWidthSpec, childHeightSpec);120 }121 122 @Override123 public void onScrollStateChanged(AbsListView view, int scrollState) {124 125 }126 @Override127 public void onScroll(AbsListView view, int firstVisibleItem,128 int visibleItemCount, int totalItemCount) {129 mFirstItemIndex = firstVisibleItem;130 }131 132 public interface OnRefreshListener {133 public void onRefresh();134 }135 136 private void onRefresh() {137 if(mRefreshListener != null) {138 mRefreshListener.onRefresh();139 }140 }141 142 public void onRefreshComplete() {143 mState = DONE;144 mLastUpdatedTextView.setText("最近更新:" + new Date().toLocaleString());145 changeHeaderViewByState();146 }147 148 public void setonRefreshListener(OnRefreshListener onRefreshListener) {149 this.mRefreshListener = onRefreshListener;150 mISRefreshable = true;151 }152 153 @Override154 public boolean onTouchEvent(MotionEvent ev) {155 if(mISRefreshable) {156 switch (ev.getAction()) {157 case MotionEvent.ACTION_DOWN:158 if(mFirstItemIndex == 0 && !mIsRecored) {159 mIsRecored = true;160 mStartY = (int) ev.getY();161 }162 break;163 164 case MotionEvent.ACTION_UP:165 if(mState != REFRESHING) {166 if(mState == DONE) {167 168 }169 if(mState == PULL_TO_REFRESH) {170 mState = DONE;171 changeHeaderViewByState();172 }173 if(mState == RELEASE_TO_REFRESH) {174 mState = REFRESHING;175 changeHeaderViewByState();176 onRefresh();177 }178 }179 mIsRecored = false;180 break;181 182 case MotionEvent.ACTION_MOVE:183 int tempY = (int) ev.getY();184 if(!mIsRecored && mFirstItemIndex == 0) {185 mIsRecored = true;186 mStartY = tempY;187 }188 if(mState != REFRESHING && mIsRecored) {189 if(mState == RELEASE_TO_REFRESH) { //释放状态:向上推,190 setSelection(0);191 if((tempY - mStartY)/RADIO < mHeadContentHeight && (tempY - mStartY) > 0) {192 mState = PULL_TO_REFRESH;193 changeHeaderViewByState();194 } else if(tempY - mStartY <= 0) {195 mState = DONE;196 changeHeaderViewByState();197 }198 }199 200 if(mState == PULL_TO_REFRESH) {201 setSelection(0);202 if((tempY - mStartY)/RADIO >= mHeadContentHeight) {203 mState = RELEASE_TO_REFRESH;204 changeHeaderViewByState();205 }206 } else if(tempY - mStartY <= 0) {207 mState = DONE;208 changeHeaderViewByState();209 }210 if(mState == DONE) {211 if(tempY - mStartY > 0) {212 mState = PULL_TO_REFRESH;213 changeHeaderViewByState();214 }215 }216 217 if(mState == PULL_TO_REFRESH) {218 mHeadView.setPadding(0, -1 * mHeadContentHeight + (tempY - mStartY)/RADIO, 0, 0);219 }220 if(mState == RELEASE_TO_REFRESH) {221 mHeadView.setPadding(0, (tempY - mStartY)/RADIO - mHeadContentHeight, 0, 0);222 }223 }224 break;225 226 default:227 break;228 }229 }230 return super.onTouchEvent(ev);231 }232 233 private void changeHeaderViewByState() {234 switch (mState) {235 case PULL_TO_REFRESH:236 mProgressBar.setVisibility(GONE);237 mTipsTextView.setVisibility(VISIBLE);238 mLastUpdatedTextView.setVisibility(VISIBLE);239 mArrowImageView.setVisibility(VISIBLE);240 mArrowImageView.clearAnimation();241 mArrowImageView.startAnimation(mReverseAnimation);242 mTipsTextView.setText("下拉可以刷新");243 break;244 245 case DONE:246 mHeadView.setPadding(0, -1 * mHeadContentHeight, 0, 0);247 mProgressBar.setVisibility(GONE);248 mArrowImageView.clearAnimation();249 mArrowImageView.setImageResource(R.drawable.arrow);250 mTipsTextView.setText("下拉可以刷新");251 mLastUpdatedTextView.setVisibility(VISIBLE);252 break;253 254 case REFRESHING:255 mHeadView.setPadding(0, 0, 0, 0);256 mProgressBar.setVisibility(VISIBLE);257 mArrowImageView.clearAnimation();258 mArrowImageView.setVisibility(GONE);259 mTipsTextView.setText("正在加载中");260 break;261 262 case RELEASE_TO_REFRESH:263 mArrowImageView.setVisibility(VISIBLE);264 mProgressBar.setVisibility(GONE);265 mTipsTextView.setVisibility(VISIBLE);266 mLastUpdatedTextView.setVisibility(VISIBLE);267 mArrowImageView.clearAnimation();268 mArrowImageView.startAnimation(mAnimation);269 mTipsTextView.setText("松开可以刷新");270 break;271 default:272 break;273 }274 }275 }