帶你一步一步實(shí)現(xiàn)listview上拉加載下拉刷新

帶你一步一步實(shí)現(xiàn)listview上拉加載下拉刷新

思路:

  • 要是實(shí)現(xiàn)這個(gè)效果我們需要,有一個(gè)view能在頭部和底部存在(通過查找的只listview有addheard,和addfoot的方法可以將一個(gè)view添加到頭部的底部)
  • 如何進(jìn)來的時(shí)候不顯示頭部的view呢?(我門可以設(shè)置View的初始高度為0,或者設(shè)置setPadding設(shè)置他距離上面的距離為-的高度就可以隱藏了,我們使用setPadding方法)
  • 如何隱藏底部的view?(同上)
  • 如何能讓view跟著我們的手勢移動(dòng)呢?(第一種我們可以使用OverScroller這個(gè)類,第二種我們可以通過動(dòng)態(tài)的改變頭部view和底部view的高度來實(shí)現(xiàn)移動(dòng)的的效果,我們使用后者)
  • 如何實(shí)現(xiàn)view的自動(dòng)回彈呢?(我們可以使用OverScroller這個(gè)類的startScroll方法,然后實(shí)現(xiàn)computeScroll方法在這個(gè)里面去動(dòng)態(tài)的改變view的高度)

第一步我們創(chuàng)建HeaderView這個(gè)類當(dāng)頂部的刷新的view


public class HeaderView extends LinearLayout {
    /** 刷新狀態(tài) */
    private LoadState mState = LoadState.NORMAL;

    private View mHeader = null;
    private ImageView mArrow = null;
    private ProgressBar mProgressBar = null;
    private TextView mRefreshTips = null;
    private TextView mRefreshLastTime = null;
    private RotateAnimation mRotateUp = null;
    private RotateAnimation mRotateDown = null;
    private final static int ROTATE_DURATION = 250;

    /** 一分鐘的毫秒值,用于判斷上次的更新時(shí)間. */
    private final long ONE_MINUTE = 60 * 1000;
    /** 一小時(shí)的毫秒值,用于判斷上次的更新時(shí)間. */
    private final long ONE_HOUR = 60 * ONE_MINUTE;
    /** 一天的毫秒值,用于判斷上次的更新時(shí)間. */
    private final long ONE_DAY = 24 * ONE_HOUR;
    /** 一月的毫秒值,用于判斷上次的更新時(shí)間. */
    private final long ONE_MONTH = 30 * ONE_DAY;
    /** 一年的毫秒值,用于判斷上次的更新時(shí)間. */
    private final long ONE_YEAR = 12 * ONE_MONTH;

    public HeaderView(Context context) {
        this(context, null);
    }

    public HeaderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initHeaderView(context);
    }

    private void initHeaderView(Context context) {
        LinearLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
        mHeader = LayoutInflater.from(context).inflate(R.layout.g_refresh_header, null);
        addView(mHeader, lp);
        setGravity(Gravity.BOTTOM);
        mArrow = (ImageView) mHeader.findViewById(R.id.ivArrow);
        mProgressBar = (ProgressBar) mHeader.findViewById(R.id.pbWaiting);
        mRefreshTips = (TextView) mHeader.findViewById(R.id.refresh_tips);
        mRefreshLastTime = (TextView) mHeader.findViewById(R.id.refresh_last_time);
        //初始化旋轉(zhuǎn)的動(dòng)畫需要反轉(zhuǎn)箭頭
        mRotateUp = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateUp.setDuration(ROTATE_DURATION);
        mRotateUp.setFillAfter(true);

        mRotateDown = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateDown.setDuration(ROTATE_DURATION);
        mRotateDown.setFillAfter(true);
        Log.e("HeaderView","HeaderView初始化了");
    }

    public void setHeaderState(LoadState state) {
        if (mState == state) {
            return;
        }
        mArrow.clearAnimation();
        if (state == LoadState.LOADING) {
            mArrow.setVisibility(View.GONE);
            mProgressBar.setVisibility(View.VISIBLE);
        } else {
            mProgressBar.setVisibility(View.GONE);
            mArrow.setVisibility(View.VISIBLE);
        }

        switch (state) {
        case NORMAL:
            mArrow.startAnimation(mRotateDown);
            mRefreshTips.setText(R.string.g_pull_down_for_refresh);
            break;

        case WILL_RELEASE:
            //旋轉(zhuǎn)當(dāng)前的箭頭的狀態(tài)
            mArrow.startAnimation(mRotateUp);
            mRefreshTips.setText(R.string.g_release_for_refresh);
            break;

        case LOADING:
            mRefreshTips.setText(R.string.g_refreshing);
            break;

        default:
            break;
        }

        mState = state;
    }

    public LoadState getCurrentState() {
        return mState;
    }

    public void setHeaderHeight(int height) {
        if (height <= 0) {
            height = 0;
        }
        LayoutParams lp = (LayoutParams) mHeader.getLayoutParams();
        lp.height = height;
        mHeader.setLayoutParams(lp);
    }

    public int getHeaderHeight() {
        return mHeader.getHeight();
    }



