美文网首页Ui
Android做下拉刷新的时候,在做些什么

Android做下拉刷新的时候,在做些什么

作者: 王三的猫阿德 | 来源:发表于2017-08-24 10:38 被阅读442次

    转载注明出处:http://www.jianshu.com/p/4607129c9efa

    1. 简介

    好长时间没有写博客了,一来是工作忙,抽不出空,二来是迷上了王者荣耀。现在正好赶上项目空闲期,写一篇关于下拉刷新的文章,个人觉得上来加载更多功能使用场景非常少,而且没有必要做的那么麻烦,文章最后会提一下加载更多的实现。

    最近项目中遇见了下拉刷新的需求,正好研究了一下,分享一下自己的心得。

    主要参考文章或工程:

    郭霖大神—Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能

    自个儿写Android的下拉刷新/上拉加载控件

    XListView

    这三篇文章各自提供了实现下拉刷新的思路,文章会分别介绍这三种实现方式的优劣。文章中会涉及到点击事件分发知识,大家可以查看这篇文章Android事件分发机制详解。自己写对三种实现做了部分优化,写了demo,地址链接

    2. 分析

    下拉刷新主要分为两部分,一部分是刷新头部Header,一部分是内容展示区域,一般是列表。通过某些方法,来控制刷新头部Header的展示范围,达到下拉刷新的效果,如下图。

    图-1 下拉刷新原理图

    做下拉刷新之前,分析一下下拉刷新场景以及达到的效果,常见的下拉刷新最少有四种状态

    • 正常状态,下拉刷新头部不展示,用户可以正常操作列表
    • 下拉状态,用户下拉列表,但是没有到达刷新时机,松开手后,刷新头部会自动隐藏
    • 松开刷新状态,到达这个状态时候,刷新头部是完全展示的,用户松开手,即可刷新,如果下拉距离过大,列表会自动上移,完整的露出刷新头部,头部显示刷新中文案。
    • 刷新中状态,请求数据的刷新态,在这种状态下,根据交互需求有不同的实现。
      • 刷新中状态,用户不能操作列表
      • 刷新中状态,可以滑动和操作列表,但刷新头部一直置顶
      • 刷新中状态,可以滑动和操作列表,刷新头部会随着列表的滑动而一起滑动,参考欣慰微博下拉刷新。(大部分下拉刷新的交互效果)

    前三种状态会根据用户手势的移动相互切换,大部分下拉刷新中状态交互是第三种,以新浪微博为参考蓝本,本文最终实现的效果也是以这个效果为目标。

    3. 第一个例子链接

    郭霖大神文章篇幅写的比较多,很多可以不用关心,关于下拉刷新的核心代码在ListView的OnTouchListener中,是通过修改Header的MarginTop值控制Header显示可见范围,到达下拉刷新的效果。缺点就是,每次更改Header的MarginTop值时候,会触发父布局重新onMeasure()/onLayout()方法,如果ListView中Item内容比较复杂,有卡顿现象,同时没有处理刷新中状态点击事件,如果要处理,需要额外添加复杂的逻辑。

    3.1 第一个例子实现过程

    3.1.1 初始化Header

    父布局中包含下拉刷新的Header和ListView,在父布局的构造方法中实例化Header,并放入父布中。

    public PtrFirstRefreshableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    
    /**
     * 实例化刷新头部并将刷新头部添加的父布局
     */
    private void init() {
        mHeader = new PtrFirstRefreshHeader(getContext());
        touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        addView(mHeader, 0);
    }
    

    3.1.2 隐藏Header

    在父布局中onLayout()方法中,设置Header的topMarigin,隐藏Header,设置ListView的点击监听器,记录一个标签isLayouted保证只设置一次。

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        
        // 如果是第一次Layout, 做一些设置
        if (changed && !isLayouted) {
            isLayouted = true;
            // 设置刷新头部MarginTop, 隐藏刷新头部
            mHeaderHeight = mHeader.getHeight();
            mHeaderLayoutParams = (LayoutParams) mHeader.getLayoutParams();
            mHeader.setTopMargin(-mHeaderHeight);
    
            // 设置ListView的事件监听
            mListView = (ListView) getChildAt(1);
            mListView.setOnTouchListener(this);
        }
    }
    

    3.1.3 处理点击事件

    这个地方逻辑复杂一些,获取用户点击事件后,调用checkTopShow()方法检查当前是否需要处理点击事件,如果ListView的第一个Item展示,且顶部距离父布局为0,则可以下拉刷新。

    DOWN事件中记录用户起始位置,注意一定要通过getRawY()获取手指相对屏幕的位置,而不是通过getY()获取手指相对ListView的位置,因为ListView会随着手指滑动而滑动,如果用getY()获取位置会有偏差。

    MOVE事件中,如果用户手指向上滑动,且刷新头部是完全隐藏的,不做处理;如果当时非刷新中状态,根据头部MarginTop的值更改当前刷新状态,同时更改刷新头部MarginTop。

    UP事件中,用户松开手,如果当前状态是下拉状态,则隐藏刷新头部;如果当前状态是松开刷新状态,则更改状态为刷新中状态,同是隐藏多余margin,仅显示完整的刷新头部,同时调用回调监听(在RefreshingTask类中)。

    在整个过程中,如果当前状态处于下拉状态或者松开刷新状态,设置ListView属性,让ListView失去焦点,否则那点击Item会一直处于点击状态。

    public boolean onTouch(View v, MotionEvent event) {
        checkTopShow(event);
        if (ableToPull) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDownY = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float yMove = event.getRawY();
                    int distance = (int) (yMove - mDownY);
                    // 如果手指向上滑动,并且下拉头是完全隐藏的,不处理
                    if (distance <= 0 && mHeader.getTopMargin() <= -mHeaderHeight) {
                        return false;
                    }
                    if (distance < touchSlop) {
                        return false;
                    }
                    if (mStatus != STATUS_REFRESHING) {
                        if (mHeader.getTopMargin()  > 0) {
                            mStatus = STATUS_RELEASE_TO_REFRESH;
                        } else {
                            mStatus = STATUS_PULL_TO_REFRESH;
                        }
                        // 通过偏移下拉头的topMargin值,来实现下拉效果
                        int topMargin = (distance / 2) - mHeaderHeight;
                        mHeader.setTopMargin(topMargin);
    
                        // 更新刷新头部圆环动画
                        mHeader.updateCircle(Math.abs(distance * 1f / 2f / mHeaderHeight));
                    }
                    break;
                case MotionEvent.ACTION_UP:
                default:
                    if (mStatus == STATUS_RELEASE_TO_REFRESH) {
                        // 松手时如果是释放立即刷新状态,就去调用正在刷新的任务
                        mStatus = STATUS_REFRESHING;
                        updateHeaderView();
                        new RefreshingTask().execute();
                        mHeader.startLoading();
                    } else if (mStatus == STATUS_PULL_TO_REFRESH) {
                        // 松手时如果是下拉状态,就去调用隐藏下拉头的任务
                        mStatus = STATUS_NORMAL;
                        updateHeaderView();
                        new HideHeaderTask().execute();
                    }
                    break;
            }
    
            if (mStatus == STATUS_PULL_TO_REFRESH ||
                    mStatus == STATUS_RELEASE_TO_REFRESH) {
                mListView.setPressed(false);
                mListView.setFocusable(false);
                mListView.setFocusableInTouchMode(false);
                updateHeaderView();
                return true;
            }
        }
        return false;
    }
    
    
    private void checkTopShow(MotionEvent event) {
        View firstChild = mListView.getChildAt(0);
        if (firstChild != null) {
            // 如果列表第一个item可见且距离ListView顶部为0,则说明ListView已经到最顶部,此时可以下拉刷新
            int firstVisiblePos = mListView.getFirstVisiblePosition();
            if (firstVisiblePos == 0 && firstChild.getTop() == 0) {
                ableToPull = true;
            } else {
                if (mHeader.getTopMargin() != -mHeaderHeight) {
                    mHeader.setTopMargin(-mHeaderHeight);
                }
                ableToPull = false;
            }
        } else {
            ableToPull = true;
        }
    }
    

    3.1.4 隐藏头部

    在用户手指离开屏幕时候,会根据当前状态选择是隐藏头部还是仅展示头部,仅以隐藏头部为例,代码如下。关于AsyncTask的使用可以查看AsyncTask 第一篇使用篇

    class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {
    
        @Override
        protected Integer doInBackground(Void... params) {
            int topMargin = mHeaderLayoutParams.topMargin;
            while (true) {
                topMargin = topMargin + SCROLL_SPEED;
                if (topMargin <= -mHeaderHeight) {
                    topMargin = -mHeaderHeight;
                    break;
                }
                publishProgress(topMargin);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return topMargin;
        }
    
        @Override
        protected void onProgressUpdate(Integer... topMargin) {
            mHeader.setTopMargin(topMargin[0]);
        }
    
        @Override
        protected void onPostExecute(Integer topMargin) {
            mHeader.setTopMargin(topMargin);
        }
    }
    

    3.1.5 请求完毕恢复原状态

    网络请求完成后,要隐藏刷新头部,同时恢复原状态。

    public void finishRefreshing() {
        mStatus = STATUS_NORMAL;
        new HideHeaderTask().execute();
        mHeader.stopLoading();
    }
    

    3.2 第一个例子实现效果

    最终的实现效果如下:

    图-2 第一个例子实现效果图

    3.3 第一个例子总结

    该方案思路清晰,不需要对ListView进行拓展。缺点也比较明显,如果ListView中Item过于复杂,会有卡顿现象,而且代码中并没有对刷新中状态的点击事件进行处理,如果在刷新中状态中,滑动布局,会将刷新头部隐藏,在完成请求之前,无法将头部下拉展出,要对此进行修复,需要添加额外的逻辑。不推荐。

    4. 第二个例子链接

    原文章是使用scrollTo()/scrollBy()方法实现下拉刷新,默认控件向上位移一段距离,正好将刷新头部隐藏。然后根据用户的手势通过scrollBy()方法将刷新头部逐渐展示出来。因为使用scrollTo()/scrollBy()来移动控件,是移动父布局中所有的子控件,如果逻辑处理不当会出现子控件部分移出父布局的情况,子控件显示出现问题。原文章实现很简单,下面实例的代码是做过优化后的代码实现。

    4.1 第二个例子实现过程

    4.1.1 初始化Header

    实例化Header,并将其添加至父布局。

    public PtrSecondRefreshableView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }
    
    private void init(Context context) {
        mHeader = new PtrSecondRefreshHeader(context);
        addView(mHeader, 0);
    
        mScroller = new Scroller(getContext());
    }
    

    4.1.2 隐藏Header

    在父布局的onMeasure()方法中测量子View的大小,在onLayout()方法中将刷新头部向上偏移,达到隐藏Header效果。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
        // 测量子View大小
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
    
        mLayoutContentHeight = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child == mHeader) { // 如果是刷新头部,向上偏移
                child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);
            } else {
                child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), mLayoutContentHeight + child.getMeasuredHeight());
                mLayoutContentHeight += child.getMeasuredHeight();
            }
        }
    }
    

    4.1.3 拦截事件

    在父布局的onTouchEvent中设置Header的可见范围,所以用户手势在操作屏幕时候,在某些情况下父布局需要拦截点击事件。

    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
    
        if(mStatus == STATUS_REFRESHING) {
            return false;
        }
    
        // 记录此次触摸事件的y坐标
        int y = (int) event.getY();
    
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                intercept = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (y > mLastMoveY) { // 下滑操作
                    View child = getChildAt(1);
                    if (child instanceof AdapterView) {
                        AdapterView adapterChild = (AdapterView) child;
                        // 判断AbsListView是否已经到达内容最顶部(如果已经到达最顶部,就拦截事件,自己处理滑动)
                        if (adapterChild.getFirstVisiblePosition() == 0
                                || adapterChild.getChildAt(0).getTop() == 0) {
                            intercept = true;
                        }
                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercept = false;
                break;
            }
        }
    
        mLastMoveY = y;
    
        return intercept;
    }
    

    4.1.4 处理点击事件

    重写父布局的onTouchEvent(),根据用户手势做出相应展示效果。

    public boolean onTouchEvent(MotionEvent event) {
        float nowY = event.getY();
    
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastMoveY = nowY;
                break;
            case MotionEvent.ACTION_MOVE:
                float distance = mLastMoveY - nowY;
                if(distance < 0) { // 如果是向下滑动,移动子View
                    // 头部没有完全展示
                    if(Math.abs(getScrollY()) <= mHeader.getMeasuredHeight()) {
                        mStatus = STATUS_PULL_TO_REFRESH;
                        scrollBy(0, (int) (distance * DRAG_COEFFICIENT_NORMAL));
                    } else { // 头部已经完全展示
                        scrollBy(0, (int) (distance * DRAG_COEFFICIENT_LIMIT));
                        mHeader.updateText(R.string.ptr_refresh_release);
                        mStatus = STATUS_RELEASE_TO_REFRESH;
                    }
                } else { // 如果是向上滑动,移动子View
                    if(getScrollY() < 0) {
                        scrollBy(0, (int) distance);
                    }
                    if(Math.abs(getScrollY()) <= mHeader.getMeasuredHeight()) {
                        mStatus = STATUS_PULL_TO_REFRESH;
                        mHeader.updateText(R.string.ptr_refresh_normal);
                    }
                }
    
                // 更新刷新头部动画
                mHeader.updateCircle(Math.abs(getScrollY() * 1f/ mHeader.getMeasuredHeight()));
    
                break;
            case MotionEvent.ACTION_UP:
            default:
                if(mStatus == STATUS_RELEASE_TO_REFRESH) { // 用户松开手后,如果是松开刷新状态,则回弹显示完整Header,并刷新数据
                    mHeader.updateText(R.string.ptr_refresh_refreshing);
                    mHeader.startLoading();
                    mStatus = STATUS_REFRESHING;
                        
                    // 刷新状态回调
                    if(mListener != null) {
                        mListener.onRefresh();
                    }
    
                    mScroller.startScroll(0, getScrollY(), 0, -(getScrollY() + mHeader.getMeasuredHeight()), 200);
                    invalidate();
                } else if(mStatus == STATUS_PULL_TO_REFRESH){ // 用户松开手后,如果是下拉刷新状态,则隐藏Header
                    mScroller.startScroll(0, getScrollY(), 0, -getScrollY(), 200);
                    invalidate();
                    mHeader.updateText(R.string.ptr_refresh_normal);
                    mStatus = STATUS_NORMAL;
                }
                break;
        }
    
        mLastMoveY = nowY;
        return true;
    }
    

    4.1.5 处理刷新中状态

    onInterceptTouchEvent()方法中,如果是刷新中状态,拦截事件,会导致用户无法操作ListView;如果不拦截事件,则事件会传递到ListView,这样当用户滚动列表ListView时候,刷新头部会一直悬浮在顶部。所以需要在dispatchTouchEvent()方法中处理刷新中状态。

    public boolean dispatchTouchEvent(MotionEvent event) {
        int nowY = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastDownY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if(mStatus == STATUS_REFRESHING) {
                    float distance = mLastDownY - nowY;
                    // 如果手势向下滑动且列表中第一个Item可见,向下移动全部子View
                    if(distance < 0
                            && mListView.getFirstVisiblePosition() == 0
                            && mListView.getChildAt(0).getTop()==0)  {
                        scrollBy(0, (int) (distance * DRAG_COEFFICIENT_LIMIT));
                        isListViewMove = true;
                        mLastDownY = nowY;
    
                        return true;
                    } else { // 如果手势向上滑动
                        if(getScrollY() < 0) { // 当Header没有完全隐藏,移动全部子View;当Header完全隐藏,将事件传递给ListView
                            if(getScrollY() + distance > 0) {
                                scrollBy(0, 0);
                            } else {
                                scrollBy(0, (int) distance);
                            }
                            mLastDownY = nowY;
                            isListViewMove = true;
                            return true;
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            default:
                // 用户抬起手,如果子View通过scrollBy移动过
                if(isListViewMove) {
                    isListViewMove = false;
                    // 如果子View向下移动,向下移动距离大于Header高度,则自动回弹,显示完整Header
                    if(getScrollY() < 0 && Math.abs(getScrollY()) > mHeader.getMeasuredHeight()) {
                        mScroller.startScroll(0, getScrollY(), 0, -(getScrollY() + mHeader.getMeasuredHeight()), 200);
                        invalidate();
                    }
                    return true;
                }
                isListViewMove = false;
                break;
        }
    
        return super.dispatchTouchEvent(event);
    }
    

    4.1.6 请求完毕恢复原状态

    请求完成后,隐藏Header,恢复原状态。

    public void finishRefresh() {
        if(!mScroller.isFinished()) {
            mScroller.abortAnimation();
        }
        scrollTo(0, 0);
    
        mHeader.updateText(R.string.ptr_refresh_normal);
        mHeader.stopLoading();
        mStatus = STATUS_NORMAL;
    }
    

    4.2 第二个例子最终实现效果

    最终效果如下图:

    图-3 第二个例子效果图

    4.3 第二个例子总结

    涉及点击事件的三个方法dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()都有对点击事件不同的处理逻辑。虽然能勉强到达文章开头提到的效果,但是在零界点,特别是刷新头部Header刚好隐藏的零界点,会有卡顿现象,加上处理逻辑比较复杂。不推荐。

    5. 第三个例子链接

    第三个例子是很久的开源项目,不同于前两种实现方式,前面两种都是自定义一个父布局,然后将刷新头部和列表放入其中,第三个例子是直接将刷新头部放在列表ListView的头部,然后动态的设置刷新头部的高度,达到下拉刷新的效果。

    5.1 实现过程

    5.1.1 初始化Header

    在ListView的构造函数中,初始化Heade作为ListView的HeaderView,这里有两点要注意,一是Header的布局文件,因为要动态设置Header的高度,所以布局文件需要嵌套一层,外面一层动态设置高度,里面一层包容所有的Header布局,高度不变;二是因为初始Header是不显示的,想要获取Header的真正高度,要在所有的View初始化以后才能获取。

    public PtrThirdRefreshableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    
    private void init(Context context) {
        mScroller = new Scroller(context, new DecelerateInterpolator());
        
        // 初始化Header,在初始化时候设置高度为0
        mHeader = new PtrThirdRefreshHeader(context);
        mHeaderContainer = mHeader.findViewById(R.id.dgp_header_container);
    
        addHeaderView(mHeader);
    
        mHeader.getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        // 获取Header的完全展示时候的高度
                        getViewTreeObserver().removeGlobalOnLayoutListener(this);
                        mHeaderViewHeight = mHeaderContainer.getHeight();
                        mHeader.setContentHeight(mHeaderViewHeight);
                    }
                });
    }
    

    5.1.2 处理点击事件

    MOVE事件中,根据当前状态,动态更新刷新Header的高度;在UP事件中根据当前Header展示高度,来做相应处理。

    public boolean onTouchEvent(MotionEvent ev) {
        if (mLastY == -1) {
            mLastY = ev.getRawY();
        }
    
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = ev.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float deltaY = ev.getRawY() - mLastY;
                mLastY = ev.getRawY();
                // 如果列表中第一个Item是可见的, 且Header的部分可见或者向下滑动,则动态设置Header高度
                if (getFirstVisiblePosition() == 0
                        && (mHeader.getShowHeight() > 0 || deltaY > 0)) {
                    updateHeaderHeight(deltaY * OFFSET_RADIO);
                    return true;
                }
                break;
            default:
                mLastY = -1;
                // 用户松开手时候,如果列表第一个Item可以见
                if (getFirstVisiblePosition() == 0) {
                    // 如果Header展示的高度大于Header的真正高度,则可刷新
                    if (mHeader.getShowHeight()  > mHeaderViewHeight) {
                        mPullRefreshing = true;
                        mHeader.updateText(R.string.ptr_refresh_refreshing);
                        mHeader.startLoading();
                        if (mRefreshListener != null) {
                            mRefreshListener.onRefresh();
                        }
                    }
                    // 根据当前情况重置Header高度
                    resetHeaderHeight();
                    return true;
                }
                break;
        }
        return super.onTouchEvent(ev);
    }
    
    /**
     * 动态更新Header高度
     * 
     * @param delta
     */
    private void updateHeaderHeight(float delta) {
        mHeader.setShowHeight((int) (delta + mHeader.getShowHeight()));
        if (!mPullRefreshing) {
            if (mHeader.getShowHeight() > mHeaderViewHeight) {
                mHeader.updateText(R.string.ptr_refresh_release);
            } else {
                mHeader.updateText(R.string.ptr_refresh_normal);
            }
        }
        setSelection(0);
    }
    

    5.1.3 请求完毕恢复原状态

    请求完毕后,恢复原状态,这里没有使用设置Header的高度来隐藏Header,为了移动平滑通过Scroller将Header移动到屏幕外,不显示在屏幕中,达到隐藏的目的。使用Scroller需要复写computeScroll()方法,才能移动。

    public void finishRefresh() {
        if (mPullRefreshing == true) {
            mPullRefreshing = false;
            resetHeaderHeight();
            mHeader.stopLoading();
        }
    }
    
    private void resetHeaderHeight() {
        int height = mHeader.getShowHeight();
        if (height == 0)
            return;
        if (mPullRefreshing && height <= mHeaderViewHeight) {
            return;
        }
        int finalHeight = 0;
        // 如果当前是刷新中状态,且Header的展示高度要大于Header的真实高度,则滑动列表,完整展示Header,否则隐藏Header
        if (mPullRefreshing && height > mHeaderViewHeight) {
            finalHeight = mHeaderViewHeight;
        }
        mScrollBack = SCROLL_BACK_HEADER;
        mScroller.startScroll(0, height, 0, finalHeight - height,
                SCROLL_DURATION);
        invalidate();
    }
    
    /**
     * 使用了Scroller, 需要复写该方法
     */
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            if (mScrollBack == SCROLL_BACK_HEADER) {
                mHeader.setShowHeight(mScroller.getCurrY());
            }
            postInvalidate();
        }
        super.computeScroll();
    }
    

    5.2 第三个例子最终实现效果

    最终效果如下图:

    图-4 第三个例子效果图

    5.3 第三个例子总结

    相比前两种实现方法,第三种是最简单最方便的实现方式,而且完全不用考虑刷新中状态的点击事件处理,唯一的缺点可能要对某些手机做一些适配,个人比较推荐。

    6. 上拉加载

    这三个例子中,后面两个例子都实现了上拉加载更多,而在市面上大部分应用没有上拉加载,看得出在实际场景中,上拉加载更多的使用频率不高。以大量使用列表的应用新浪微博为例子,滑动到列表最下方,继续向上拉时候不会像下拉刷新一样,有一定拉伸的弹簧效果,而是直接在加载了。我猜测这种实现是在Adapter中,当滑动到最后一个Item时候,直接返回一个加载中的View,同时请求数据,当用户看见这个View时候,其实请求已经发出去了(部分应用是设置一个按钮,然用户手动点击请求数据)。

    7. 总结

    下拉刷新开源库很多,上面列举出的几种实现可能不是最优的,个人认为最好的下拉刷新库是这个下拉刷新库,它基本支持所有的布局。但是在选择使用哪一个开源库的时候,并不是实现的最全最好的那个,而是最贴合实际业务的库。

    在做下拉刷新时候,因为没有去对这个功能做出具体分析,走了很多弯路,浪费很多时间,以此为戒。

    最后附上三个例子工程地址:https://github.com/Kyogirante/PtrDemo

    相关文章

      网友评论

        本文标题:Android做下拉刷新的时候,在做些什么

        本文链接:https://www.haomeiwen.com/subject/iybsdxtx.html