項目中需要下拉刷新的功能,但是這個View不是ListView這類的控件,需要ViewGroup實現(xiàn)這個功能,一開始網(wǎng)上大略找了一下,沒發(fā)現(xiàn)特別合適的,代碼也是沒怎么看懂,所以決定還是自己寫一個。
于是翻出XlistView的源碼看是一點一點看,再大致理解了XLisview源碼,終于決定自己動手啦
為了省事,headView還是用了XListView的HeadView,省了很多事:)
下拉刷新,下拉刷新,肯定是先實現(xiàn)下拉功能,最開始我是打算通過 extends ScrollView 來實現(xiàn),因為有現(xiàn)成的滾動效果嘛,可是實際因為兩個原因放棄了:
1、ScrollView下只能有一個子控件View ,雖然在 Scroll下添加一個ViewGroup,然后講headView動態(tài)添加進前面的ViewGroup,但是我還是比較習慣studio的可視化預覽,總覺得不直觀!
2、 ScrollView內(nèi)嵌ListView時會發(fā)生 沖突,還需要去重寫ListView。于是放棄換個思路!
關于上面的原因1:動態(tài)添加headView進ScrollView的中GroupView中,可以在重寫ScrollView的onViewAdded()方法,將初始化時解析的headView添加進子GroupView
@Override public void onViewAdded(View child) {   super.onViewAdded(child);   //因為headView要在最上面,最先想到的就是Vertical的LinearLayout   LinearLayout linearLayout = (LinearLayout) getChildAt(0);   linearLayout.addView(view, 0); } 	  換個思路,通過extends LinearLayout來實現(xiàn)吧!
	先做準備工作,我們需要一個HeaderView以及要獲取到HeaderView的高度,還有初始時Layout的高度
private void initView(Context context) {    mHeaderView = new SRefreshHeader(context);    mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);    setOrientation(VERTICAL);    addView(mHeaderView, 0);    getHeaderViewHeight();    getViewHeight();  } 	mHeaderView = new SRefreshHeader(context);
	通過構造方法實例化HeaderView
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
這是解析headerView內(nèi)容區(qū)域iew,等會兒要獲取這個view的高度,你肯定會問為啥不用上面的mHeaderView來獲取高度,點進構造方法里可以看到如下代碼
// 初始情況,設置下拉刷新view高度為0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); w(mContainer, lp);
	如果直接獲取mHeaderView的高度 那肯定是0
	getHeaderViewHeight();
	getViewHeight();
	分別是獲取HeaderView的高度和Layout的初始高度
/**   * 獲取headView高度   */   private void getHeaderViewHeight() {     ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();     vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {       @Override       public void onGlobalLayout() {         mHeaderViewHeight = mHeaderViewContent.getHeight();         mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);       }     });   }    /**   * 獲取SRefreshLayout當前實例的高度   */   private void getViewHeight() {     ViewTreeObserver thisView = getViewTreeObserver();     thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {       @Override       public void onGlobalLayout() {         SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();         SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);       }     });   } 	準備工作完成了,接下來就是要成下拉操作了
	到這里,肯定一下就想到了onTouchEvent()方法,是的!現(xiàn)在就開始在這里施工
	實現(xiàn)下拉一共 會經(jīng)歷三個過程
	ACTION_UP→ACTION_MOVE→ACTION_UP
	在ACTION_UP事件中,也就是手指按下的時候,我們需要做的只是記錄按下時候的坐標
