Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 對XListView 原理的理解

對XListView 原理的理解

編輯:關於Android編程

一、從用戶操作角度分析源碼的組成

XListView是一個很不錯的實現了下拉及上拉刷新的listview控件,雖然已經停止維護了,但其基本功能還是被不少app在使用的。

既然要實現上拉及下拉刷新,就以下拉為例來討論一下:

首先,下拉是用戶的一個動作,用戶按住屏幕後手指下移一定距離後再抬起手指,這是listview頂端出現額外的提示內容,當移動距離達到一定條件,就允許刷新動作。同時,listview自動上移回到頂端。

考慮到這些內容,就可以想到大致的實現方法了。

1、 布局文件要分三部分(1)下拉展示的headerview (2)上拉展示的footerview (3)正文內容listview

2、 用戶上拉、下拉動作過程中view的變化通過onTouchEvent()實現,因為這時用戶有手指觸摸屏幕的動作

3、 用戶抬起手指後,headerview或footerview的回彈動作,通過scroller來實現。(這需要對scroller有一定的了解)

 

二、具體的細節實現

1、headerview的源碼

 

public class XListViewHeader extends LinearLayout {
   private LinearLayout mContainer;
   private ImageView mArrowImageView;
   private ProgressBar mProgressBar;
   private TextView mHintTextView;
   private int mState = STATE_NORMAL;

   private Animation mRotateUpAnim;
   private Animation mRotateDownAnim;
   
   private final int ROTATE_ANIM_DURATION = 180;
   
   public final static int STATE_NORMAL = 0;
   public final static int STATE_READY = 1;
   public final static int STATE_REFRESHING = 2;

   public XListViewHeader(Context context) {
      super(context);
      initView(context);
   }

   /**
    * @param context
    * @param attrs
    */
   public XListViewHeader(Context context, AttributeSet attrs) {
      super(context, attrs);
      initView(context);
   }

   private void initView(Context context) {
      // 初始情況,設置下拉刷新view高度為0
      LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
            LayoutParams.FILL_PARENT, 0);
      mContainer = (LinearLayout) LayoutInflater.from(context).inflate(
            R.layout.xlistview_header, null);
      addView(mContainer, lp);
      setGravity(Gravity.BOTTOM);

      mArrowImageView = (ImageView)findViewById(R.id.xlistview_header_arrow);
      mHintTextView = (TextView)findViewById(R.id.xlistview_header_hint_textview);
      mProgressBar = (ProgressBar)findViewById(R.id.xlistview_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);
   }

   /**
    * 更改Headerview的狀態
    * @param state
     */
   public void setState(int state) {
      if (state == mState) return ;
      
      if (state == STATE_REFRESHING) {   // 顯示進度
         mArrowImageView.clearAnimation();
         mArrowImageView.setVisibility(View.INVISIBLE);
         mProgressBar.setVisibility(View.VISIBLE);
      } else {   // 顯示箭頭圖片
         mArrowImageView.setVisibility(View.VISIBLE);
         mProgressBar.setVisibility(View.INVISIBLE);
      }
      
      switch(state){
      case STATE_NORMAL:
         if (mState == STATE_READY) {
            mArrowImageView.startAnimation(mRotateDownAnim);
         }
         if (mState == STATE_REFRESHING) {
            mArrowImageView.clearAnimation();
         }
         mHintTextView.setText(R.string.xlistview_header_hint_normal);
         break;
      case STATE_READY:
         if (mState != STATE_READY) {
            mArrowImageView.clearAnimation();
            mArrowImageView.startAnimation(mRotateUpAnim);
            mHintTextView.setText(R.string.xlistview_header_hint_ready);
         }
         break;
      case STATE_REFRESHING:
         mHintTextView.setText(R.string.xlistview_header_hint_loading);
         break;
         default:
      }
      
      mState = state;
   }

   /**
    * 更改headerview的高度
    * @param height
     */
   public void setVisiableHeight(int height) {
      if (height < 0)
         height = 0;
      LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContainer
            .getLayoutParams();
      lp.height = height;
      mContainer.setLayoutParams(lp);
   }

   /**
    * 獲取headerview的當前高度
    * @return
     */
   public int getVisiableHeight() {
      return mContainer.getLayoutParams().height;
   }

}

 

headerview的源碼很容易看懂,對應的布局文件是:

 




    

        

            

            

                

                
            
        

        

        
    

