Android下拉重新整理架構實現代碼執行個體_Android

來源:互聯網
上載者:User

前段時間項目中用到了下拉重新整理功能,之前在網上也找到過類似的demo,但這些demo的品質參差不齊,使用者體驗也不好,介面設計也不行。最張沒辦法,終於忍不了了,自己就寫了一個下拉重新整理的架構,這個架構是一個通用的架構,效果和設計感覺都還不錯,現在分享給各位看官。

一. 關於下拉重新整理

下拉重新整理這種使用者互動最早由twitter創始人洛倫•布裡切特(Loren Brichter)發明,有理論認為,下拉重新整理是一種適用於按照從新到舊的時間順序排列feeds的應用,在這種應用情境中看完舊的內容時,使用者會很自然地下拉尋找更新的內容,因此下拉重新整理就顯得非常合理。大家可以參考這篇文章:有趣的下拉重新整理,下面我貼出一個有趣的下拉重新整理的案例。

圖一、有趣的下拉重新整理案例(一)
圖二、有趣的下拉重新整理案例(二)

二. 實現原理

上面這些例子,外觀做得再好看,他的本質上都一樣,那就是一個下拉重新整理控制項通常由以下幾部分組成:

【1】Header

Header通常有下拉式箭頭,文字,進度條等元素,根據下拉的距離來改變它的狀態,從而顯示不同的樣式

【2】Content

這部分是內容地區,網上有很多例子都是直接在ListView裡面添加Header,但這就有局限性,因為好多情況下並不一定是用ListView來顯示資料。我們把要顯示內容的View放置在我們的一個容器中,如果你想實現一個用ListView顯示資料的下拉重新整理,你需要建立一個ListView旋轉到我的容器中。我們處理這個容器的事件(down, move, up),如果向下拉,則把整個布局向下滑動,從而把header顯示出來。

【3】Footer

Footer可以用來顯示向上拉的箭頭,自動載入更多的進度條等。

以上三部分總結的說來,就是如下圖所示的這種布局結構:

圖三,下拉重新整理的布局結構

關於上圖,需要說明幾點:

1、這個布局擴充於LinearLayout,垂直排列

2、從上到下的順序是:Header, Content, Footer

3、Content填充滿父控制項,通過設定top, bottom的padding來使Header和Footer不可見,也就是讓它超出螢幕外

4、下拉時,調用scrollTo方法來將整個布局向下滑動,從而把Header顯示出來,上拉正好與下拉相反。

5、衍生類別需要實現的是:將Content View填充到父容器中,比如,如果你要使用的話,那麼你需要把ListView, ScrollView, WebView等添加到容器中。

6、上圖中的紅色地區就是屏的大小(嚴格來說,這裡說螢幕大小並不準確,應該說成內容地區更加準確)

三. 具體實現

明白了實現原理與過程,我們嘗試來具體實現,首先,為了以後更好地擴充,設計更加合理,我們把下拉重新整理的功能抽象成一個介面:
1、IPullToRefresh<T extends View>

它具體的定義方法如下:

public interface IPullToRefresh<T extends View> {   public void setPullRefreshEnabled(boolean pullRefreshEnabled);   public void setPullLoadEnabled(boolean pullLoadEnabled);   public void setScrollLoadEnabled(boolean scrollLoadEnabled);   public boolean isPullRefreshEnabled();   public boolean isPullLoadEnabled();   public boolean isScrollLoadEnabled();   public void setOnRefreshListener(OnRefreshListener<T> refreshListener);   public void onPullDownRefreshComplete();   public void onPullUpRefreshComplete();   public T getRefreshableView();   public LoadingLayout getHeaderLoadingLayout();   public LoadingLayout getFooterLoadingLayout();   public void setLastUpdatedLabel(CharSequence label); } 

這個介面是一個泛型的,它接受View的衍生類別,因為要放到我們的容器中的不就是一個View嗎?

2、PullToRefreshBase<T extends View>

這個類實現了IPullToRefresh介面,它是從LinearLayout繼承過來,作為下拉重新整理的一個抽象基類,如果你想實現ListView的下拉重新整理,只需要擴充這個類,實現一些必要的方法就可以了。這個類的職責主要有以下幾點:

  • 處理onInterceptTouchEvent()和onTouchEvent()中的事件:當內容的View(比如ListView)正如處於最頂部,此時再向下拉,我們必須截斷事件,然後move事件就會把後續的事件傳遞到onTouchEvent()方法中,然後再在這個方法中,我們根據move的距離再進行scroll整個View。
  • 負責建立Header、Footer和Content View:在構造方法中調用方法去建立這三個部分的View,衍生類別可以重寫這些方法,以提供不同式樣的Header和Footer,它會調用createHeaderLoadingLayout和createFooterLoadingLayout方法來建立Header和Footer建立Content View的方法是一個抽象方法,必須讓衍生類別來實現,返回一個非null的View,然後容器再把這個View添加到自己裡面。
  • 設定各種狀態:這裡面有很多狀態,如下拉、上拉、重新整理、載入中、釋放等,它會根據使用者拉動的距離來更改狀態,狀態的改變,它也會把Header和Footer的狀態改變,然後Header和Footer會根據狀態去顯示相應的介面式樣。

