国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android下拉刷新框架實(shí)現(xiàn)代碼實(shí)例

2019-12-12 04:47:17
字體:
供稿:網(wǎng)友

前段時(shí)間項(xiàng)目中用到了下拉刷新功能,之前在網(wǎng)上也找到過類似的demo,但這些demo的質(zhì)量參差不齊,用戶體驗(yàn)也不好,接口設(shè)計(jì)也不行。最張沒辦法,終于忍不了了,自己就寫了一個(gè)下拉刷新的框架,這個(gè)框架是一個(gè)通用的框架,效果和設(shè)計(jì)感覺都還不錯(cuò),現(xiàn)在分享給各位看官。

一. 關(guān)于下拉刷新

下拉刷新這種用戶交互最早由twitter創(chuàng)始人洛倫•布里切特(Loren Brichter)發(fā)明,有理論認(rèn)為,下拉刷新是一種適用于按照從新到舊的時(shí)間順序排列feeds的應(yīng)用,在這種應(yīng)用場景中看完舊的內(nèi)容時(shí),用戶會很自然地下拉查找更新的內(nèi)容,因此下拉刷新就顯得非常合理。大家可以參考這篇文章:有趣的下拉刷新,下面我貼出一個(gè)有趣的下拉刷新的案例。

圖一、有趣的下拉刷新案例(一)
圖二、有趣的下拉刷新案例(二)

二. 實(shí)現(xiàn)原理

上面這些例子,外觀做得再好看,他的本質(zhì)上都一樣,那就是一個(gè)下拉刷新控件通常由以下幾部分組成:

【1】Header

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

【2】Content

這部分是內(nèi)容區(qū)域,網(wǎng)上有很多例子都是直接在ListView里面添加Header,但這就有局限性,因?yàn)楹枚嗲闆r下并不一定是用ListView來顯示數(shù)據(jù)。我們把要顯示內(nèi)容的View放置在我們的一個(gè)容器中,如果你想實(shí)現(xiàn)一個(gè)用ListView顯示數(shù)據(jù)的下拉刷新,你需要?jiǎng)?chuàng)建一個(gè)ListView旋轉(zhuǎn)到我的容器中。我們處理這個(gè)容器的事件(down, move, up),如果向下拉,則把整個(gè)布局向下滑動(dòng),從而把header顯示出來。

【3】Footer

Footer可以用來顯示向上拉的箭頭,自動(dòng)加載更多的進(jìn)度條等。

以上三部分總結(jié)的說來,就是如下圖所示的這種布局結(jié)構(gòu):

圖三,下拉刷新的布局結(jié)構(gòu)

關(guān)于上圖,需要說明幾點(diǎn):

1、這個(gè)布局?jǐn)U展于LinearLayout,垂直排列

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

3、Content填充滿父控件,通過設(shè)置top, bottom的padding來使Header和Footer不可見,也就是讓它超出屏幕外

4、下拉時(shí),調(diào)用scrollTo方法來將整個(gè)布局向下滑動(dòng),從而把Header顯示出來,上拉正好與下拉相反。

5、派生類需要實(shí)現(xiàn)的是:將Content View填充到父容器中,比如,如果你要使用的話,那么你需要把ListView, ScrollView, WebView等添加到容器中。

6、上圖中的紅色區(qū)域就是屏的大小(嚴(yán)格來說,這里說屏幕大小并不準(zhǔn)確,應(yīng)該說成內(nèi)容區(qū)域更加準(zhǔn)確)

三. 具體實(shí)現(xiàn)

明白了實(shí)現(xiàn)原理與過程,我們嘗試來具體實(shí)現(xiàn),首先,為了以后更好地?cái)U(kuò)展,設(shè)計(jì)更加合理,我們把下拉刷新的功能抽象成一個(gè)接口:
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); } 

這個(gè)接口是一個(gè)泛型的,它接受View的派生類,因?yàn)橐诺轿覀兊娜萜髦械牟痪褪且粋€(gè)View嗎?

2、PullToRefreshBase<T extends View>

