1. 程式人生 > >打造通用的Android下拉重新整理元件 適用於ListView GridView等各類View

打造通用的Android下拉重新整理元件 適用於ListView GridView等各類View

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

前言

 最近在做專案時,使用了一個開源的下拉重新整理ListView元件,極其的不穩定,bug還多。穩定的元件又寫得太複雜了,jar包較大。在我的一篇部落格中也講述過下拉重新整理的實現,即

Android打造(ListView、GridView等)通用的下拉重新整理、上拉自動載入的元件。但是這種通過修改Margin的形式感覺不是特別的流暢,因此在這漫長的國慶長假又花了點時間用另外的原理實現了一遍,特此分享出來。


基本原理

 原理就是自定義一個ViewGroup,將Header View, Content View, Footer View從上到下依次佈局,如圖1 (紅色區域為螢幕的顯示區域)。在初始時通過滾動,使得該元件在Y軸方向上滾動HeaderView的高度的距離,這樣HeaderView就被隱藏掉了,如圖2。而Content View的寬度和高度都是match_parent的,因此此時螢幕上只顯示Content View, HeaderView 和 FooterView都被隱藏在螢幕外了。當元件被滾動到頂端時,如果使用者繼續下拉,那麼攔截觸控事件,然後通過Scroller來滾動y軸的偏移量,實現逐步的顯示HeaderView,從而到達下拉的效果,如圖3。當用戶滑動到最底部時會觸發載入更多的操作。

                 

  圖 1 (紅色區域為螢幕)     圖2 (紅色區域為螢幕)    圖 3(紅色區域為螢幕) 

 通過使用Scroller使得整個滾動更加的平滑,而使用Margin來實現的話需要自己來計算滾動時間和margin值,並不是很流暢,而且頻繁的修改佈局引數效率也不高。使用Scroller只是滾動位置,而沒有修改佈局引數,因此有點較為突出。


Scroller的使用

 為了更好的理解下拉刷的實現,我們先要了解Scroller的作用以及如何使用。這裡我們將做一個簡單的示例來說明。

 Scroller是一個幫助View滾動的輔助類,在使用它之前使用者需要通過startScroll來設定滾動的引數,即起始點座標和x,y軸上要滾動的距離。Scroller它封裝了滾動時間、要滾動的目標x軸和y軸,以及在每個時間內view應該滾動到的x,y軸的座標點,這樣使用者就可以在有效的滾動週期內通過Scroller的getCurX()和getCurY()來獲取當前時刻View應該滾動的位置,然後通過呼叫View的scrollTo或者ScrollBy方法進行滾動。那麼如何判斷滾動是否結束呢 ? 我們只需要覆寫View類的computeScroll方法,該方法會在View繪製的時候被呼叫,在裡面呼叫Scroller的computeScrollOffset來判斷滾動是否完成,如果返回true表明滾動未完成,否則滾動完成。上述說的scrollTo或者ScrollBy的呼叫就是在computeScrollOffset為true的情況下呼叫,並且最後還要呼叫目標view的postInvalidate()或者invalidate()以實現View的重繪。View的重繪又會導致computeScroll方法被呼叫,從而繼續整個滾動過程,直至computeScrollOffset返回false, 即滾動結束。整個過程有點繞,我們看一個例子吧。

public class ScrollLayout extends FrameLayout {    private String TAG = ScrollLayout.class.getSimpleName();    Scroller mScroller ;    public ScrollLayout(Context context) {        super(context);                mScroller = new Scroller(context) ;    }        // 該函式會在View重繪之時被呼叫    @Override    public void computeScroll() {        if ( mScroller.computeScrollOffset() ) {            // 滾動到此刻View應該滾動到的x,y座標上.            this.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());            // 請求重繪該View,從而又會導致computeScroll被呼叫,然後繼續滾動,直到computeScrollOffset返回false            this.postInvalidate();        }    }    // 呼叫這個方法進行滾動,這裡我們只滾動豎直方向,    public void scrollTo(int y) {            // 引數1和引數2分別為滾動的起始點水平、豎直方向的滾動偏移量            // 引數3和引數4為在水平和豎直方向上滾動的距離            mScroller.startScroll(getScrollX(), getScrollY(), 0, y);            this.invalidate();    }}