3、PullToRefreshBase<T extends View>繼承關係

這裡我實現了三個下拉重新整理的衍生類別,分別是ListView、ScrollView、WebView三個,它們的繼承關係如下:

圖四、PullToRefreshBase類的繼承關係

關於PullToRefreshBase類及其派和類,有幾點需要說明:

對於ListView,ScrollView,WebView這三種情況,他們是否滑動到最頂部或是最底部的實現是不一樣的,所以,在PullToRefreshBase類中需要調用兩個抽象方法來判斷當前的位置是否在頂部或底部,而其衍生類別必須要實現這兩個方法。比如對於ListView,它滑動到最頂部的條件就是第一個child完全可見並且first postion是0。這兩個抽象方法是:

/**  * 判斷重新整理的View是否滑動到頂部  *  * @return true表示已經滑動到頂部,否則false  */ protected abstract boolean isReadyForPullDown();  /**  * 判斷重新整理的View是否滑動到底  *  * @return true表示已經滑動到底部,否則false  */ protected abstract boolean isReadyForPullUp(); 

建立可下拉重新整理的View(也就是content view)的抽象方法是

/**  * 建立可以重新整理的View  *  * @param context context  * @param attrs 屬性  * @return View  */ protected abstract T createRefreshableView(Context context, AttributeSet attrs); 

4、LoadingLayout

LoadingLayout是重新整理Layout的一個抽象,它是一個抽象基類。Header和Footer都擴充於這個類。這類抽象類別,提供了兩個抽象方法:

getContentSize

這個方法返回當前這個重新整理Layout的大小,通常返回的是布局的高度,為了以後可以擴充為水平拉動,所以方法名字沒有取成getLayoutHeight()之類的,這個傳回值,將會作為鬆手後是否可以重新整理的臨界值,如果下拉的位移值大於這個值,就認為可以重新整理,否則不重新整理,這個方法必須由衍生類別來實現。

setState

這個方法用來設定當前重新整理Layout的狀態,PullToRefreshBase類會調用這個方法,當進入下拉,鬆手等動作時,都會調用這個方法,衍生類別裡面只需要根據這些狀態實現不同的介面顯示,如下拉狀態時,就顯示出箭頭,重新整理狀態時,就顯示loading的表徵圖。

可能的狀態值有:RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA

LoadingLayout及其衍生類別的繼承關係如下圖所示:

圖五、LoadingLayout及其衍生類別的類圖

我們可以隨意地制定自己的Header和Footer,我們也可以實現如圖一和圖二中顯示的各種下拉重新整理案例中的Header和Footer,只要重寫上述兩個方法getContentSize()和setState()就行了。HeaderLoadingLayout,它預設是顯示箭頭式樣的布局,而RotateLoadingLayout則是顯示一個旋轉表徵圖的式樣。

5、事件處理

我們必須重寫PullToRefreshBase類的兩個事件相關的方法onInterceptTouchEvent()和onTouchEvent()方法。由於ListView,ScrollView,WebView它們是放到PullToRefreshBase內部的,所在事件先是傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,所以我們應該在這個方法中去處理ACTION_MOVE事件,判斷如果當前ListView,ScrollView,WebView是否在最頂部或最底部,如果是,則開始截斷事件,一旦事件被截斷,後續的事件就會傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,我們再在ACTION_MOVE事件中去移動整個布局,從而實現下拉或上拉動作。

6、滾動布局(scrollTo)

如圖三的布局結構可知,預設情況下Header和Footer是放置在Content View的最上面和最下面,通過設定padding來讓他跑到螢幕外面去了,如果我們將整個布局向下滾動(scrollTo)一定距離,那麼Header就會被顯示出來,基於這種情況,所以在我的實現中,最終我是調用scrollTo來實現下拉動作的。

總的說來,實現的重要的點就這些,具體的一些細節在實現在會碰到很多,可以參考代碼。

四. 如何使用

使用下拉重新整理的代碼如下

@Override   public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);          mPullListView = new PullToRefreshListView(this);     setContentView(mPullListView);          // 上拉載入不可用     mPullListView.setPullLoadEnabled(false);     // 滾動到底自動載入可用     mPullListView.setScrollLoadEnabled(true);          mCurIndex = mLoadDataCount;     mListItems = new LinkedList<String>();     mListItems.addAll(Arrays.asList(mStrings).subList(0, mCurIndex));     mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);          // 得到實際的ListView     mListView = mPullListView.getRefreshableView();     // 綁定資料     mListView.setAdapter(mAdapter);         // 設定下拉重新整理的listener     mPullListView.setOnRefreshListener(new OnRefreshListener<ListView>() {       @Override       public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {         mIsStart = true;         new GetDataTask().execute();       }        @Override       public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {         mIsStart = false;         new GetDataTask().execute();       }     });     setLastUpdateTime();          // 自動重新整理     mPullListView.doPullRefreshing(true, 500);   } 