我們可以看到HeaderView這個(gè)類很簡單,就是初始化一個(gè)view,然后提供了設(shè)置高度,和不同狀態(tài)的判斷顯示的不同字體的邏輯

我們創(chuàng)建FooterView這個(gè)類其實(shí)和heardview一樣

        
/**
 * 底部
 */
public class FooterView extends LinearLayout {
    /** 加載更多. */
    private LoadState mState = LoadState.NORMAL;

    private View mFooter = null;
    private ImageView mArrow = null;
    private ProgressBar mProgressBar = null;
    private TextView mLoaderTips = null;

    private RotateAnimation mRotateUp = null;
    private RotateAnimation mRotateDown = null;
    private final static int ROTATE_DURATION = 250;

    public FooterView(Context context) {
        this(context, null);
    }

    public FooterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initFooterView(context);
    }

    private void initFooterView(Context context) {
        LinearLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
        mFooter = LayoutInflater.from(context).inflate(R.layout.g_loader_footer, null);
        addView(mFooter, lp);

        mArrow = (ImageView) mFooter.findViewById(R.id.ivLoaderArrow);
        mProgressBar = (ProgressBar) mFooter.findViewById(R.id.pbLoaderWaiting);
        mLoaderTips = (TextView) mFooter.findViewById(R.id.loader_tips);

        mRotateDown = new RotateAnimation(0.0f, 180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateDown.setDuration(ROTATE_DURATION);
        mRotateDown.setFillAfter(true);

        mRotateUp = new RotateAnimation(180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateUp.setDuration(ROTATE_DURATION);
        mRotateUp.setFillAfter(true);

        show();
    }

    public void setFooterState(LoadState state) {
        if (mState == state) {
            return;
        }

        mArrow.clearAnimation();
        if (state == LoadState.LOADING) {
            mProgressBar.setVisibility(View.VISIBLE);
            mArrow.setVisibility(View.GONE);
        } else if (state == LoadState.NODATA) {
            mProgressBar.setVisibility(View.GONE);
            mArrow.setVisibility(View.GONE);
        } else {
            mProgressBar.setVisibility(View.GONE);
            mArrow.setVisibility(View.VISIBLE);
        }

        switch (state) {
        case NORMAL:
            mArrow.startAnimation(mRotateUp);
            mLoaderTips.setText(R.string.g_pull_up_for_more);
            break;

        case WILL_RELEASE:
            mArrow.startAnimation(mRotateDown);
            mLoaderTips.setText(R.string.g_release_for_more);
            break;

        case LOADING:
            mLoaderTips.setText(R.string.g_loading);
            break;

        case NODATA:
            mLoaderTips.setText(R.string.g_nodata);
            break;

        default:
            break;
        }
        mState = state;
    }

    public LoadState getCurrentState() {
        return mState;
    }

    public void setFooterHeight(int height) {
        if (height <= 0) {
            height = 0;
        }

        LayoutParams lp = (LayoutParams) mFooter.getLayoutParams();
        lp.height = height;
        mFooter.setLayoutParams(lp);
    }

    public int getFooterHeight() {
        return mFooter.getHeight();
    }

}


邏輯也是一樣,設(shè)置高度,提供不同狀態(tài)的判斷,顯示不同的內(nèi)容

接下來上我們的主要類XrefershListview



/**
 * 作者:liujingyuan on 2015/12/21 13:14
 * 郵箱:906514731@qq.com
 * 自定義的下拉刷新上拉加載的Listview
 */
public class XrefershListview extends ListView implements AbsListView.OnScrollListener {
    private final static String TAG = "[XrefershListview]";
    private final static float OFFSET_Y = 0.7f;
    private OverScroller mScroller;
    private final static int SCROLL_HEADER = 0;
    private final static int SCROLL_FOOTER = 1;
    //刷新的監(jiān)聽
    public XrefershListviewListener mListViewListener;
    /** 滑動(dòng)項(xiàng). */
    private int iScrollWhich = SCROLL_HEADER;
    //是否顯示footview
    public boolean isShowLoadeFooterView=false;
    HeaderView mHeaderView;
    FooterView mFooterView;
    int   iHeaderHeight;
    int iFooterHeight;
    float EndRawy;
    float mStartY;
    float dy;
    private float mLastY;

    public XrefershListview(Context context) {
        super(context);
        initHeardView(context);
    }

    public XrefershListview(Context context, AttributeSet attrs) {
        super(context, attrs);
        initHeardView(context);
    }

    public XrefershListview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initHeardView(context);
    }
    //初始化頭部的view
    private void initHeardView(Context context) {
        //初始化scmScroller
        mScroller = new OverScroller(context,new DecelerateInterpolator());
        mHeaderView = new HeaderView(context);
        mFooterView = new FooterView(context);
        //初始化hearview的高度
        mHeaderView.setHeaderHeight(200);
        //初始化footview
        initFooterView(context);
        final RelativeLayout header_content = (RelativeLayout) mHeaderView.findViewById(R.id.header_content);
        addHeaderView(mHeaderView);
        this.setOnScrollListener(this);
        //監(jiān)聽到view加載完畢獲取view的高度
        mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                iHeaderHeight =mHeaderView.getMeasuredHeight();
                Log.d(TAG, "iHeaderHeight = " + iHeaderHeight);
                //剛進(jìn)來的時(shí)候我們隱藏heardview
                mHeaderView.setPadding(0,-iHeaderHeight,0,0);
                getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //按下的時(shí)候我們需要記住起始點(diǎn)的Y軸的坐標(biāo)
                mLastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //移動(dòng)的時(shí)候獲取當(dāng)前的y軸的坐標(biāo)
                mStartY = ev.getY();
                //計(jì)算滑動(dòng)的距離
                dy=mStartY-mLastY;
                //再次獲取一次,因?yàn)槲覀兪窃隽吭黾訉挾鹊?,每次獲取的是相對(duì)于上一點(diǎn)的位移
                mLastY = ev.getY();
                if (getFirstVisiblePosition()==0&& (mHeaderView.getMeasuredHeight() > 0 || dy > 0)){
                    //通過改變heardview的高度
                    upDataHeardView(dy*0.7f);
                }else if (getLastVisiblePosition() == getCount() - 1 && (mFooterView.getFooterHeight() > 0 || dy < 0)){
                    //通過改變footview的高度
                    updateFooterState(-dy);
                }
                Log.e(TAG,"dy============"+dy);
               break;
            case MotionEvent.ACTION_UP:
                EndRawy = ev.getRawY();
                //如果當(dāng)前的可見的第一個(gè)條目是0,并且當(dāng)前的hreadview的高度不為0,滑動(dòng)的距離大于0就證明是在下拉刷新
                if (getFirstVisiblePosition()==0&& (mHeaderView.getMeasuredHeight() > 0 || dy > 0)){
                    //判斷當(dāng)前的狀態(tài)不是正在刷新的狀態(tài)就設(shè)置為刷新的狀態(tài)
                    if (mHeaderView.getCurrentState() != LoadState.LOADING) {
                        if (mHeaderView.getHeaderHeight() > iHeaderHeight) {
                            mHeaderView.setHeaderState(LoadState.LOADING);
                            //調(diào)用是界面的刷新,延時(shí)1秒
                            mListViewListener.onRefresh();
                            new Handler().postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    resetHeaderHeight();
                                }
                            },1000);
                        }
                    }
                 //如果當(dāng)前已經(jīng)顯示到最后一個(gè)條目了就是上拉加載,并且當(dāng)前footview的高度大于0,移動(dòng)的距離也大于0
                }else if(getLastVisiblePosition() == getCount() - 1 && (mFooterView.getFooterHeight() > 0 || dy < 0)){
                    //判斷當(dāng)前的狀態(tài)不是正在刷新的狀態(tài)就設(shè)置為刷新的狀態(tài)
                    if (mFooterView.getCurrentState() != LoadState.LOADING) {
                        if (mFooterView.getFooterHeight() > iFooterHeight) {
                            mFooterView.setFooterState(LoadState.LOADING);
                            new Handler().postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    //調(diào)用是界面的刷新,延時(shí)1秒
                                    mListViewListener.onLoadMore();
                                    resetFooter();
                                }
                            },3000);
                        }
                    }
                }
                break;
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }
    /**
     * 更新底部footview的狀態(tài)
     *
     * @param delta
     */
    private void updateFooterState(float delta) {
        if (null == mFooterView) {
            return;
        }
        //設(shè)置footview的高度移動(dòng)的時(shí)候,這個(gè)高度是增加每次移動(dòng)的點(diǎn)相當(dāng)于上一個(gè)點(diǎn)的位置的距離
        mFooterView.setFooterHeight((int) (delta + mFooterView.getFooterHeight()));
        //當(dāng)前的狀態(tài)不是在刷新的狀態(tài)!
        if (mFooterView.getCurrentState() != LoadState.LOADING) {
            if (mFooterView.getFooterHeight() > iFooterHeight) {
                //設(shè)置當(dāng)前的顯示的內(nèi)容為松開加載更多
                mFooterView.setFooterState(LoadState.WILL_RELEASE);
            } else {
                mFooterView.setFooterState(LoadState.NORMAL);
            }
        }
    }
    /**
     * 初始化底部footview
     */
    private void initFooterView(Context context) {
        mFooterView = new FooterView(context);
        mFooterView.setFooterHeight(200);
        //隱藏footview
        mFooterView.setPadding(0,0,0,-200);
        addFooterView(mFooterView);
        mFooterView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //獲取測量的footview的高度
                iFooterHeight = mFooterView.getMeasuredHeight();
                getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
    }
    /**
     * 重置footview
     */
    private void resetFooter() {
        //重置刷新狀態(tài)
        mFooterView.setFooterState(LoadState.NORMAL);
        if (null == mFooterView) {
            return;
        }
        int height = mFooterView.getMeasuredHeight();
        if (height == 0) {
            return;
        }
        int finalHeight = 0;
        if (height > iFooterHeight && mFooterView.getCurrentState() != LoadState.NORMAL) {
            finalHeight = iFooterHeight;
        } else if (mFooterView.getCurrentState() == LoadState.LOADING) {
            return;
        }
        iScrollWhich = SCROLL_FOOTER;
        mScroller.startScroll(0, height, 0, finalHeight - height, 300);
        invalidate();
    }
    /**
     * 重置heardview的狀態(tài)
     */
    private void resetHeaderHeight() {
        //重置刷新的狀態(tài)
        mHeaderView.setHeaderState(LoadState.NORMAL);
        //獲取測量得到的heardview的高度
        int height = mHeaderView.getMeasuredHeight();
        //如果獲取當(dāng)前的View的高度等于0代表是沒有移動(dòng)就不做處理
        if (height == 0) // not visible.
            return;
        int finalHeight = 0;
        //如果超過HeaderView高度,則回滾到HeaderView高度即可
        if (height > iHeaderHeight && mHeaderView.getCurrentState() != LoadState.NORMAL) {
            finalHeight = iHeaderHeight;
        }
        //標(biāo)記當(dāng)前是heardview在移動(dòng)
        iScrollWhich = SCROLL_HEADER;
        //inalHeight - height, finalHeight代表heardview抬起的時(shí)候高度-heardview初始化的高度=heardview在y軸上移動(dòng)的距離,280毫秒內(nèi)移動(dòng)完畢
        //int startX,開始的位置
        // int mLastY,y位置開始的位置
        // int dx, x滑動(dòng)的距離
        // int dy, y滑動(dòng)的距離
        // int duration執(zhí)行完畢需要的時(shí)間
        mScroller.startScroll(0, height, 0, finalHeight - height, 300);
        //手動(dòng)調(diào)用刷新移動(dòng)
        invalidate();
    }
    /**
     * 設(shè)置頭部的view的高度,來達(dá)到下移的效果
     */
    private void upDataHeardView(float dy) {
        //如果當(dāng)前的狀態(tài)不是正在加載中,就改變狀態(tài)
        if (mHeaderView.getCurrentState() != LoadState.LOADING) {
            //如果當(dāng)前的heardview的高度大于原始的高度就代表用戶下拉刷新了要該表狀態(tài),變成下拉刷新的狀態(tài)
            if (mHeaderView.getHeaderHeight() > iHeaderHeight) {
                mHeaderView.setHeaderState(LoadState.WILL_RELEASE);
            } else {
                mHeaderView.setHeaderState(LoadState.NORMAL);
            }
        }
        //移動(dòng)距離等于move的距離加上heardview的高度
        mHeaderView.setHeaderHeight((int) (dy + mHeaderView.getHeaderHeight()));
    }

    /**
     * 設(shè)置刷新的監(jiān)聽
     */
    public void setXrefershListviewListener(XrefershListviewListener l) {
        mListViewListener = l;
    }
    //每次移動(dòng)都會(huì)調(diào)用computeScroll
    @Override
    public void computeScroll() {
        //判斷是否滑動(dòng)結(jié)束
        if (mScroller.computeScrollOffset()) {
            //代表是下拉刷新
            if (iScrollWhich == SCROLL_HEADER) {
                //獲取到當(dāng)前滾動(dòng)的位置,來動(dòng)態(tài)的設(shè)置mHeaderView的高度
                mHeaderView.setHeaderHeight(mScroller.getCurrY());
                //代表是上拉加載改變footview
            } else if (iScrollWhich == SCROLL_FOOTER) {
                //不斷去改變footview的高度
                mFooterView.setFooterHeight(mScroller.getCurrY());
            }
            invalidate();
        }
    }
    //監(jiān)聽listview的滑動(dòng)監(jiān)聽
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }
    //監(jiān)聽listview的滑動(dòng)監(jiān)聽
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    }
}