這裡要注意以下,這個布局的對齊方式是bottom並且初始化其高度為0.

 

footerview與headerview很相似,只是其顯示不是通過更改高度,而是更改margin來實現的。

 

2、整個XListView的布局形成的代碼

 

/**
 *  初始化上下拉刷新時顯示的header和footer  view  和   scroller
 * @param context
    */
private void initWithContext(Context context) {
   mScroller = new Scroller(context, new DecelerateInterpolator());
   // XListView need the scroll event, and it will dispatch the event to
   // user's listener (as a proxy).
   super.setOnScrollListener(this);

   // init header view
   mHeaderView = new XListViewHeader(context);
   mHeaderViewContent = (RelativeLayout) mHeaderView
         .findViewById(R.id.xlistview_header_content);
   mHeaderTimeView = (TextView) mHeaderView
         .findViewById(R.id.xlistview_header_time);
   addHeaderView(mHeaderView);

   // init footer view
   mFooterView = new XListViewFooter(context);

   // init header height
   mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(
         new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
               mHeaderViewHeight = mHeaderViewContent.getHeight();
               getViewTreeObserver()
                     .removeGlobalOnLayoutListener(this);
            }
         });
}

@Override
public void setAdapter(ListAdapter adapter) {
   // make sure XListViewFooter is the last footer view, and only add once.
   if (mIsFooterReady == false) {
      mIsFooterReady = true;
      addFooterView(mFooterView);
   }
   super.setAdapter(adapter);
}
listveiw本來就有在頭部和尾部添加item的方法addHeaderview和addFooterview。

 

可見,headerview是在初始化時通過addHeaderview添加進去的,而footerview是在setAdapter之前添加進去並保證了其唯一性。(這裡,不是很清楚為什麼。希望研究比較深入的各位給予指點)

同時,這裡也初始化了一個scroller類,在其構造函數中還傳入了一個插值器作為參數。這樣,如果headerview或footerview高度不為零了,用戶抬起手指時再調用各自高度的reset函數,使用startscroll來配置一下這個scroller,就可以實現回彈效果了。

 

作者這裡用的很巧妙

一般我們用scroller實現滑動,都是在computeScroll中調用scrollTo()這個方法並不斷的刷新view。如果這樣做能實現回彈效果,但是headerview和footerview的高度還沒有改變,而且這兩個view其實也是整個listview的兩個item。

所以作者是這樣實現的:

 

@Override
public void computeScroll() {
   if (mScroller.computeScrollOffset()) {
      if (mScrollBack == SCROLLBACK_HEADER) {
         mHeaderView.setVisiableHeight(mScroller.getCurrY());
      } else {
         mFooterView.setBottomMargin(mScroller.getCurrY());
      }
      postInvalidate();
      invokeOnScrolling();
   }
   super.computeScroll();
}

 

代碼中通過scroller.computeScrollOffset不斷的重新計算CurrX和CurrY,有在內部將其設置為這兩個item的高度。

 

我還沒有弄清楚的是:下面這個接口的作用

/**
 * you can listen ListView.OnScrollListener or this one. it will invoke
 * onXScrolling when header/footer scroll back.
 */
public interface OnXScrollListener extends OnScrollListener {
   public void onXScrolling(View view);
}

 

 

對XListView的細節理解可以參考下面的文章:http://blog.csdn.net/zhaokaiqiang1992/article/details/42392731

文章內容:

XListview是一個非常受歡迎的下拉刷新控件,但是已經停止維護了。之前寫過一篇XListview的使用介紹,用起來非常簡單,這兩天放假無聊,研究了下XListview的實現原理,學到了很多,今天分享給大家。

提前聲明,為了讓代碼更好的理解,我對代碼進行了部分刪減和重構,如果大家想看原版代碼,請去github自行下載。

Xlistview項目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分別是XListView主體、header、footer的實現。下面我們分開來介紹。

下面是修改之後的XListViewHeader代碼

