UI实现之下拉刷新:SwipeRefreshLayout PullToRefres

UI实现之下拉刷新:SwipeRefreshLayout PullToRefresh

第一个:SwipeRefreshLayout


SwipeRefreshLayout是一个ViewGroup,使用的时候需要在布局文件里添加子View(mTarget),一般是ListView;下拉的时候把ListView整个往下移动并播放动画;
SwipeRefreshLayout继承ViewGroup;onMeasure()、onLayout()中都没有太多内容,跳过直接看onTouchEvent的ACTION_MOVE代码

 if(this.mDownEvent != null && !this.mReturningToStart) {
                float eventY = event.getY();
                float yDiff = eventY - this.mDownEvent.getY();
                //此处分两步
                //第一步:当刚开始下拉的时候会移动mTarget并且动画慢慢出场,
                //第二步:当下拉到一定程度则mTarget回到原处播放动画
                if(yDiff > (float)this.mTouchSlop) {
                    if(yDiff > this.mDistanceToTriggerSync) {
                    //此为第二步
                        this.startRefresh();//调用播放动画
                        handled = true;
                    } else {

                    //此为第一步
            this.setTriggerPercentage(this.mAccelerateInterpolator.getInterpolation(yDiff / this.mDistanceToTriggerSync));//更新mProgressBar
                        float offsetTop = yDiff;
                        if(this.mPrevY > eventY) {
                            offsetTop = yDiff - (float)this.mTouchSlop;
                        }

                        this.updateContentOffsetTop((int)offsetTop);//移动mTarget
                        if(this.mPrevY > eventY &&this.mTarget.getTop() < this.mTouchSlop) {
                            this.removeCallbacks(this.mCancel);
                        } else {
                            this.updatePositionTimeout();//当手指一定时间不动则重置mTarget和动画
                        }

                        this.mPrevY = event.getY();
                        handled = true;
                    }
                }
            }

简单来说,这几行代码已经可以把SwipeRefreshLayout的大致运行过程表达清楚了。第一步,用户滑动屏幕的时候,判定用户下滑的距离。小于mDistanceToTriggerSync的时候会去下移mTarget(xml布局中,SwipeRefreshLayout下的子View,一般ListView),同时根据下滑距离改变mProgressBar(即顶部不断变长的颜色条)的进度;同时这里还判断了如果下滑时手指不动一定时间了,则重置mTarget、mProgressBar。第二步,当用户下滑距离大于mDistanceToTriggerSync则mTarget重置,同时播放动画。在这里我们只看onTouchEvent,深入一层层的代码具体实现想知道的可以自己跳进去看;具体mTarget是怎么移动的(最终是调用offsetTopAndBottom方法实现移动的)?mProgressBar是怎么运行动画的(在自定义SwipeProgressBar类里,在onDraw里画出来的)?

最后聊下触摸事件的传递,在SwipeRefreshLayout的onInterceptTouchEvent中会把根据相关状态值(是否可以播放动画或者正在播放中)以及是否是下拉来决定是否拦截改事件。

第二:PushToReresh

https://github.com/MarkMjw/PullToRefresh

这个的原理比较容易理解,XListView继承ListView,通过addHeaderView加入XHeaderView,同时设置其高度为0;下拉时改变XHeaderView的高度,同时让XHeaderView播放动画。XScrollView的原理也是一样,但是XScrollView继承自ScrollView没有HeaderView,所以他加了一个布局,相信大家看下就理解了,三个LinearLayout依次就类似于ListView的HeaderView、ListView、FooterView

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">

    <LinearLayout  android:id="@+id/header_layout" android:layout_gravity="center_horizontal|top" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" />

    <LinearLayout  android:id="@+id/content_layout" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" />

    <LinearLayout  android:id="@+id/footer_layout" android:layout_gravity="center_horizontal|bottom" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" />

</LinearLayout>

下面来看看XListView的onTouchEvent的ACTION_MOVE部分

 case MotionEvent.ACTION_MOVE:
                final float deltaY = ev.getRawY() - mLastY;
                mLastY = ev.getRawY();
                //if分支是下拉刷新,else分支是加载更多(不关心)
                if (getFirstVisiblePosition() == 0 && (mHeader.getVisibleHeight() > 0 ||
                        deltaY > 0)) {
                    // the first item is showing, header has shown or pull down.
                    //关键就是这句,改变XHeaderView的高度
                    updateHeaderHeight(deltaY / OFFSET_RADIO);
                    //没找到OnXScrollListener的实现,不知道实际干嘛的;注释掉后没发现有啥不对劲
                    invokeOnScrolling();

                } else if (getLastVisiblePosition() == mTotalItemCount - 1 && (mFooterView
                        .getBottomMargin() > 0 || deltaY < 0)) {
                    // last item, already pulled up or want to pull up.
                    updateFooterHeight(-deltaY / OFFSET_RADIO);
                }
                break;

现在跟进去updateHeaderHeight看看

    private void updateHeaderHeight(float delta) {
        //调用XHeaderView的方法改变它的高度
        mHeader.setVisibleHeight((int) delta + mHeader.getVisibleHeight());
        //设置XHeaderView的状态,告诉它应该执行哪个动画了
        if (mEnablePullRefresh && !mPullRefreshing) {
            // update the arrow image unrefreshing
            if (mHeader.getVisibleHeight() > mHeaderHeight) {
                //Release to refresh部分
                mHeader.setState(XHeaderView.STATE_READY);
            } else {
                //Pull to refresh部分
                mHeader.setState(XHeaderView.STATE_NORMAL);
            }
        }

        // scroll to top each time
        //这个地方大家可以注释掉,看看效果;注释掉后下拉之后再往上拉动的时候不止是XHeaderView的高度会变小,ListView也会往上滑动;而它的作用就是把ListView滚动到顶部,这样就可以避免这个问题的发生
        setSelection(0);
    }

相信到这里,整个下拉的大致过程大家已经差不多清楚了,具体还是要自己去看代码。此外想说的是,XListView继承自ListView两种滑动一个View没有办法通过事件onInterceptTouchEvent来解决所以此处采用了setSelection(0)解决;同样的XScrollView是通过下面这段代码来解决的(原理一样,调用函数不同而已),下面看看XScrollView的updateHeaderHeight函数

  private void updateHeaderHeight(float delta) {
        mHeader.setVisibleHeight((int) delta + mHeader.getVisibleHeight());

        if (mEnablePullRefresh && !mPullRefreshing) {
            // update the arrow image unrefreshing
            if (mHeader.getVisibleHeight() > mHeaderHeight) {
                mHeader.setState(XHeaderView.STATE_READY);
            } else {
                mHeader.setState(XHeaderView.STATE_NORMAL);
            }
        }

        // scroll to top each time
        //就是它了
        post(new Runnable() {
            @Override
            public void run() {
                XScrollView.this.fullScroll(ScrollView.FOCUS_UP);
            }
        });
    }

再者

还有一个地方在ACTION_UP等地方将一切归于最原始的状态等待下一次的下拉刷新;这里就贴代码了,无非就是重置属性值,将mTarget或XHeaderView重置成原始的位置、状态
最后要说的一个地方就是Animation、Scroller、Interpolator的加入,让整个过程更加自然流畅

小白一枚,学习记录,轻喷~~~

附(开始学UML类图的绘制了。。。):
UI实现之下拉刷新:SwipeRefreshLayout PullToRefres_第1张图片

你可能感兴趣的:(android,UI,ListView)