上面的注釋也非常的清楚,大體思路:

  • 首先我們直接初始化HeaderView和FooterView然后設(shè)置她們的默認(rèn)高度為200dp
  • addHeaderView然后我們添加頭部的view
  • 設(shè)置addOnGlobalLayoutListener的監(jiān)聽mHeaderView.getMeasuredHeight();在回調(diào)的方法中獲取測量的heardview的高度
  • mHeaderView.setPadding(0,-iHeaderHeight,0,0);這步我們就隱藏了heardview,設(shè)置他距離上面的距離為-iHeaderHeight
  • 然后我們重寫onTouchEvent來實(shí)現(xiàn)具體的移動(dòng)的邏輯,首先我們在ACTION_DOWN里面獲取當(dāng)前的坐標(biāo),然后在ACTION_MOVE中我們?nèi)ビ?jì)算滑動(dòng)的距離,詳情看代碼的注釋
  • 通過 upDataHeardView(dy*0.7f);這個(gè)方法我們實(shí)現(xiàn)頭部view下拉的效果
 /**
     * 設(shè)置頭部的view的高度,來達(dá)到下移的效果
     */
    private void upDataHeardView(float dy) {
        //如果當(dāng)前的狀態(tài)不是正在加載中,就改變狀態(tài)
        if (mHeaderView.getCurrentState() != LoadState.LOADING) {
            //如果當(dāng)前的heardview的高度大于原始的高度就代表用戶下拉刷新了要該表狀態(tài),變成下拉刷新的狀態(tài)
            if (mHeaderView.getHeaderHeight() > iHeaderHeight) {
                mHeaderView.setHeaderState(LoadState.WILL_RELEASE);
            } else {
                mHeaderView.setHeaderState(LoadState.NORMAL);
            }
        }
        //移動(dòng)距離等于move的距離加上heardview的高度
        mHeaderView.setHeaderHeight((int) (dy + mHeaderView.getHeaderHeight()));
    }

  • 然后我們在看ACTION_UP,抬起的時(shí)候我們?nèi)ヅ袛喈?dāng)前的是否是下拉刷新,如果是就直接調(diào)用onRefresh是刷新界面的數(shù)據(jù)在resetHeaderHeight我們重置頭部view的狀態(tài)
 /**
     * 重置heardview的狀態(tài)
     */
    private void resetHeaderHeight() {
        //重置刷新的狀態(tài)
        mHeaderView.setHeaderState(LoadState.NORMAL);
        //獲取測量得到的heardview的高度
        int height = mHeaderView.getMeasuredHeight();
        //如果獲取當(dāng)前的View的高度等于0代表是沒有移動(dòng)就不做處理
        if (height == 0) // not visible.
            return;
        int finalHeight = 0;
        //如果超過HeaderView高度,則回滾到HeaderView高度即可
        if (height > iHeaderHeight && mHeaderView.getCurrentState() != LoadState.NORMAL) {
            finalHeight = iHeaderHeight;
        }
        //標(biāo)記當(dāng)前是heardview在移動(dòng)
        iScrollWhich = SCROLL_HEADER;
        //inalHeight - height, finalHeight代表heardview抬起的時(shí)候高度-heardview初始化的高度=heardview在y軸上移動(dòng)的距離,280毫秒內(nèi)移動(dòng)完畢
        //int startX,開始的位置
        // int mLastY,y位置開始的位置
        // int dx, x滑動(dòng)的距離
        // int dy, y滑動(dòng)的距離
        // int duration執(zhí)行完畢需要的時(shí)間
        mScroller.startScroll(0, height, 0, finalHeight - height, 300);
        //手動(dòng)調(diào)用刷新移動(dòng)
        invalidate();
    }

  • 我們需要重寫computeScroll這個(gè)方法來實(shí)現(xiàn)阻尼的效果
     @Override
    public void computeScroll() {
        //判斷是否滑動(dòng)結(jié)束
        if (mScroller.computeScrollOffset()) {
            //代表是下拉刷新
            if (iScrollWhich == SCROLL_HEADER) {
                //獲取到當(dāng)前滾動(dòng)的位置,來動(dòng)態(tài)的設(shè)置mHeaderView的高度
                mHeaderView.setHeaderHeight(mScroller.getCurrY());
                //代表是上拉加載改變footview
            } else if (iScrollWhich == SCROLL_FOOTER) {
                //不斷去改變footview的高度
                mFooterView.setFooterHeight(mScroller.getCurrY());
            }
            invalidate();
        }
    }

底部的刷新邏輯和頭部的都一樣,這樣我們就實(shí)現(xiàn)了上拉刷新下拉加載的效果了!
代碼地址點(diǎn)擊這

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容