publicclassXListViewHeaderextendsLinearLayout{

privatestaticfinalStringHINT_NORMAL="下拉刷新";
privatestaticfinalStringHINT_READY="松開刷新數據";
privatestaticfinalStringHINT_LOADING="正在加載...";

//正常狀態
publicfinalstaticintSTATE_NORMAL=0;
//准備刷新狀態,也就是箭頭方向發生改變之後的狀態
publicfinalstaticintSTATE_READY=1;
//刷新狀態,箭頭變成了progressBar
publicfinalstaticintSTATE_REFRESHING=2;
//布局容器,也就是根布局
privateLinearLayoutcontainer;
//箭頭圖片
privateImageViewmArrowImageView;
//刷新狀態顯示
privateProgressBarmProgressBar;
//說明文本
privateTextViewmHintTextView;
//記錄當前的狀態
privateintmState;
//用於改變箭頭的方向的動畫
privateAnimationmRotateUpAnim;
privateAnimationmRotateDownAnim;
//動畫持續時間
privatefinalintROTATE_ANIM_DURATION=180;

publicXListViewHeader(Contextcontext){
super(context);
initView(context);
}

publicXListViewHeader(Contextcontext,AttributeSetattrs){
super(context,attrs);
initView(context);
}

privatevoidinitView(Contextcontext){
mState=STATE_NORMAL;
//初始情況下,設置下拉刷新view高度為0
LinearLayout.LayoutParamslp=newLinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT,0);
container=(LinearLayout)LayoutInflater.from(context).inflate(
R.layout.xlistview_header,null);
addView(container,lp);
//初始化控件
mArrowImageView=(ImageView)findViewById(R.id.xlistview_header_arrow);
mHintTextView=(TextView)findViewById(R.id.xlistview_header_hint_textview);
mProgressBar=(ProgressBar)findViewById(R.id.xlistview_header_progressbar);
//初始化動畫
mRotateUpAnim=newRotateAnimation(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=newRotateAnimation(-180.0f,0.0f,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,
0.5f);
mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
mRotateDownAnim.setFillAfter(true);
}

//設置header的狀態
publicvoidsetState(intstate){
if(state==mState)
return;

//顯示進度
if(state==STATE_REFRESHING){
mArrowImageView.clearAnimation();
mArrowImageView.setVisibility(View.INVISIBLE);
mProgressBar.setVisibility(View.VISIBLE);
}else{
//顯示箭頭
mArrowImageView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.INVISIBLE);
}

switch(state){
caseSTATE_NORMAL:
if(mState==STATE_READY){
mArrowImageView.startAnimation(mRotateDownAnim);
}
if(mState==STATE_REFRESHING){
mArrowImageView.clearAnimation();
}
mHintTextView.setText(HINT_NORMAL);
break;
caseSTATE_READY:
if(mState!=STATE_READY){
mArrowImageView.clearAnimation();
mArrowImageView.startAnimation(mRotateUpAnim);
mHintTextView.setText(HINT_READY);
}
break;
caseSTATE_REFRESHING:
mHintTextView.setText(HINT_LOADING);
break;
}

mState=state;
}

publicvoidsetVisiableHeight(intheight){
if(height<0)
height=0;
LinearLayout.LayoutParamslp=(LinearLayout.LayoutParams)container
.getLayoutParams();
lp.height=height;
container.setLayoutParams(lp);
}

publicintgetVisiableHeight(){
returncontainer.getHeight();
}

publicvoidshow(){
container.setVisibility(View.VISIBLE);
}

publicvoidhide(){
container.setVisibility(View.INVISIBLE);
}

}
XListViewHeader繼承自linearLayout,用來實現下拉刷新時的界面展示,可以分為三種狀態:正常、准備刷新、正在加載。

 

在Linearlayout布局裡面,主要有指示箭頭、說明文本、圓形加載條三個控件。在構造函數中,調用了initView()進行控件的初始化操作。在添加布局文件的時候,指定高度為0,這是為了隱藏header,然後初始化動畫,是為了完成箭頭的旋轉動作。

setState()是設置header的狀態,因為header需要根據不同的狀態,完成控件隱藏、顯示、改變文字等操作,這個方法主要是在XListView裡面調用。除此之外,還有setVisiableHeight()和getVisiableHeight(),這兩個方法是為了設置和獲取Header中根布局文件的高度屬性,從而完成拉伸和收縮的效果,而show()和hide()則顯然就是完成顯示和隱藏的效果。

下面是Header的布局文件

android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom">

android:id="@+id/xlistview_header_content"
android:layout_width="match_parent"
android:layout_height="60dp"
tools:ignore="UselessParent">

android:id="@+id/xlistview_header_hint_textview"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:text="正在加載"
android:textColor="@android:color/black"
android:textSize="14sp"/>