 滾動該檢視的程式碼 : 

        ScrollLayout scrollView = new ScrollLayout(getContext()) ;        scrollView.scrollTo(100);
 通過上面這段程式碼會讓scrollView在y軸上向下滾動100個畫素點。我們結合程式碼來分析一下。首先呼叫scrollTo(int y)方法,然後我們在該方法中通過mScroller.startScroll()方法來設定了滾動的引數,然後呼叫invalidate()方法使得該View重繪。重繪時會呼叫computeScroll方法,在該方法中通過mScroller.computeScrollOffset()判斷滾動是否完成,如果返回true那代表沒有滾動完成,此時把該View滾動到此刻View應該滾動到的x, y位置,這個位置通過mScroller的getCurX, getCurY獲得。然後繼續呼叫重繪方法,繼續執行滾動過程,直至滾動完成。

 瞭解了Scroller原理後,我們繼續看通用的下拉重新整理元件的實現吧。


下拉重新整理實現

 程式碼量不算多,但是也挺長的,我們這裡只拿出重要的點來分析,完成的原始碼在博文最後會給出。以下是重要的程式碼段 : 

/** * @author mrsimple */public abstract class RefreshLayoutBase<T extends View> extends ViewGroup implements        OnScrollListener {    /**     *      */    protected Scroller mScroller;    /**     * 下拉重新整理時顯示的header view     */    protected View mHeaderView;    /**     * 上拉載入更多時顯示的footer view     */    protected View mFooterView;    /**     * 本次觸控滑動y座標上的偏移量     */    protected int mYOffset;    /**     * 內容檢視, 即使用者觸控導致下拉重新整理、上拉載入的主檢視. 比如ListView, GridView等.     */    protected T mContentView;    /**     * 最初的滾動位置.第一次佈局時滾動header的高度的距離     */    protected int mInitScrollY = 0;    /**     * 最後一次觸控事件的y軸座標     */    protected int mLastY = 0;    /**     * 空閒狀態     */    public static final int STATUS_IDLE = 0;    /**     * 下拉或者上拉狀態, 還沒有到達可重新整理的狀態     */    public static final int STATUS_PULL_TO_REFRESH = 1;    /**     * 下拉或者上拉狀態     */    public static final int STATUS_RELEASE_TO_REFRESH = 2;    /**     * 重新整理中     */    public static final int STATUS_REFRESHING = 3;    /**     * LOADING中     */    public static final int STATUS_LOADING = 4;    /**     * 當前狀態     */    protected int mCurrentStatus = STATUS_IDLE;    /**     * 下拉重新整理監聽器     */    protected OnRefreshListener mOnRefreshListener;    /**     * header中的箭頭圖示     */    private ImageView mArrowImageView;    /**     * 箭頭是否向上     */    private boolean isArrowUp;    /**     * header 中的文字標籤     */    private TextView mTipsTextView;    /**     * header中的時間標籤     */    private TextView mTimeTextView;    /**     * header中的進度條     */    private ProgressBar mProgressBar;    /**     *      */    private int mScreenHeight;    /**     *      */    private int mHeaderHeight;    /**     *      */    protected OnLoadListener mLoadListener;    /**     * @param context     */    public RefreshLayoutBase(Context context) {        this(context, null);    }    /**     * @param context     * @param attrs     */    public RefreshLayoutBase(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    /**     * @param context     * @param attrs     * @param defStyle     */    public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs);        // 初始化Scroller物件        mScroller = new Scroller(context);        // 獲取螢幕高度        mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;        // header 的高度為螢幕高度的 1/4        mHeaderHeight = mScreenHeight / 4;        // 初始化整個佈局        initLayout(context);    }    /**     * 初始化整個佈局     *      * @param context     */    private final void initLayout(Context context) {        // header view        setupHeaderView(context);        // 設定內容檢視        setupContentView(context);        // 設定佈局引數        setDefaultContentLayoutParams();        //        addView(mContentView);        // footer view        setupFooterView(context);    }    /**     * 初始化 header view     */    protected void setupHeaderView(Context context) {        mHeaderView = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this,                false);        mHeaderView                .setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,                        mHeaderHeight));        mHeaderView.setBackgroundColor(Color.RED);        // header的高度整個為1/4的螢幕高度,但是它只有100px是有效的顯示區域,取餘取餘為paddingTop,這樣是為了達到下拉的效果        mHeaderView.setPadding(0, mHeaderHeight - 100, 0, 0);        addView(mHeaderView);        // HEADER VIEWS        mArrowImageView = (ImageView) mHeaderView.findViewById(R.id.pull_to_arrow_image);        mTipsTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);        mTimeTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_updated_at);        mProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);    }    /**     * 初始化Content View, 子類覆寫.     */    protected abstract void setupContentView(Context context);    /**     * 與Scroller合作,實現平滑滾動。在該方法中呼叫Scroller的computeScrollOffset來判斷滾動是否結束。如果沒有結束,     * 那麼滾動到相應的位置,並且呼叫postInvalidate方法重繪介面,從而再次進入到這個computeScroll流程,直到滾動結束。     */    @Override    public void computeScroll() {        if (mScroller.computeScrollOffset()) {            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());            postInvalidate();        }    }    /*     * 在適當的時候攔截觸控事件,這裡指的適當的時候是當mContentView滑動到頂部,並且是下拉時攔截觸控事件,否則不攔截,交給其child     * view 來處理。     * @see     * android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)     */    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        /*         * This method JUST determines whether we want to intercept the motion.         * If we return true, onTouchEvent will be called and we do the actual         * scrolling there.         */        final int action = MotionEventCompat.getActionMasked(ev);        // Always handle the case of the touch gesture being complete.        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {            // Do not intercept touch event, let the child handle it            return false;        }        switch (action) {            case MotionEvent.ACTION_DOWN:                mLastY = (int) ev.getRawY();                break;            case MotionEvent.ACTION_MOVE:                // int yDistance = (int) ev.getRawY() - mYDown;                mYOffset = (int) ev.getRawY() - mLastY;                // 如果拉到了頂部, 並且是下拉,則攔截觸控事件,從而轉到onTouchEvent來處理下拉重新整理事件                if (isTop() && mYOffset > 0) {                    return true;                }                break;        }        // Do not intercept touch event, let the child handle it        return false;    }    /**     * 是否已經到了最頂部,子類需覆寫該方法,使得mContentView滑動到最頂端時返回true, 如果到達最頂端使用者繼續下拉則攔截事件;     *      * @return     */    protected abstract boolean isTop();    /**     * 是否已經到了最底部,子類需覆寫該方法,使得mContentView滑動到最底端時返回true;從而觸發自動載入更多的操作     *      * @return     */    protected abstract boolean isBottom();    /**     * 顯示footer view     */    private void showFooterView() {        startScroll(mFooterView.getMeasuredHeight());        mCurrentStatus = STATUS_LOADING;    }    /**     * 設定滾動的引數     *      * @param yOffset     */    private void startScroll(int yOffset) {        mScroller.startScroll(getScrollX(), getScrollY(), 0, yOffset);        invalidate();    }    /*     * 在這裡處理觸控事件以達到下拉重新整理或者上拉自動載入的問題     * @see android.view.View#onTouchEvent(android.view.MotionEvent)     */    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d(VIEW_LOG_TAG, "@@@ onTouchEvent : action = " + event.getAction());        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastY = (int) event.getRawY();                break;            case MotionEvent.ACTION_MOVE:                int currentY = (int) event.getRawY();                mYOffset = currentY - mLastY;                if (mCurrentStatus != STATUS_LOADING) {                    //                    changeScrollY(mYOffset);                }                rotateHeaderArrow();                changeTips();                mLastY = currentY;                break;            case MotionEvent.ACTION_UP:                // 下拉重新整理的具體操作                doRefresh();                break;            default:                break;        }        return true;    }    /**     * 修改y軸上的滾動值,從而實現header被下拉的效果     * @param distance     * @return     */    private void changeScrollY(int distance) {        // 最大值為 scrollY(header 隱藏), 最小值為0 ( header 完全顯示).        int curY = getScrollY();        // 下拉        if (distance > 0 && curY - distance > getPaddingTop()) {            scrollBy(0, -distance);        } else if (distance < 0 && curY - distance <= mInitScrollY) {            // 上拉過程            scrollBy(0, -distance);        }        curY = getScrollY();        int slop = mInitScrollY / 2;        //        if (curY > 0 && curY < slop) {            mCurrentStatus = STATUS_RELEASE_TO_REFRESH;        } else if (curY > 0 && curY > slop) {            mCurrentStatus = STATUS_PULL_TO_REFRESH;        }    }    /**     * 重新整理結束,恢復狀態     */    public void refreshComplete() {        mCurrentStatus = STATUS_IDLE;        // 隱藏header view        mScroller.startScroll(getScrollX(), getScrollY(), 0, mInitScrollY - getScrollY());        invalidate();        updateHeaderTimeStamp();        // 200毫秒後處理arrow和progressbar,免得太突兀        this.postDelayed(new Runnable() {            @Override            public void run() {                mArrowImageView.setVisibility(View.VISIBLE);                mProgressBar.setVisibility(View.GONE);            }        }, 100);    }    /**     * 載入結束,恢復狀態     */    public void loadCompelte() {        // 隱藏footer        startScroll(mInitScrollY - getScrollY());        mCurrentStatus = STATUS_IDLE;    }    /**     * 手指擡起時,根據使用者下拉的高度來判斷是否是有效的下拉重新整理操作。如果下拉的距離超過header view的     * 1/2那麼則認為是有效的下拉重新整理操作,否則恢復原來的檢視狀態.     */    private void changeHeaderViewStaus() {        int curScrollY = getScrollY();        // 超過1/2則認為是有效的下拉重新整理, 否則還原        if (curScrollY < mInitScrollY / 2) {            // 滾動到能夠正常顯示header的位置            mScroller.startScroll(getScrollX(), curScrollY, 0, mHeaderView.getPaddingTop()                    - curScrollY);            mCurrentStatus = STATUS_REFRESHING;            mTipsTextView.setText(R.string.pull_to_refresh_refreshing_label);            mArrowImageView.clearAnimation();            mArrowImageView.setVisibility(View.GONE);            mProgressBar.setVisibility(View.VISIBLE);        } else {            mScroller.startScroll(getScrollX(), curScrollY, 0, mInitScrollY - curScrollY);            mCurrentStatus = STATUS_IDLE;        }        invalidate();    }    /**     * 執行下拉重新整理     */    private void doRefresh() {        changeHeaderViewStaus();        // 執行重新整理操作        if (mCurrentStatus == STATUS_REFRESHING && mOnRefreshListener != null) {            mOnRefreshListener.onRefresh();        }    }    /**     * 執行下拉(自動)載入更多的操作     */    private void doLoadMore() {        if (mLoadListener != null) {            mLoadListener.onLoadMore();        }    }    /*     * 丈量檢視的寬、高。寬度為使用者設定的寬度,高度則為header, content view, footer這三個子控制元件的高度只和。     * @see android.view.View#onMeasure(int, int)     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int width = MeasureSpec.getSize(widthMeasureSpec);        int childCount = getChildCount();        int finalHeight = 0;        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            // measure            measureChild(child, widthMeasureSpec, heightMeasureSpec);            // 該view所需要的總高度            finalHeight += child.getMeasuredHeight();        }        setMeasuredDimension(width, finalHeight);    }    /*     * 佈局函式,將header, content view,     * footer這三個view從上到下佈局。佈局完成後通過Scroller滾動到header的底部,即滾動距離為header的高度 +     * 本檢視的paddingTop,從而達到隱藏header的效果.     * @see android.view.ViewGroup#onLayout(boolean, int, int, int, int)     */    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int childCount = getChildCount();        int top = getPaddingTop();        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            child.layout(0, top, child.getMeasuredWidth(), child.getMeasuredHeight() + top);            top += child.getMeasuredHeight();        }        // 計算初始化滑動的y軸距離        mInitScrollY = mHeaderView.getMeasuredHeight() + getPaddingTop();        // 滑動到header view高度的位置, 從而達到隱藏header view的效果        scrollTo(0, mInitScrollY);    }    /*     * 滾動監聽,當滾動到最底部,且使用者設定了載入更多的監聽器時觸發載入更多操作.     * @see android.widget.AbsListView.OnScrollListener#onScroll(android.widget.     * AbsListView, int, int, int)     */    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,            int totalItemCount) {        // 使用者設定了載入更多監聽器,且到了最底部,並且是上拉操作,那麼執行載入更多.        if (mLoadListener != null && isBottom() && mScroller.getCurrY() <= mInitScrollY                && mYOffset <= 0                && mCurrentStatus == STATUS_IDLE) {            showFooterView();            doLoadMore();        }    }}
  在建構函式中會呼叫initLayout來新增Header View, Content View, Footer View這三個區域的檢視, 其中Content View就是我們的核心元件,比如ListView、GridView,這個區域的檢視預設寬高都是match_parent的。Header的高度為螢幕寬度的1/4,但它的有效顯示區域只有100畫素,其他的都是paddingTop,這樣就是的內容顯示區域顯示在最下面。這樣當用戶一直下拉時,首先會顯示內容區域,繼續下拉則會顯示PaddingTop區域,此時就達到header view高度被拉伸的效果。如下圖 : 

      

   圖 4               圖5

 不斷下拉,y軸的偏移量不斷減小,使得header越來越多的部分顯示出來。只有白色的內容顯示區域是有效的顯示區,上面的綠色都是paddingTop區,這樣就形成了被拉伸的效果。
 新增這三個view之後,我們在onMeasure中對這幾個子view進行丈量。使得該元件的寬度為使用者設定的寬度,高度為header, content view, footer的高度之和。得到各個子檢視的寬高和該元件的總寬高以後,會進行佈局操作,即會呼叫onLayout方法。我們把這個幾個檢視從上到下排列。最後將該元件在y方向上滾動與header view的高度同樣大小的畫素值,使得header view隱藏掉,使得Content View完全顯示出來。

    /*     * 丈量檢視的寬、高。寬度為使用者設定的寬度,高度則為header, content view, footer這三個子控制元件的高度只和。     * @see android.view.View#onMeasure(int, int)     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int width = MeasureSpec.getSize(widthMeasureSpec);        int childCount = getChildCount();        int finalHeight = 0;        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            // measure            measureChild(child, widthMeasureSpec, heightMeasureSpec);            // 該view所需要的總高度            finalHeight += child.getMeasuredHeight();        }        setMeasuredDimension(width, finalHeight);    }    /*     * 佈局函式,將header, content view,     * footer這三個view從上到下佈局。佈局完成後通過Scroller滾動到header的底部,即滾動距離為header的高度 +     * 本檢視的paddingTop,從而達到隱藏header的效果.     * @see android.view.ViewGroup#onLayout(boolean, int, int, int, int)     */    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int childCount = getChildCount();        int top = getPaddingTop();        for (int i = 0; i < childCount; i++) {            View child = getChildAt(i);            child.layout(0, top, child.getMeasuredWidth(), child.getMeasuredHeight() + top);            top += child.getMeasuredHeight();        }        // 計算初始化滑動的y軸距離        mInitScrollY = mHeaderView.getMeasuredHeight() + getPaddingTop();        // 滑動到header view高度的位置, 從而達到隱藏header view的效果        scrollTo(0, mInitScrollY);    }
  然後就是下拉重新整理觸發點了。在onInterceptTouchEvent方法中,對於ACTION_MOVE事件我們會判斷,如果已經滑到了Content View的頂部,並且還繼續下拉,那麼攔截觸控事件,使得事件轉到onTouchEvent方法中處理。事件攔截的關鍵點如下 :

      case MotionEvent.ACTION_MOVE:                // int yDistance = (int) ev.getRawY() - mYDown;                mYOffset = (int) ev.getRawY() - mLastY;                // 如果拉到了頂部, 並且是下拉,則攔截觸控事件,從而轉到onTouchEvent來處理下拉重新整理事件                if (isTop() && mYOffset > 0) {                    return true;                }                break;
 如果在onTouchEvent中我們根據使用者當前觸控事件的y軸位置與上一次的y軸位置的偏移量來修改該元件在y軸上的滾動值,呼叫的方法為changeScrollY()函式,並且會修改header中的文字內容。當用戶擡起手指時,會判斷使用者在y軸上滑動的距離是否大於header view的1/2, 如果大於header view的1/2那麼為有效的下拉重新整理,此時滾動到剛好顯示header view的內容y軸位置,然後觸發重新整理操作,直到使用者呼叫refreshCompete()位置,最後完全隱藏header。否則視為無效的下拉重新整理操作,然後通過Scroller滾動來隱藏header view。

 而載入更多操作為使用者滑動到了最底部,並且繼續上拉,那麼會觸發載入更多的操作。在操作在onScroll方法中被觸發。

 基本原理就是通過一個ViewGroup來組織header view, content view, footer view, 使它們從上到下排列,並且在初始化時滾動y軸,使得header 和 footer完全隱藏,只顯示content view。使用者下拉或者上拉時,通過判斷是否顯示header 或者 footer, 也是通過Scroller來滾動y軸的偏移量來實現HeaderView, Footer View的顯示和隱藏,不需要修改margin值,這樣效率更高,滾動也更平滑。當用戶的上拉或者下拉操作滿足了條件時,則會觸發相應的操作,即下拉重新整理、上拉載入更多。如有不明白的地方,就對比參考Android打造(ListView、GridView等)通用的下拉重新整理、上拉自動載入的元件吧,原理都差不多。


下拉重新整理的ListView

/** * @author mrsimple */public class RefreshListView extends RefreshLayoutBase<ListView> {    /**     * @param context     */    public RefreshListView(Context context) {        this(context, null);    }    /**     * @param context     * @param attrs     */    public RefreshListView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    /**     * @param context     * @param attrs     * @param defStyle     */    public RefreshListView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    @Override    protected void setupContentView(Context context) {        mContentView = new ListView(context);        // 設定滾動監聽器        mContentView.setOnScrollListener(this);    }    @Override    protected boolean isTop() {        // Log.d(VIEW_LOG_TAG,        // "### first pos = " + mContentView.getFirstVisiblePosition()        // + ", getScrollY= " + getScrollY());        return mContentView.getFirstVisiblePosition() == 0                && getScrollY() <= mHeaderView.getMeasuredHeight();    }    @Override    protected boolean isBottom() {        // Log.d(VIEW_LOG_TAG, "### last position = " +        // contentView.getLastVisiblePosition()        // + ", count = " + contentView.getAdapter().getCount());        return mContentView != null && mContentView.getAdapter() != null                && mContentView.getLastVisiblePosition() ==                mContentView.getAdapter().getCount() - 1;    }}
 需要下拉重新整理的元件只需要實現isTop來判斷是否滑動到最頂端、isBottom是否滑動到最底部,已經通過setupContentView設定mContentView物件即可。

使用示例

        final RefreshListView refreshLayout = new RefreshListView(this);        String[] dataStrings = new String[20];        for (int i = 0; i < dataStrings.length; i++) {