這是初始化一個下拉重新整理的布局,並且調用setContentView來設定到Activity中。

在下拉重新整理完成後,我們可以調用onPullDownRefreshComplete()和onPullUpRefreshComplete()方法來停止重新整理和載入

五. 運行效果
這裡列出了demo的運行效果圖。

圖六、ListView下拉重新整理,注意Header和Footer的樣式

六. Bug修複

已知bug修複情況如下,發現了代碼bug的看官也可以給我反饋,謝謝~~~

1,對於ListView的下拉重新整理,當啟用滾動到底自動載入時,如果footer由隱藏變為顯示時,出現顯示異常的情況
這個問題已經修複了,修正的代碼如下:

PullToRefreshListView#setScrollLoadEnabled方法,修正後的代碼如下:@Override public void setScrollLoadEnabled(boolean scrollLoadEnabled) {   if (isScrollLoadEnabled() == scrollLoadEnabled) {     return;   }      super.setScrollLoadEnabled(scrollLoadEnabled);      if (scrollLoadEnabled) {     // 設定Footer     if (null == mLoadMoreFooterLayout) {       mLoadMoreFooterLayout = new FooterLoadingLayout(getContext());       mListView.addFooterView(mLoadMoreFooterLayout, null, false);     }          mLoadMoreFooterLayout.show(true);   } else {     if (null != mLoadMoreFooterLayout) {       mLoadMoreFooterLayout.show(false);     }   } } 

LoadingLayout#show方法,修正後的代碼如下:

/**  * 顯示或隱藏這個布局  *  * @param show flag  */ public void show(boolean show) {   // If is showing, do nothing.   if (show == (View.VISIBLE == getVisibility())) {     return;   }      ViewGroup.LayoutParams params = mContainer.getLayoutParams();   if (null != params) {     if (show) {       params.height = ViewGroup.LayoutParams.WRAP_CONTENT;     } else {       params.height = 0;     }          requestLayout();     setVisibility(show ? View.VISIBLE : View.INVISIBLE);   } } 

在更改LayoutParameter後,調用requestLayout()方法。

圖片旋轉相容2.x系統

我之前想的是這個只需要相容3.x以上的系統,但發現有很多網友在使用過程中遇到過相容性問題,這次抽空將這個相容性一併實現了。

onPull的修改如下:
    

  @Override public void onPull(float scale) {   if (null == mRotationHelper) {     mRotationHelper = new ImageViewRotationHelper(mArrowImageView);   }      float angle = scale * 180f; // SUPPRESS CHECKSTYLE   mRotationHelper.setRotation(angle); } 

ImageViewRotationHelper主要的作用就是實現了ImageView的旋轉功能,內部作了版本的區分,實現代碼如下:

/**    * The image view rotation helper    *    * @author lihong06    * @since 2014-5-2    */   static class ImageViewRotationHelper {     /** The imageview */     private final ImageView mImageView;     /** The matrix */     private Matrix mMatrix;     /** Pivot X */     private float mRotationPivotX;     /** Pivot Y */     private float mRotationPivotY;          /**      * The constructor method.      *      * @param imageView the image view      */     public ImageViewRotationHelper(ImageView imageView) {       mImageView = imageView;     }          /**      * Sets the degrees that the view is rotated around the pivot point. Increasing values      * result in clockwise rotation.      *      * @param rotation The degrees of rotation.      *      * @see #getRotation()      * @see #getPivotX()      * @see #getPivotY()      * @see #setRotationX(float)      * @see #setRotationY(float)      *      * @attr ref android.R.styleable#View_rotation      */     public void setRotation(float rotation) {       if (APIUtils.hasHoneycomb()) {         mImageView.setRotation(rotation);       } else {         if (null == mMatrix) {           mMatrix = new Matrix();                      // 計算旋轉的中心點           Drawable imageDrawable = mImageView.getDrawable();           if (null != imageDrawable) {             mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);             mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);           }         }                  mMatrix.setRotate(rotation, mRotationPivotX, mRotationPivotY);         mImageView.setImageMatrix(mMatrix);       }     }   } 

最核心的就是,如果在2.x的版本上,旋轉ImageView使用Matrix。

PullToRefreshBase構造方法相容2.x

在三個參數的構造方法聲明如下標註:

@SuppressLint("NewApi")

@TargetApi(Build.VERSION_CODES.HONEYCOMB)

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.