android:id="@+id/xlistview_header_arrow"
android:layout_width="30dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/xlistview_header_hint_textview"
android:src="@drawable/xlistview_arrow"/>

android:id="@+id/xlistview_header_progressbar"
style="@style/progressbar_style"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/xlistview_header_hint_textview"
android:visibility="invisible"/>

說完了Header,我們再看看Footer。Footer是為了完成加載更多功能時候的界面展示,基本思路和Header是一樣的,下面是Footer的代碼

publicclassXListViewFooterextendsLinearLayout{

//正常狀態
publicfinalstaticintSTATE_NORMAL=0;
//准備狀態
publicfinalstaticintSTATE_READY=1;
//加載狀態
publicfinalstaticintSTATE_LOADING=2;

privateViewmContentView;
privateViewmProgressBar;
privateTextViewmHintView;

publicXListViewFooter(Contextcontext){
super(context);
initView(context);
}

publicXListViewFooter(Contextcontext,AttributeSetattrs){
super(context,attrs);
initView(context);
}

privatevoidinitView(Contextcontext){

LinearLayoutmoreView=(LinearLayout)LayoutInflater.from(context)
.inflate(R.layout.xlistview_footer,null);
addView(moreView);
moreView.setLayoutParams(newLinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));

mContentView=moreView.findViewById(R.id.xlistview_footer_content);
mProgressBar=moreView.findViewById(R.id.xlistview_footer_progressbar);
mHintView=(TextView)moreView
.findViewById(R.id.xlistview_footer_hint_textview);
}

/**
*設置當前的狀態
*
*@paramstate
*/
publicvoidsetState(intstate){

mProgressBar.setVisibility(View.INVISIBLE);
mHintView.setVisibility(View.INVISIBLE);

switch(state){
caseSTATE_READY:
mHintView.setVisibility(View.VISIBLE);
mHintView.setText(R.string.xlistview_footer_hint_ready);
break;

caseSTATE_NORMAL:
mHintView.setVisibility(View.VISIBLE);
mHintView.setText(R.string.xlistview_footer_hint_normal);
break;

caseSTATE_LOADING:
mProgressBar.setVisibility(View.VISIBLE);
break;

}

}

publicvoidsetBottomMargin(intheight){
if(height>0){

LinearLayout.LayoutParamslp=(LinearLayout.LayoutParams)mContentView
.getLayoutParams();
lp.bottomMargin=height;
mContentView.setLayoutParams(lp);
}
}

publicintgetBottomMargin(){
LinearLayout.LayoutParamslp=(LinearLayout.LayoutParams)mContentView
.getLayoutParams();
returnlp.bottomMargin;
}

publicvoidhide(){
LinearLayout.LayoutParamslp=(LinearLayout.LayoutParams)mContentView
.getLayoutParams();
lp.height=0;
mContentView.setLayoutParams(lp);
}

publicvoidshow(){
LinearLayout.LayoutParamslp=(LinearLayout.LayoutParams)mContentView
.getLayoutParams();
lp.height=LayoutParams.WRAP_CONTENT;
mContentView.setLayoutParams(lp);
}

}
從上面的代碼裡面,我們可以看出,footer和header的思路是一樣的,只不過,footer的拉伸和顯示效果不是通過高度來模擬的,而是通過設置BottomMargin來完成的。

android:layout_width="fill_parent"
android:layout_height="wrap_content">

android:id="@+id/xlistview_footer_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5dp"
tools:ignore="UselessParent">

android:id="@+id/xlistview_footer_progressbar"
style="@style/progressbar_style"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:visibility="invisible"/>

android:id="@+id/xlistview_footer_hint_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/xlistview_footer_hint_normal"
android:textColor="@android:color/black"
android:textSize="14sp"/>

在了解了Header和footer之後,我們就要介紹最核心的XListView的代碼實現了。

在介紹代碼實現之前,我先介紹一下XListView的實現原理。

首先,一旦使用XListView,Footer和Header就已經添加到我們的ListView上面了,XListView就是通過繼承ListView,然後處理了屏幕點擊事件和控制滑動實現效果的。所以,如果我們的Adapter中getCount()返回的值是20,那麼其實XListView裡面是有20+2個item的,這個數量即使我們關閉了XListView的刷新和加載功能,也是不會變化的。Header和Footer通過addHeaderView和addFooterView添加上去之後,如果想實現下拉刷新和上拉加載功能,那麼就必須有拉伸效果,所以就像上面的那樣,Header是通過設置height,Footer是通過設置BottomMargin來模擬拉伸效果。那麼回彈效果呢?僅僅通過設置高度或者是間隔是達不到模擬回彈效果的,因此,就需要用Scroller來實現模擬回彈效果。在說明原理之後,我們開始介紹XListView的核心實現原理。