switch (ev.getAction()) {       case MotionEvent.ACTION_DOWN:         //記錄起始高度         mLastY = ev.getRawY();//記錄按下時的Y坐標         break; 然后就是ACTION_MOVE事件了,這里是最重要的,因為下拉時HeadView和Layout的高度變化都在這里進行
case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//按一定比例縮小移動距離 updateHeight(); break;
里面的updateHeaderViewHeight和updateHeight分別是改變HeaderView的高度和Layout的高度
 private void updateHeight() {     ViewGroup.LayoutParams lp = getLayoutParams();     //更新當前l(fā)ayout實例高度為headerView高度加上最初的layout高度     //如果不更新layout 會造成內(nèi)容高度壓縮 無法保持比例     lp.height = (mHeight + mHeaderView.getVisiableHeight());     setLayoutParams(lp);   }    private void updateHeaderViewHeight(float space) { //    if (space < 0) //      space = 0; //    int factHeight = (int) (space - mHeaderViewHeight);     if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {       //如果不處于刷新中同時如果高度       if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {         mHeaderView.setState(SRefreshHeader.STATE_NORMAL);       }       if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {         mHeaderView.setState(SRefreshHeader.STATE_READY);       }     }     mHeaderView.setVisiableHeight((int) space         + mHeaderView.getVisiableHeight());   } 	更新Header高度時,通過下拉的距離來判斷是否到達刷新的距離,上面代碼中我設定的是到達mHeaderView初始高度的兩倍,就進入“釋放刷新”的狀態(tài),如果沒有達到則保持“下拉刷新”的狀態(tài)
	HeaderView中的狀態(tài)一共設定了3個分別是
public final static int STATE_NORMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//釋放刷新 public final static int STATE_REFRESHING = 2;//刷新中
更新高度的方法headerView和layout都是相同的,就是原高度加上移動的距離重新賦給headerView或者layout
	mHeaderView.setVisiableHeight((int) space 
	               + mHeaderView.getVisiableHeight()); 
最后就是ACTION_UP事件了就是手指離開屏幕的時候,在這里我們需要根據(jù)headerView目前狀態(tài)來決定headerView的最終狀態(tài)!
case MotionEvent.ACTION_UP:         //松開時         //避免點擊事件觸發(fā)         if (!isRefreashing)           break;         //如果headView狀態(tài)處于READY狀態(tài) 則說明松開時應該進入REFRESHING狀態(tài)         if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {           mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);         }         //根據(jù)狀態(tài)重置SrefreshLayout當前實例和headView高度         resetHeadView(mHeaderView.getStatus());         reset(mHeaderView.getStatus());         mLastY = -1;//重置坐標         break; resetHeadView和reset分別是重置headerView高度和layout高度的方法
private void reset(int status) {     ViewGroup.LayoutParams lp = getLayoutParams();     switch (status) {       case SRefreshHeader.STATE_REFRESHING:         lp.height = mHeight + mHeaderViewHeight;         break;       case SRefreshHeader.STATE_NORMAL:         lp.height = mHeight;         break;     }     setLayoutParams(lp);   }    private void resetHeadView(int status) {     switch (status) {       case SRefreshHeader.STATE_REFRESHING:         mHeaderView.setVisiableHeight(mHeaderViewHeight);         break;       case SRefreshHeader.STATE_NORMAL:         mHeaderView.setVisiableHeight(0);         break;     }   } 實現(xiàn)方式也是一樣的。根據(jù)狀態(tài)來判斷,如果是處于刷新中,那headerView應該正常顯示,并且高度是初始的高度,如果處于NORMAL,也就是"下拉刷新"狀態(tài),那么說未觸發(fā)刷新,重置時,headerView應該被隱藏,也就是高度重置為0
到這里下拉刷新操作也基本完成了,還需要加一個回調(diào)接口進行通知
interface OnRefreshListener {     void onRefresh();   } case MotionEvent.ACTION_UP:         //松開時         //避免點擊事件觸發(fā)         if (!isRefreashing)           break;         //如果headView狀態(tài)處于READY狀態(tài) 則說明松開時應該進入REFRESHING狀態(tài)         if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {           mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);           if (mOnRefreshListener != null)             mOnRefreshListener.onRefresh();         }         //根據(jù)狀態(tài)重置SrefreshLayout當前實例和headView高度         resetHeadView(mHeaderView.getStatus());         reset(mHeaderView.getStatus());         mLastY = -1;//重置坐標         break; 	好,到這里就基本完成了,試試效果吧。咦,發(fā)現(xiàn)一個問題,嵌套ListView的時候為什么這個Layout不能執(zhí)行下拉刷新!仔細想想應該是事件分發(fā)的問題,還需要處理一下事件的攔截!
	關于事件攔截的處理,閱讀了鴻洋大神寫的viewgroup事件分發(fā)的博客和Android-Ultra-Pull-To-Refresh的部分源碼,從中找到了解決辦法:
@Override   public boolean onInterceptTouchEvent(MotionEvent ev) {     AbsListView absListView = null;     for (int n = 0; n < getChildCount(); n++) {       if (getChildAt(n) instanceof AbsListView) {         absListView = (ListView) getChildAt(n);         Logs.v("查找到listView");       }     }     if (absListView == null)       return super.onInterceptTouchEvent(ev);     switch (ev.getAction()) {       case MotionEvent.ACTION_DOWN:         mStartY = ev.getRawY();         break;       case MotionEvent.ACTION_MOVE:         float space = ev.getRawY() - mStartY;         Logs.v("space:" + space);         if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {           Logs.v("攔截成功");           return true;         } else {           Logs.v("不攔截");           return false;         }     }     return super.onInterceptTouchEvent(ev);   } 其中
	if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) 
	space即移動的距離 canScrollVertically()是判斷ListView能否在垂直方向上滾動,參數(shù)為負數(shù)時代表向上,為正數(shù)時代碼向下滾動,最后一個就是ListView第一個可見的item的postion
加上上面的事件攔截處理,一個可以滿足開頭提到的需求的Viewgroup也就完成了!
	
下面貼上Layout的源碼和HeaderView(直接使用的XlistView的HeaderView)的源碼
public class SRefreshLayout extends LinearLayout {   private SRefreshHeader mHeaderView;   private RelativeLayout mHeaderViewContent;   private boolean isRefreashing;   private float mLastY = -1;//按下的起始高度   private int mHeaderViewHeight;//headerView內(nèi)容高度   private int mHeight;//布局高度   private float mStartY;    interface OnRefreshListener {     void onRefresh();   }    public OnRefreshListener mOnRefreshListener;    public SRefreshLayout(Context context) {     super(context);     initView(context);   }    public SRefreshLayout(Context context, AttributeSet attrs) {     super(context, attrs);     initView(context);   }    public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {     super(context, attrs, defStyleAttr);     initView(context);   }    private void initView(Context context) {     mHeaderView = new SRefreshHeader(context);     mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content);     setOrientation(VERTICAL);     addView(mHeaderView, 0);     getHeaderViewHeight();     getViewHeight();   }    /**    * 獲取headView高度    */   private void getHeaderViewHeight() {     ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver();     vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {       @Override       public void onGlobalLayout() {         mHeaderViewHeight = mHeaderViewContent.getHeight();         mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this);       }     });   }    /**    * 獲取SRefreshLayout當前實例的高度    */   private void getViewHeight() {     ViewTreeObserver thisView = getViewTreeObserver();     thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {       @Override       public void onGlobalLayout() {         SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight();         SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);       }     });   }    @Override   public boolean onInterceptTouchEvent(MotionEvent ev) {     AbsListView absListView = null;     for (int n = 0; n < getChildCount(); n++) {       if (getChildAt(n) instanceof AbsListView) {         absListView = (ListView) getChildAt(n);         Logs.v("查找到listView");       }     }     if (absListView == null)       return super.onInterceptTouchEvent(ev);     switch (ev.getAction()) {       case MotionEvent.ACTION_DOWN:         mStartY = ev.getRawY();         break;       case MotionEvent.ACTION_MOVE:         float space = ev.getRawY() - mStartY;         Logs.v("space:" + space);         if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) {           Logs.v("攔截成功");           return true;         } else {           Logs.v("不攔截");           return false;         }     }     return super.onInterceptTouchEvent(ev);   }    @Override   public boolean onTouchEvent(MotionEvent ev) {     if (mLastY == -1)       mLastY = ev.getRawY();     switch (ev.getAction()) {       case MotionEvent.ACTION_DOWN:         //記錄起始高度         mLastY = ev.getRawY();//記錄按下時的Y坐標         break;       //手指離開屏幕時       case MotionEvent.ACTION_UP:         //松開時         //避免點擊事件觸發(fā)         if (!isRefreashing)           break;         //如果headView狀態(tài)處于READY狀態(tài) 則說明松開時應該進入REFRESHING狀態(tài)         if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) {           mHeaderView.setState(SRefreshHeader.STATE_REFRESHING);           if (mOnRefreshListener != null)             mOnRefreshListener.onRefresh();         }         //根據(jù)狀態(tài)重置SrefreshLayout當前實例和headView高度         resetHeadView(mHeaderView.getStatus());         reset(mHeaderView.getStatus());         mLastY = -1;//重置坐標         break;       case MotionEvent.ACTION_MOVE:         if (!isRefreashing)           isRefreashing = true;         final float deltaY = ev.getRawY() - mLastY;         mLastY = ev.getRawY();         updateHeaderViewHeight(deltaY / 1.8f);//按一定比例縮小移動距離         updateHeight();         break;     }     return super.onTouchEvent(ev);   }     private void reset(int status) {     ViewGroup.LayoutParams lp = getLayoutParams();     switch (status) {       case SRefreshHeader.STATE_REFRESHING:         lp.height = mHeight + mHeaderViewHeight;         break;       case SRefreshHeader.STATE_NORMAL:         lp.height = mHeight;         break;     }     setLayoutParams(lp);   }    private void resetHeadView(int status) {     switch (status) {       case SRefreshHeader.STATE_REFRESHING:         mHeaderView.setVisiableHeight(mHeaderViewHeight);         break;       case SRefreshHeader.STATE_NORMAL:         mHeaderView.setVisiableHeight(0);         break;     }   }    private void updateHeight() {     ViewGroup.LayoutParams lp = getLayoutParams();     //更新當前l(fā)ayout實例高度為headerView高度加上最初的layout高度     //如果不更新layout 會造成內(nèi)容高度壓縮 無法保持比例     lp.height = (mHeight + mHeaderView.getVisiableHeight());     setLayoutParams(lp);   }    private void updateHeaderViewHeight(float space) { //    if (space < 0) //      space = 0; //    int factHeight = (int) (space - mHeaderViewHeight);     if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) {       //如果不處于刷新中同時如果高度       if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) {         mHeaderView.setState(SRefreshHeader.STATE_NORMAL);       }       if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) {         mHeaderView.setState(SRefreshHeader.STATE_READY);       }     }     mHeaderView.setVisiableHeight((int) space         + mHeaderView.getVisiableHeight());   }     public void stopRefresh() {     if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) {       mHeaderView.setState(SRefreshHeader.STATE_NORMAL);       resetHeadView(SRefreshHeader.STATE_NORMAL);       reset(SRefreshHeader.STATE_NORMAL);     }   }    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {     this.mOnRefreshListener = onRefreshListener;   } } 
public class SRefreshHeader extends LinearLayout {   private LinearLayout mContainer;   private int mState = STATE_NORMAL;    private Animation mRotateUpAnim;   private Animation mRotateDownAnim;    private final int ROTATE_ANIM_DURATION = 500;    public final static int STATE_NORMAL = 0;//下拉刷新   public final static int STATE_READY = 1;//釋放刷新   public final static int STATE_REFRESHING = 2;//刷新中   private ImageView mHeadArrowImage;   private TextView mHeadLastRefreashTimeTxt;   private TextView mHeadHintTxt;   private TextView mHeadLastRefreashTxt;   private ProgressBar mRefreshingProgress;    public SRefreshHeader(Context context) {     super(context);     initView(context);   }    /**    * @param context    * @param attrs    */   public SRefreshHeader(Context context, AttributeSet attrs) {     super(context, attrs);     initView(context);   }    private void initView(Context context) {     // 初始情況,設置下拉刷新view高度為0     LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);     mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null);     addView(mContainer, lp);     setGravity(Gravity.BOTTOM);      mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow);     mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time);     mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text);     mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt);     mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar);      mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,         Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,         0.5f);     mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);     mRotateUpAnim.setFillAfter(true);     mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,         Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,         0.5f);     mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);     mRotateDownAnim.setFillAfter(true);   }    public void setState(int state) {     if (state == mState) return;      if (state == STATE_REFRESHING) {  // 顯示進度       mHeadArrowImage.clearAnimation();       mHeadArrowImage.setVisibility(View.INVISIBLE);       mRefreshingProgress.setVisibility(View.VISIBLE);     } else {  // 顯示箭頭圖片       mHeadArrowImage.setVisibility(View.VISIBLE);       mRefreshingProgress.setVisibility(View.INVISIBLE);     }     switch (state) {       case STATE_NORMAL:         if (mState == STATE_READY) {           mHeadArrowImage.startAnimation(mRotateDownAnim);         }         if (mState == STATE_REFRESHING) {           mHeadArrowImage.clearAnimation();         }         mHeadHintTxt.setText("下拉刷新");         break;       case STATE_READY:         if (mState != STATE_READY) {           mHeadArrowImage.clearAnimation();           mHeadArrowImage.startAnimation(mRotateUpAnim);           mHeadHintTxt.setText("松開刷新");         }         break;       case STATE_REFRESHING:         mHeadHintTxt.setText("正在刷新");         break;       default:     }      mState = state;   }    public void setVisiableHeight(int height) {     if (height < 0)       height = 0;     LayoutParams lp = (LayoutParams) mContainer         .getLayoutParams();     lp.height = height;     mContainer.setLayoutParams(lp);   }     public int getStatus() {     return mState;   }    public int getVisiableHeight() {     return mContainer.getHeight();   } } 最后是布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom"> <RelativeLayout android:id="@+id/slistview_header_content" android:layout_width="match_parent" android:layout_height="60dp"> <LinearLayout android:id="@+id/slistview_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/slistview_header_hint_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp"> <TextView android:id="@+id/slistview_header_last_refreash_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="上次刷新時間" android:textSize="12sp" /> <TextView android:id="@+id/slistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <ProgressBar android:id="@+id/slistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:visibility="invisible" /> <ImageView android:id="@+id/slistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/slistview_header_progressbar" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:src="@drawable/mmtlistview_arrow" /> </RelativeLayout> </LinearLayout>
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網(wǎng)。
新聞熱點
疑難解答
圖片精選