這個(gè)類實(shí)現(xiàn)了IPullToRefresh接口,它是從LinearLayout繼承過來,作為下拉刷新的一個(gè)抽象基類,如果你想實(shí)現(xiàn)ListView的下拉刷新,只需要擴(kuò)展這個(gè)類,實(shí)現(xiàn)一些必要的方法就可以了。這個(gè)類的職責(zé)主要有以下幾點(diǎn):

  • 處理onInterceptTouchEvent()和onTouchEvent()中的事件:當(dāng)內(nèi)容的View(比如ListView)正如處于最頂部,此時(shí)再向下拉,我們必須截?cái)嗍录缓髆ove事件就會把后續(xù)的事件傳遞到onTouchEvent()方法中,然后再在這個(gè)方法中,我們根據(jù)move的距離再進(jìn)行scroll整個(gè)View。
  • 負(fù)責(zé)創(chuàng)建Header、Footer和Content View:在構(gòu)造方法中調(diào)用方法去創(chuàng)建這三個(gè)部分的View,派生類可以重寫這些方法,以提供不同式樣的Header和Footer,它會調(diào)用createHeaderLoadingLayout和createFooterLoadingLayout方法來創(chuàng)建Header和Footer創(chuàng)建Content View的方法是一個(gè)抽象方法,必須讓派生類來實(shí)現(xiàn),返回一個(gè)非null的View,然后容器再把這個(gè)View添加到自己里面。
  • 設(shè)置各種狀態(tài):這里面有很多狀態(tài),如下拉、上拉、刷新、加載中、釋放等,它會根據(jù)用戶拉動(dòng)的距離來更改狀態(tài),狀態(tài)的改變,它也會把Header和Footer的狀態(tài)改變,然后Header和Footer會根據(jù)狀態(tài)去顯示相應(yīng)的界面式樣。

3、PullToRefreshBase<T extends View>繼承關(guān)系

這里我實(shí)現(xiàn)了三個(gè)下拉刷新的派生類,分別是ListView、ScrollView、WebView三個(gè),它們的繼承關(guān)系如下:

圖四、PullToRefreshBase類的繼承關(guān)系

關(guān)于PullToRefreshBase類及其派和類,有幾點(diǎn)需要說明:

對于ListView,ScrollView,WebView這三種情況,他們是否滑動(dòng)到最頂部或是最底部的實(shí)現(xiàn)是不一樣的,所以,在PullToRefreshBase類中需要調(diào)用兩個(gè)抽象方法來判斷當(dāng)前的位置是否在頂部或底部,而其派生類必須要實(shí)現(xiàn)這兩個(gè)方法。比如對于ListView,它滑動(dòng)到最頂部的條件就是第一個(gè)child完全可見并且first postion是0。這兩個(gè)抽象方法是:

/**  * 判斷刷新的View是否滑動(dòng)到頂部  *  * @return true表示已經(jīng)滑動(dòng)到頂部,否則false  */ protected abstract boolean isReadyForPullDown();  /**  * 判斷刷新的View是否滑動(dòng)到底  *  * @return true表示已經(jīng)滑動(dòng)到底部,否則false  */ protected abstract boolean isReadyForPullUp(); 

創(chuàng)建可下拉刷新的View(也就是content view)的抽象方法是

/**  * 創(chuàng)建可以刷新的View  *  * @param context context  * @param attrs 屬性  * @return View  */ protected abstract T createRefreshableView(Context context, AttributeSet attrs); 

4、LoadingLayout

LoadingLayout是刷新Layout的一個(gè)抽象,它是一個(gè)抽象基類。Header和Footer都擴(kuò)展于這個(gè)類。這類抽象類,提供了兩個(gè)抽象方法:

getContentSize

這個(gè)方法返回當(dāng)前這個(gè)刷新Layout的大小,通常返回的是布局的高度,為了以后可以擴(kuò)展為水平拉動(dòng),所以方法名字沒有取成getLayoutHeight()之類的,這個(gè)返回值,將會作為松手后是否可以刷新的臨界值,如果下拉的偏移值大于這個(gè)值,就認(rèn)為可以刷新,否則不刷新,這個(gè)方法必須由派生類來實(shí)現(xiàn)。

setState

這個(gè)方法用來設(shè)置當(dāng)前刷新Layout的狀態(tài),PullToRefreshBase類會調(diào)用這個(gè)方法,當(dāng)進(jìn)入下拉,松手等動(dòng)作時(shí),都會調(diào)用這個(gè)方法,派生類里面只需要根據(jù)這些狀態(tài)實(shí)現(xiàn)不同的界面顯示,如下拉狀態(tài)時(shí),就顯示出箭頭,刷新狀態(tài)時(shí),就顯示loading的圖標(biāo)。

可能的狀態(tài)值有:RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA

LoadingLayout及其派生類的繼承關(guān)系如下圖所示:

圖五、LoadingLayout及其派生類的類圖

我們可以隨意地制定自己的Header和Footer,我們也可以實(shí)現(xiàn)如圖一和圖二中顯示的各種下拉刷新案例中的Header和Footer,只要重寫上述兩個(gè)方法getContentSize()和setState()就行了。HeaderLoadingLayout,它默認(rèn)是顯示箭頭式樣的布局,而RotateLoadingLayout則是顯示一個(gè)旋轉(zhuǎn)圖標(biāo)的式樣。

5、事件處理