再次提示,下面的代碼經過我重構了,只是為了看起來更好的理解。

publicclassXListViewextendsListView{

privatefinalstaticintSCROLLBACK_HEADER=0;
privatefinalstaticintSCROLLBACK_FOOTER=1;
//滑動時長
privatefinalstaticintSCROLL_DURATION=400;
//加載更多的距離
privatefinalstaticintPULL_LOAD_MORE_DELTA=100;
//滑動比例
privatefinalstaticfloatOFFSET_RADIO=2f;
//記錄按下點的y坐標
privatefloatlastY;
//用來回滾
privateScrollerscroller;
privateIXListViewListenermListViewListener;
privateXListViewHeaderheaderView;
privateRelativeLayoutheaderViewContent;
//header的高度
privateintheaderHeight;
//是否能夠刷新
privatebooleanenableRefresh=true;
//是否正在刷新
privatebooleanisRefreashing=false;
//footer
privateXListViewFooterfooterView;
//是否可以加載更多
privatebooleanenableLoadMore;
//是否正在加載
privatebooleanisLoadingMore;
//是否footer准備狀態
privatebooleanisFooterAdd=false;
//totallistitems,usedtodetectisatthebottomoflistview.
privateinttotalItemCount;
//記錄是從header還是footer返回
privateintmScrollBack;

privatestaticfinalStringTAG="XListView";

publicXListView(Contextcontext){
super(context);
initView(context);
}

publicXListView(Contextcontext,AttributeSetattrs){
super(context,attrs);
initView(context);
}

publicXListView(Contextcontext,AttributeSetattrs,intdefStyle){
super(context,attrs,defStyle);
initView(context);
}

privatevoidinitView(Contextcontext){

scroller=newScroller(context,newDecelerateInterpolator());

headerView=newXListViewHeader(context);
footerView=newXListViewFooter(context);

headerViewContent=(RelativeLayout)headerView
.findViewById(R.id.xlistview_header_content);
headerView.getViewTreeObserver().addOnGlobalLayoutListener(
newOnGlobalLayoutListener(){
@SuppressWarnings("deprecation")
@Override
publicvoidonGlobalLayout(){
headerHeight=headerViewContent.getHeight();
getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
});
addHeaderView(headerView);

}

@Override
publicvoidsetAdapter(ListAdapteradapter){
//確保footer最後添加並且只添加一次
if(isFooterAdd==false){
isFooterAdd=true;
addFooterView(footerView);
}
super.setAdapter(adapter);

}

@Override
publicbooleanonTouchEvent(MotionEventev){

totalItemCount=getAdapter().getCount();
switch(ev.getAction()){
caseMotionEvent.ACTION_DOWN:
//記錄按下的坐標
lastY=ev.getRawY();
break;
caseMotionEvent.ACTION_MOVE:
//計算移動距離
floatdeltaY=ev.getRawY()-lastY;
lastY=ev.getRawY();
//是第一項並且標題已經顯示或者是在下拉
if(getFirstVisiblePosition()==0
&&(headerView.getVisiableHeight()>0||deltaY>0)){
updateHeaderHeight(deltaY/OFFSET_RADIO);
}elseif(getLastVisiblePosition()==totalItemCount-1
&&(footerView.getBottomMargin()>0||deltaY<0)){
updateFooterHeight(-deltaY/OFFSET_RADIO);
}
break;

caseMotionEvent.ACTION_UP:

if(getFirstVisiblePosition()==0){
if(enableRefresh
&&headerView.getVisiableHeight()>headerHeight){
isRefreashing=true;
headerView.setState(XListViewHeader.STATE_REFRESHING);
if(mListViewListener!=null){
mListViewListener.onRefresh();
}
}
resetHeaderHeight();
}elseif(getLastVisiblePosition()==totalItemCount-1){
if(enableLoadMore
&&footerView.getBottomMargin()>PULL_LOAD_MORE_DELTA){
startLoadMore();
}
resetFooterHeight();
}
break;
}
returnsuper.onTouchEvent(ev);
}

@Override
publicvoidcomputeScroll(){

//松手之後調用
if(scroller.computeScrollOffset()){

if(mScrollBack==SCROLLBACK_HEADER){
headerView.setVisiableHeight(scroller.getCurrY());
}else{
footerView.setBottomMargin(scroller.getCurrY());
}
postInvalidate();
}
super.computeScroll();

}

publicvoidsetPullRefreshEnable(booleanenable){
enableRefresh=enable;

if(!enableRefresh){
headerView.hide();
}else{
headerView.show();
}
}

publicvoidsetPullLoadEnable(booleanenable){
enableLoadMore=enable;
if(!enableLoadMore){
footerView.hide();
footerView.setOnClickListener(null);
}else{
isLoadingMore=false;
footerView.show();
footerView.setState(XListViewFooter.STATE_NORMAL);
footerView.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
startLoadMore();
}
});
}
}

