麥洛原文:http://miloisbadboy.com/archives/55
由於這個文章比較長點,這裡簡單貼點代碼,詳細說明還是看原文吧
主要源碼
package com.miloisbadboy.view;import android.content.Context;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.animation.LinearInterpolator;import android.view.animation.RotateAnimation;import android.widget.AdapterView;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.ProgressBar;import android.widget.ScrollView;import android.widget.TextView;import com.miloisbadboy.R;public class PullToRefreshView extends LinearLayout {private static final String TAG = "PullToRefreshView";// refresh statesprivate static final int PULL_TO_REFRESH = 2;private static final int RELEASE_TO_REFRESH = 3;private static final int REFRESHING = 4;// pull stateprivate static final int PULL_UP_STATE = 0;private static final int PULL_DOWN_STATE = 1;/** * last y */private int mLastMotionY;/** * lock */private boolean mLock;/** * header view */private View mHeaderView;/** * footer view */private View mFooterView;/** * list or grid */private AdapterView<?> mAdapterView;/** * scrollview */private ScrollView mScrollView;/** * header view height */private int mHeaderViewHeight;/** * footer view height */private int mFooterViewHeight;/** * header view image */private ImageView mHeaderImageView;/** * footer view image */private ImageView mFooterImageView;/** * header tip text */private TextView mHeaderTextView;/** * footer tip text */private TextView mFooterTextView;/** * header refresh time */private TextView mHeaderUpdateTextView;/** * footer refresh time */// private TextView mFooterUpdateTextView;/** * header progress bar */private ProgressBar mHeaderProgressBar;/** * footer progress bar */private ProgressBar mFooterProgressBar;/** * layout inflater */private LayoutInflater mInflater;/** * header view current state */private int mHeaderState;/** * footer view current state */private int mFooterState;/** * pull state,pull up or pull down;PULL_UP_STATE or PULL_DOWN_STATE */private int mPullState;/** * 變為向下的箭頭,改變箭頭方向 */private RotateAnimation mFlipAnimation;/** * 變為逆向的箭頭,旋轉 */private RotateAnimation mReverseFlipAnimation;/** * footer refresh listener */private OnFooterRefreshListener mOnFooterRefreshListener;/** * footer refresh listener */private OnHeaderRefreshListener mOnHeaderRefreshListener;/** * last update time */private String mLastUpdateTime;public PullToRefreshView(Context context, AttributeSet attrs) {super(context, attrs);init();}public PullToRefreshView(Context context) {super(context);init();}/** * init * * @description * @param context */private void init() {// Load all of the animations we need in code rather than through XMLmFlipAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,RotateAnimation.RELATIVE_TO_SELF, 0.5f);mFlipAnimation.setInterpolator(new LinearInterpolator());mFlipAnimation.setDuration(250);mFlipAnimation.setFillAfter(true);mReverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF,0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);mReverseFlipAnimation.setInterpolator(new LinearInterpolator());mReverseFlipAnimation.setDuration(250);mReverseFlipAnimation.setFillAfter(true);mInflater = LayoutInflater.from(getContext());// header view 在此添加,保證是第一個添加到linearlayout的最上端addHeaderView();}private void addHeaderView() {// header viewmHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);mHeaderImageView = (ImageView) mHeaderView.findViewById(R.id.pull_to_refresh_image);mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);mHeaderUpdateTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_updated_at);mHeaderProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);// header layoutmeasureView(mHeaderView);mHeaderViewHeight = mHeaderView.getMeasuredHeight();LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mHeaderViewHeight);// 設定topMargin的值為負的header View高度,即將其隱藏在最上方params.topMargin = -(mHeaderViewHeight);// mHeaderView.setLayoutParams(params1);addView(mHeaderView, params);}private void addFooterView() {// footer viewmFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);mFooterImageView = (ImageView) mFooterView.findViewById(R.id.pull_to_load_image);mFooterTextView = (TextView) mFooterView.findViewById(R.id.pull_to_load_text);mFooterProgressBar = (ProgressBar) mFooterView.findViewById(R.id.pull_to_load_progress);// footer layoutmeasureView(mFooterView);mFooterViewHeight = mFooterView.getMeasuredHeight();LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mFooterViewHeight);//int top = getHeight();//params.topMargin =getHeight();//在這裡getHeight()==0,但在onInterceptTouchEvent()方法裡getHeight()已經有值了,不再是0;// getHeight()什麼時候會賦值,稍候再研究一下// 由於是線性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那麼footer view就會被添加到最後,並隱藏addView(mFooterView, params);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();// footer view 在此添加保證添加到linearlayout中的最後addFooterView();initContentAdapterView();}/** * init AdapterView like ListView,GridView and so on;or init ScrollView * */private void initContentAdapterView() {int count = getChildCount();if (count < 3) {throw new IllegalArgumentException("this layout must contain 3 child views,and AdapterView or ScrollView must in the second position!");}View view = null;for(int i=0;i<count-1;++i){view = getChildAt(i);if (view instanceof AdapterView<?>) {mAdapterView = (AdapterView<?>) view;}if (view instanceof ScrollView) {// finish latermScrollView = (ScrollView)view;}}if (mAdapterView == null && mScrollView==null) {throw new IllegalArgumentException("must contain a AdapterView or ScrollView in this layout!");}}private void measureView(View child) {ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);int lpHeight = p.height;int childHeightSpec;if (lpHeight > 0) {childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);} else {childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);}child.measure(childWidthSpec, childHeightSpec);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {int y = (int) e.getRawY();switch (e.getAction()) {case MotionEvent.ACTION_DOWN:// 首先攔截down事件,記錄y座標mLastMotionY = y;break;case MotionEvent.ACTION_MOVE:// deltaY > 0 是向下運動,< 0是向上運動int deltaY = y - mLastMotionY;if (isRefreshViewScroll(deltaY)) {return true;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:break;}return false;}/* * 如果在onInterceptTouchEvent()方法中沒有攔截(即onInterceptTouchEvent()方法中 return * false)則由PullToRefreshView 的子View來處理;否則由下面的方法來處理(即由PullToRefreshView自己來處理) */@Overridepublic boolean onTouchEvent(MotionEvent event) {if (mLock) {return true;}int y = (int) event.getRawY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// onInterceptTouchEvent已經記錄// mLastMotionY = y;break;case MotionEvent.ACTION_MOVE:int deltaY = y - mLastMotionY;if (mPullState == PULL_DOWN_STATE) {// PullToRefreshView執行下拉Log.i(TAG, " pull down!parent view move!");headerPrepareToRefresh(deltaY);// setHeaderPadding(-mHeaderViewHeight);} else if (mPullState == PULL_UP_STATE) {// PullToRefreshView執行上拉Log.i(TAG, "pull up!parent view move!");footerPrepareToRefresh(deltaY);}mLastMotionY = y;break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:int topMargin = getHeaderTopMargin();if (mPullState == PULL_DOWN_STATE) {if (topMargin >= 0) {// 開始重新整理headerRefreshing();} else {// 還沒有執行重新整理,重新隱藏setHeaderTopMargin(-mHeaderViewHeight);}} else if (mPullState == PULL_UP_STATE) {if(Math.abs(topMargin)>=mHeaderViewHeight+mFooterViewHeight){// 開始執行footer 重新整理footerRefreshing();} else {// 還沒有執行重新整理,重新隱藏setHeaderTopMargin(-mHeaderViewHeight);}}break;}return super.onTouchEvent(event);}/** * 是否應該到了父View,即PullToRefreshView滑動 * * @param deltaY * , deltaY > 0 是向下運動,< 0是向上運動 * @return */private boolean isRefreshViewScroll(int deltaY) {if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {return false;}//對於ListView 以及GridViewif (mAdapterView != null) {View child = mAdapterView.getChildAt(0);if (child == null) {// 如果mAdapterView中沒有資料,不攔截// 可以考慮返回truereturn false;}// 子view(ListView or GridView)滑動到最頂端if (deltaY > 0 && child.getTop() == 0) {mPullState = PULL_DOWN_STATE;return true;} else if (deltaY < 0) {View lastChild = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);if (lastChild == null) {// 如果mAdapterView中沒有資料,不攔截return false;}// 最後一個子view的Bottom小於父View的高度說明mAdapterView的資料沒有填滿父view,// 等於父View的高度說明mAdapterView已經滑動到最後if (lastChild.getBottom() <= getHeight()) {mPullState = PULL_UP_STATE;return true;}}}//對於ScrollViewif(mScrollView!=null){// 子scroll view滑動到最頂端View child = mScrollView.getChildAt(0);if (deltaY > 0 && mScrollView.getScrollY() == 0) {mPullState = PULL_DOWN_STATE;return true;} else if (deltaY < 0 && child.getMeasuredHeight()<=getHeight()+mScrollView.getScrollY()) {mPullState = PULL_UP_STATE;return true;}}return false;}/** * header 準備重新整理,手指移動過程,還沒有釋放 * * @param deltaY * ,手指滑動的距離 */private void headerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);// 當header view的topMargin>=0時,說明已經完全顯示出來了,修改header view 的提示狀態if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {mHeaderTextView.setText(R.string.pull_to_refresh_release_label);mHeaderUpdateTextView.setVisibility(View.VISIBLE);mHeaderImageView.clearAnimation();mHeaderImageView.startAnimation(mFlipAnimation);mHeaderState = RELEASE_TO_REFRESH;}}/** * footer 準備重新整理,手指移動過程,還沒有釋放 移動footer view高度同樣和移動header view * 高度是一樣,都是通過修改header view的topmargin的值來達到 * * @param deltaY * ,手指滑動的距離 */private void footerPrepareToRefresh(int deltaY) {int newTopMargin = changingHeaderViewTopMargin(deltaY);//如果header view topMargin 的絕對值大於或等於header + footer 的高度//說明footer view 完全顯示出來了,修改footer view 的提示狀態if(Math.abs(newTopMargin)>=(mHeaderViewHeight+mFooterViewHeight)&& mFooterState != RELEASE_TO_REFRESH){mFooterTextView.setText(R.string.pull_to_refresh_footer_release_label);mFooterImageView.clearAnimation();mFooterImageView.startAnimation(mFlipAnimation);mFooterState = RELEASE_TO_REFRESH;}}/** * 修改Header view top margin的值 * @description * @param deltaY * @return */private int changingHeaderViewTopMargin(int deltaY){LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();float newTopMargin = params.topMargin + deltaY * 0.3f;params.topMargin = (int) newTopMargin;mHeaderView.setLayoutParams(params);invalidate();return params.topMargin;}/** * header refreshing * */private void headerRefreshing() {mHeaderState = REFRESHING;setHeaderTopMargin(0);mHeaderImageView.setVisibility(View.GONE);mHeaderImageView.clearAnimation();mHeaderImageView.setImageDrawable(null);mHeaderProgressBar.setVisibility(View.VISIBLE);mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);if (mOnHeaderRefreshListener != null) {mOnHeaderRefreshListener.onHeaderRefresh(this);}}/** * footer refreshing * */private void footerRefreshing() {mFooterState = REFRESHING;int top = mHeaderViewHeight + mFooterViewHeight;setHeaderTopMargin(-top);mFooterImageView.setVisibility(View.GONE);mFooterImageView.clearAnimation();mFooterImageView.setImageDrawable(null);mFooterProgressBar.setVisibility(View.VISIBLE);mFooterTextView.setText(R.string.pull_to_refresh_footer_refreshing_label);if (mOnFooterRefreshListener != null) {mOnFooterRefreshListener.onFooterRefresh(this);}}/** * 設定header view 的topMargin的值 * * @description * @param topMargin * ,為0時,說明header view 剛好完全顯示出來; 為-mHeaderViewHeight時,說明完全隱藏了 */private void setHeaderTopMargin(int topMargin) {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();params.topMargin = topMargin;mHeaderView.setLayoutParams(params);invalidate();}/** * header view 完成更新後恢複初始狀態 * */public void onHeaderRefreshComplete() {setHeaderTopMargin(-mHeaderViewHeight);mHeaderImageView.setVisibility(View.VISIBLE);mHeaderImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow);mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);mHeaderProgressBar.setVisibility(View.GONE);// mHeaderUpdateTextView.setText("");mHeaderState = PULL_TO_REFRESH;}/** * Resets the list to a normal state after a refresh. * @param lastUpdated Last updated at. */ public void onHeaderRefreshComplete(CharSequence lastUpdated) { setLastUpdated(lastUpdated); onHeaderRefreshComplete(); }/** * footer view 完成更新後恢複初始狀態 */public void onFooterRefreshComplete() {setHeaderTopMargin(-mHeaderViewHeight);mFooterImageView.setVisibility(View.VISIBLE);mFooterImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow_up);mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);mFooterProgressBar.setVisibility(View.GONE);// mHeaderUpdateTextView.setText("");mFooterState = PULL_TO_REFRESH;}/** * Set a text to represent when the list was last updated. * @param lastUpdated Last updated at. */ public void setLastUpdated(CharSequence lastUpdated) { if (lastUpdated != null) { mHeaderUpdateTextView.setVisibility(View.VISIBLE); mHeaderUpdateTextView.setText(lastUpdated); } else { mHeaderUpdateTextView.setVisibility(View.GONE); } }/** * 擷取當前header view 的topMargin * * @description */private int getHeaderTopMargin() {LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();return params.topMargin;}/** * lock * */private void lock() {mLock = true;}/** * unlock * */private void unlock() {mLock = false;}/** * set headerRefreshListener * * @description * @param headerRefreshListener */public void setOnHeaderRefreshListener(OnHeaderRefreshListener headerRefreshListener) {mOnHeaderRefreshListener = headerRefreshListener;}public void setOnFooterRefreshListener(OnFooterRefreshListener footerRefreshListener) {mOnFooterRefreshListener = footerRefreshListener;}/** * Interface definition for a callback to be invoked when list/grid footer * view should be refreshed. */public interface OnFooterRefreshListener {public void onFooterRefresh(PullToRefreshView view);}/** * Interface definition for a callback to be invoked when list/grid header * view should be refreshed. */public interface OnHeaderRefreshListener {public void onHeaderRefresh(PullToRefreshView view);}}