我們必須重寫PullToRefreshBase類的兩個(gè)事件相關(guān)的方法onInterceptTouchEvent()和onTouchEvent()方法。由于ListView,ScrollView,WebView它們是放到PullToRefreshBase內(nèi)部的,所在事件先是傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,所以我們應(yīng)該在這個(gè)方法中去處理ACTION_MOVE事件,判斷如果當(dāng)前ListView,ScrollView,WebView是否在最頂部或最底部,如果是,則開始截?cái)嗍录坏┦录唤財(cái)啵罄m(xù)的事件就會傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,我們再在ACTION_MOVE事件中去移動(dòng)整個(gè)布局,從而實(shí)現(xiàn)下拉或上拉動(dòng)作。

6、滾動(dòng)布局(scrollTo)

如圖三的布局結(jié)構(gòu)可知,默認(rèn)情況下Header和Footer是放置在Content View的最上面和最下面,通過設(shè)置padding來讓他跑到屏幕外面去了,如果我們將整個(gè)布局向下滾動(dòng)(scrollTo)一定距離,那么Header就會被顯示出來,基于這種情況,所以在我的實(shí)現(xiàn)中,最終我是調(diào)用scrollTo來實(shí)現(xiàn)下拉動(dòng)作的。

總的說來,實(shí)現(xiàn)的重要的點(diǎn)就這些,具體的一些細(xì)節(jié)在實(shí)現(xiàn)在會碰到很多,可以參考代碼。

四. 如何使用

使用下拉刷新的代碼如下

@Override   public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);          mPullListView = new PullToRefreshListView(this);     setContentView(mPullListView);          // 上拉加載不可用     mPullListView.setPullLoadEnabled(false);     // 滾動(dòng)到底自動(dòng)加載可用     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);          // 得到實(shí)際的ListView     mListView = mPullListView.getRefreshableView();     // 綁定數(shù)據(jù)     mListView.setAdapter(mAdapter);         // 設(shè)置下拉刷新的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();          // 自動(dòng)刷新     mPullListView.doPullRefreshing(true, 500);   } 

這是初始化一個(gè)下拉刷新的布局,并且調(diào)用setContentView來設(shè)置到Activity中。

在下拉刷新完成后,我們可以調(diào)用onPullDownRefreshComplete()和onPullUpRefreshComplete()方法來停止刷新和加載

五. 運(yùn)行效果
這里列出了demo的運(yùn)行效果圖。

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

六. Bug修復(fù)

已知bug修復(fù)情況如下,發(fā)現(xiàn)了代碼bug的看官也可以給我反饋,謝謝~~~

1,對于ListView的下拉刷新,當(dāng)啟用滾動(dòng)到底自動(dòng)加載時(shí),如果footer由隱藏變?yōu)轱@示時(shí),出現(xiàn)顯示異常的情況
這個(gè)問題已經(jīng)修復(fù)了,修正的代碼如下:

PullToRefreshListView#setScrollLoadEnabled方法,修正后的代碼如下:@Override public void setScrollLoadEnabled(boolean scrollLoadEnabled) {   if (isScrollLoadEnabled() == scrollLoadEnabled) {     return;   }      super.setScrollLoadEnabled(scrollLoadEnabled);      if (scrollLoadEnabled) {     // 設(shè)置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方法,修正后的代碼如下:

/**  * 顯示或隱藏這個(gè)布局  *  * @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后,調(diào)用requestLayout()方法。

圖片旋轉(zhuǎn)兼容2.x系統(tǒng)

我之前想的是這個(gè)只需要兼容3.x以上的系統(tǒng),但發(fā)現(xiàn)有很多網(wǎng)友在使用過程中遇到過兼容性問題,這次抽空將這個(gè)兼容性一并實(shí)現(xiàn)了。

onPull的修改如下:
    

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

ImageViewRotationHelper主要的作用就是實(shí)現(xiàn)了ImageView的旋轉(zhuǎn)功能,內(nèi)部作了版本的區(qū)分,實(shí)現(xiàn)代碼如下:

/**    * 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();                      // 計(jì)算旋轉(zhuǎn)的中心點(diǎn)           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的版本上,旋轉(zhuǎn)ImageView使用Matrix。

PullToRefreshBase構(gòu)造方法兼容2.x

在三個(gè)參數(shù)的構(gòu)造方法聲明如下標(biāo)注:

@SuppressLint("NewApi")

@TargetApi(Build.VERSION_CODES.HONEYCOMB)

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 五常市| 唐山市| 林甸县| 庆城县| 翁源县| 浦江县| 宣威市| 吐鲁番市| 娄底市| 陕西省| 漳州市| 崇明县| 肥乡县| 锦州市| 高碑店市| 永宁县| 北流市| 如皋市| 大余县| 泸溪县| 黑水县| 白沙| 肇东市| 高陵县| 武山县| 林西县| 九台市| 大理市| 邹平县| 大田县| 庄河市| 宜川县| 义马市| 江孜县| 沈阳市| 青冈县| 晋州市| 宁城县| 永登县| 枞阳县| 溧阳市|