publicvoidstopRefresh(){
if(isRefreashing==true){
isRefreashing=false;
resetHeaderHeight();
}
}

publicvoidstopLoadMore(){
if(isLoadingMore==true){
isLoadingMore=false;
footerView.setState(XListViewFooter.STATE_NORMAL);
}
}

privatevoidupdateHeaderHeight(floatdelta){
headerView.setVisiableHeight((int)delta
+headerView.getVisiableHeight());
//未處於刷新狀態,更新箭頭
if(enableRefresh&&!isRefreashing){
if(headerView.getVisiableHeight()>headerHeight){
headerView.setState(XListViewHeader.STATE_READY);
}else{
headerView.setState(XListViewHeader.STATE_NORMAL);
}
}

}

privatevoidresetHeaderHeight(){
//當前的可見高度
intheight=headerView.getVisiableHeight();
//如果正在刷新並且高度沒有完全展示
if((isRefreashing&&height<=headerHeight)||(height==0)){
return;
}
//默認會回滾到header的位置
intfinalHeight=0;
//如果是正在刷新狀態,則回滾到header的高度
if(isRefreashing&&height>headerHeight){
finalHeight=headerHeight;
}
mScrollBack=SCROLLBACK_HEADER;
//回滾到指定位置
scroller.startScroll(0,height,0,finalHeight-height,
SCROLL_DURATION);
//觸發computeScroll
invalidate();
}

privatevoidupdateFooterHeight(floatdelta){
intheight=footerView.getBottomMargin()+(int)delta;
if(enableLoadMore&&!isLoadingMore){
if(height>PULL_LOAD_MORE_DELTA){
footerView.setState(XListViewFooter.STATE_READY);
}else{
footerView.setState(XListViewFooter.STATE_NORMAL);
}
}
footerView.setBottomMargin(height);

}

privatevoidresetFooterHeight(){
intbottomMargin=footerView.getBottomMargin();
if(bottomMargin>0){
mScrollBack=SCROLLBACK_FOOTER;
scroller.startScroll(0,bottomMargin,0,-bottomMargin,
SCROLL_DURATION);
invalidate();
}
}

privatevoidstartLoadMore(){
isLoadingMore=true;
footerView.setState(XListViewFooter.STATE_LOADING);
if(mListViewListener!=null){
mListViewListener.onLoadMore();
}
}

publicvoidsetXListViewListener(IXListViewListenerl){
mListViewListener=l;
}

publicinterfaceIXListViewListener{

publicvoidonRefresh();

publicvoidonLoadMore();
}
}

在三個構造函數中,都調用initView進行了header和footer的初始化,並且定義了一個Scroller,並傳入了一個減速的插值器,為了模仿回彈效果。在initView方法裡面,因為header可能還沒初始化完畢,所以通過GlobalLayoutlistener來獲取了header的高度,然後addHeaderView添加到了listview上面。

通過重寫setAdapter方法,保證Footer最後天假,並且只添加一次。

最重要的,要屬onTouchEvent了。在方法開始之前,通過getAdapter().getCount()獲取到了item的總數,便於計算位置。這個操作在源代碼中是通過scrollerListener完成的,因為ScrollerListener在這裡沒大有用,所以我直接去掉了,然後把位置改到了這裡。如果在setAdapter裡面獲取的話,只能獲取到沒有header和footer的item數量。

