今天遇到一個下拉刷新的需求,但是和以往不同的是,不是頂部刷新,而是先有普通頭部,然后下拉刷新樣式頭部,要求下拉刷新時第一頭部不變,為實現此效果,特總結整理下相關知識點。
1.一個完整的過程:原始-下拉-釋放-刷新-原始
2.移動時:下拉-釋放、下拉-原始;釋放-下拉、釋放-原始(向上推);釋放-刷新(彈起);
3.手勢彈起時:原始、下拉、釋放、刷新(不用考慮)
4.是么時候可以執行下拉刷新?
通過OnScrollListener監聽ListView滑動到了頂部,即firstVisibleItem=0時;當然onScrollStateChanged可以判斷是否顯示加載更多,這里就不討論了。
1 @Override 2 public void onScrollStateChanged(AbsListView view, int scrollState) { 3 4 } 5 @Override 6 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
<?xml version="1.0" encoding="utf-8"?> <!-- ListView的頭部 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <!-- 內容 --> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- 箭頭頭像、進度條 --> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true"> <!-- 箭頭 --> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:id="@+id/head_arrowImageView" android:src="@drawable/arrow"/> <!-- 進度條 --> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleSmall" android:layout_gravity="center" android:visibility="gone" android:id="@+id/head_progressBar"/> </FrameLayout> <!-- 提示、最近更新 --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:orientation="vertical" android:gravity="center_horizontal"> <!-- 提示 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:id="@+id/head_tipsTextView" android:textSize="@dimen/head_tips_size" android:text="@string/head_tips_text"/> <!-- 最近更新 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/head_lastUpdatedTextView" android:textColor="@color/gold" android:text="@string/lash_updated_text" android:textSize="@dimen/last_updated_size"/> </LinearLayout> </RelativeLayout> </LinearLayout>
自定義類: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 @Override 123 public void onScrollStateChanged(AbsListView view, int scrollState) { 124 125 } 126 @Override 127 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 @Override 154 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 }