在ACTION_DOWN裡面,進行了lastY的初始化,lastY是為了判斷移動方向的,因為在ACTION_MOVE裡面,通過ev.getRawY()-lastY可以計算出手指的移動趨勢,如果>0,那麼就是向下滑動,反之向上。getRowY()是獲取元Y坐標,意思就是和Window和View坐標沒有關系的坐標,代表在屏幕上的絕對位置。然後在下面的代碼裡面,如果第一項可見並且header的可見高度>0或者是向下滑動,就說明用戶在向下拉動或者是向上拉動header,也就是指示箭頭顯示的時候的狀態,這時候調用了updateHeaderHeight,來更新header的高度,實現header可以跟隨手指動作上下移動。這裡有個OFFSET_RADIO,這個值是一個移動比例,就是說,你手指在Y方向上移動400px,如果比例是2,那麼屏幕上的控件移動就是400px/2=200px,可以通過這個值來控制用戶的滑動體驗。下面的關於footer的判斷與此類似,不再贅述。

當用戶移開手指之後,ACTION_UP方法就會被調用。在這裡面,只對可見位置是0和item總數-1的位置進行了處理,其實正好對應header和footer。如果位置是0,並且可以刷新,然後當前的header可見高度>原始高度的話,就說明用戶確實是要進行刷新操作,所以通過setState改變header的狀態,如果有監聽器的話,就調用onRefresh方法,然後調用resetHeaderHeight初始化header的狀態,因為footer的操作如出一轍,所以不再贅述。但是在footer中有一個PULL_LOAD_MORE_DELTA,這個值是加載更多觸發條件的臨界值,只有footer的間隔超過這個值之後,才能夠觸發加載更多的功能,因此我們可以修改這個值來改變用戶體驗。

說到現在,大家應該明白基本的原理了,其實XListView就是通過對用戶手勢的方向和距離的判斷,來動態的改變Header和Footer實現的功能,所以如果我們也有類似的需求,就可以參照這種思路進行自定義。

下面再說幾個比較重要的方法。

前面我們說道,在ACTION_MOVE裡面,會不斷的調用下面的updateXXXX方法,來動態的改變header和fooer的狀態,

privatevoidupdateHeaderHeight(floatdelta){
headerView.setVisiableHeight((int)delta
+headerView.getVisiableHeight());
//未處於刷新狀態,更新箭頭
if(enableRefresh&&!isRefreashing){
if(headerView.getVisiableHeight()>headerHeight){
headerView.setState(XListViewHeader.STATE_READY);
}else{
headerView.setState(XListViewHeader.STATE_NORMAL);
}
}

}

privatevoidupdateFooterHeight(floatdelta){
intheight=footerView.getBottomMargin()+(int)delta;
if(enableLoadMore&&!isLoadingMore){
if(height>PULL_LOAD_MORE_DELTA){
footerView.setState(XListViewFooter.STATE_READY);
}else{
footerView.setState(XListViewFooter.STATE_NORMAL);
}
}
footerView.setBottomMargin(height);

} 

在移開手指之後,會調用下面的resetXXX來初始化header和footer的狀態
privatevoidresetHeaderHeight(){
//當前的可見高度
intheight=headerView.getVisiableHeight();
//如果正在刷新並且高度沒有完全展示
if((isRefreashing&&height<=headerHeight)||(height==0)){
return;
}
//默認會回滾到header的位置
intfinalHeight=0;
//如果是正在刷新狀態,則回滾到header的高度
if(isRefreashing&&height>headerHeight){
finalHeight=headerHeight;
}
mScrollBack=SCROLLBACK_HEADER;
//回滾到指定位置
scroller.startScroll(0,height,0,finalHeight-height,
SCROLL_DURATION);
//觸發computeScroll
invalidate();
}

privatevoidresetFooterHeight(){
intbottomMargin=footerView.getBottomMargin();
if(bottomMargin>0){
mScrollBack=SCROLLBACK_FOOTER;
scroller.startScroll(0,bottomMargin,0,-bottomMargin,
SCROLL_DURATION);
invalidate();
}
} 
我們可以看到,滾動操作不是通過直接的設置高度來實現的,而是通過Scroller.startScroll()來實現的,通過調用此方法,computeScroll()就會被調用,然後在這個裡面,根據mScrollBack區分是哪一個滾動,然後再通過設置高度和間隔,就可以完成收縮的效果了。 至此,整個XListView的實現原理就完全的搞明白了,以後如果做滾動類的自定義控件,應該也有思路了。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved