PullToRefresh的学习研究(二)

注意到里面好多类似这样的方法public void setReleaseLabel(CharSequence releaseLabel, Mode mode) {
        getLoadingLayoutProxy(mode.showHeaderLoadingLayout(),
                mode.showFooterLoadingLayout()).setReleaseLabel(releaseLabel);
    },这些都是ILoadingLayout这个接口的方法,看来以前代码设计者是把状态改变时的lable改变写在了基类里,然后子类直接调用方法来改变的。但是现在应该没有什么用了,因为headlayout是继承的LoadingLayout,而LoadingLayout实现了IloadingLayout接口。只需要在自定义的LoadingLayout里实现方法就可以了,基类会在适当的时机调用headlayout.setxxxlabel方法,而不是调用自己的setxxxLable了。全部删除后demo中只有一个ListAct有错误mPullRefreshListView.setOnRefreshListener(new OnRefreshListener<ListView>() {
            @Override
            public void onRefresh(PullToRefreshBase<ListView> refreshView) {
                String label = DateUtils.formatDateTime(getApplicationContext(), System.currentTimeMillis(),
                        DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL);

                // Update the LastUpdatedLabel
//                refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);
                refreshView.getHeaderLayout().setLastUpdatedLabel(label);
                // Do work to refresh the list here.
                new GetDataTask().execute();
            }
        });

    他在回调中还是使用的refreshView.getLoadingLayoutProxy().这里建议将基类原来的getHeaderLayout()设置为public,这样也为自定义的LoadingLayout做准备,也可以封装一部分常用的属性在基类中,比如setLastUpdatedLabel(label){内部调用headlayout去处理}其实LoadingLayout和基类之间的关系还是有点“深”,后面尝试分离一下,否则这样虽然有 一定灵活度,但其实还是定死了几个东西,比如你即使自定义,也必须有一堆很必须的值,比如updateText,可能有些时候你并不需要它,demo中在不需要的时候,其实只是没有设置text,所以就看不到。但不代表控件本身不存在。我觉得可以在LoadingLayout中只实现state周期必须的几个东西,然后在子类中去设置文本之类的操作,嗯,这些读者们自行考虑吧,其实感觉最重要的是接下来要看的ontouch事件,以及各个子类对临界值的判断。如此则可以写出更多灵活的代码,比如有些app,下拉的时候,从上面出来个发条,但是listview本身并不动,这样的情况使用博文介绍的组件是不能实现的,当然也许可以通过变动来实现,比如上篇说了,是通过padding定位header,然后通过layout自己的scrollto属性来呈现下拉的。那么问题来了,挖掘技术哪家强?。。我去,是想实现listview不动的情况应该怎么办?学会了这里的逻辑,完全可以继承一个RelativeLayout了,或者尝试改变lsitview和hear之间的margain试试?(但线性布局,它会盖着header。。。)感觉应该使用RelativeLayout,这样感觉好一点,方便操控,学着学着就想推倒了。言归正传,先把核心学到手,后面随意优化(其实也不难,使用layout(int top,int left...)来替换掉scrollto就可以了,scrollto在这里其实占了个大便宜而已,带来的劣势也看到了,不方便重叠布局Head)。

大家自行复习  onInterceptTouchEvent 和onTouch吧,或者以后我会补一篇相关文章。总结就是onInterceptTouchEvent从父类向子类传递,遇到true则停止并交由当时的子类来执行onTouch,ontouch开始反传,遇true则自己消耗掉事件及以后的事件。

@Override
    public final boolean onInterceptTouchEvent(MotionEvent event) {

        if (!isPullToRefreshEnabled()) {
            return false;// 如果设置为不能fresh,则不拦截,交给子view处理,即所有touch事件交给了listview,上下的headlayout等也一直invisiable
        }

        final int action = event.getAction();

        if (action == MotionEvent.ACTION_CANCEL
                || action == MotionEvent.ACTION_UP) {
            mIsBeingDragged = false;
            return false;
        }

        // 动作不是按下,不是松手(差不多也就剩下move了)并且当前在拖动状态,则拦截给自己的ontouch处理
        if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
            return true;//这里就会跳转到自己的ontouch,同时所有的子view都不再可能接收ontouch事件了
        }

        // mIsBeingDragged默认是false的,所以,当第一次move并且未将mIsBeingDragged设置true时,就会执行这里

        switch (action) {
        case MotionEvent.ACTION_MOVE: {
            // If we're refreshing, and the flag is set. Eat all MOVE events
            // 如何设置了 刷新中不能滑动,并且当前在刷新中,则拦截处理,这样listview就接收不到事件了,自然也就不能滑动了
            if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
                return true;
            }

            if (isReadyForPull()) {
                final float y = event.getY(), x = event.getX();
                final float diff, oppositeDiff, absDiff;

                // We need to use the correct values, based on scroll
                // direction
                switch (getPullToRefreshScrollDirection()) {
                case HORIZONTAL:
                    diff = x - mLastMotionX;
                    oppositeDiff = y - mLastMotionY;
                    break;
                case VERTICAL:
                default:
                    diff = y - mLastMotionY;
                    oppositeDiff = x - mLastMotionX;
                    break;
                }
                absDiff = Math.abs(diff);

                // 移动距离超过了判断为滑动的最小距离mtouchslop..并且(按纵向分析)(纵向的移动距离大于横向 ||
                // !mFilterTouchEvents)
                if (absDiff > mTouchSlop
                        && (!mFilterTouchEvents || absDiff > Math
                                .abs(oppositeDiff))) {
                    if (mMode.showHeaderLoadingLayout() && diff >= 1f
                            && isReadyForPullStart()) {// 如果当前可以显示header(其实就是看mModel是可以下拉刷新或者上下都可以拉的情况),并且移动差距>1f(向下拉动),并且子类判断已经准备好可以开始下拉了(比如listivew的话第一个item已经到头了)//这里重点就两个,一是分辨出当前是在向上还是向下做运动,二是子类要知道现在是否到顶或底了
                        mLastMotionY = y;
                        mLastMotionX = x;
                        mIsBeingDragged = true;
                        if (mMode == Mode.BOTH) {
                            mCurrentMode = Mode.PULL_FROM_START;
                        }
                    } else if (mMode.showFooterLoadingLayout() && diff <= -1f
                            && isReadyForPullEnd()) {
                        mLastMotionY = y;
                        mLastMotionX = x;
                        mIsBeingDragged = true;
                        if (mMode == Mode.BOTH) {
                            mCurrentMode = Mode.PULL_FROM_END;
                        }
                    }
                }
            }
            break;
        }
        case MotionEvent.ACTION_DOWN: {
            if (isReadyForPull()) {
                mLastMotionY = mInitialMotionY = event.getY();
                mLastMotionX = mInitialMotionX = event.getX();
                mIsBeingDragged = false;
            }
            break;
        }
        }

        return mIsBeingDragged;
    }


@Override
    public final boolean onTouchEvent(MotionEvent event) {

        if (!isPullToRefreshEnabled()) {
            return false;
        }

        // If we're refreshing, and the flag is set. Eat the event
        if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
            return true;
        }

        if (event.getAction() == MotionEvent.ACTION_DOWN
                && event.getEdgeFlags() != 0) {
            //getEdgeFlags():当事件类型是ActionDown时可以通过此方法获得,手指触控开始的边界. 如果是的话,有如下几种值:EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM
            return false;
        }

        switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE: {
            if (mIsBeingDragged) {
                mLastMotionY = event.getY();
                mLastMotionX = event.getX();
                pullEvent();
                return true;
            }
            break;
        }

        case MotionEvent.ACTION_DOWN: {
            if (isReadyForPull()) {
                mLastMotionY = mInitialMotionY = event.getY();
                mLastMotionX = mInitialMotionX = event.getX();
                return true;
            }
            break;
        }

        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP: {
            if (mIsBeingDragged) {
                mIsBeingDragged = false;

                if (mState == State.RELEASE_TO_REFRESH
                        && (null != mOnRefreshListener || null != mOnRefreshListener2)) {
                    setState(State.REFRESHING, true);
                    return true;
                }

                // If we're already refreshing, just scroll back to the top
                if (isRefreshing()) {
                    smoothScrollTo(0);
                    return true;
                }

                // If we haven't returned by here, then we're not in a state
                // to pull, so just reset
                setState(State.RESET);

                return true;
            }
            break;
        }
        }

        return false;
    }

ontouch里面最重要的两个个方法就是:(擦,粘两个方法的代码就超出字数限制了)

1、//基本就是按滑动的位置来改变状态,不同的state也会在LoadingLayout里有相关的state设置,比如protected void onPullToRefresh() {
        switch (mCurrentMode) {
        case PULL_FROM_END:
            mFooterLayout.pullToRefresh();
            break;
        case PULL_FROM_START:
            mHeaderLayout.pullToRefresh();
            break;
        default:
            // NO-OP
            break;
        }
    }

在调用这个方法之前的move里面会调用onpullevent(),里面会调用 mFooterLayout.onPull(scale);这个是把当前的拖动百分比给Loadinglayout,这样就可以根据百分比显示旋转或翻转的动画了。onpullevent里面有个注意点,它是根据当前拖拽的距离和Loadinglayout的实际内容距离做的比较,它有个innerLayout,我们知道LoadingLayout有listview的0.6倍,而header的实际大小是xml定义的,而且他放在了LoadingLayout的底部(foot反之),根据这样来显示“向下拉动刷新”和“释放开始刷新”之类的字样提醒。同时会调用setState(State.PULL_TO_REFRESH);setState(State.RELEASE_TO_REFRESH);,当然字样提醒就是在对应的onReleaseToRefresh事件里,去调用mHeaderLayout.releaseTorefresh去实现的。(其实这里就是另一个方法了:onpullevent,不过贴代码就超了)

final void setState(State state, final boolean... params) {
        mState = state;
        if (DEBUG) {
            Log.d(LOG_TAG, "State: " + mState.name());
        }

        switch (mState) {
        case RESET:
            onReset();
            break;
        case PULL_TO_REFRESH:
            onPullToRefresh();
            break;
        case RELEASE_TO_REFRESH:
            onReleaseToRefresh();
            break;
        case REFRESHING:
        case MANUAL_REFRESHING:
            onRefreshing(params[0]);
            break;
        case OVERSCROLLING:
            // NO-OP
            break;
        }

        // Call OnPullEventListener
        if (null != mOnPullEventListener) {
            mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
        }
    }

ps:其实分离Loadinglayout还是挺容易的,因为现在LoadingLayout,它管的事有点多,他管的事就是有相同布局风格的代表,比如都有updatelabel,都有mHeaderImage等等。其实他应该还有个父类,或者或他应该去掉多余的方法,再拥有个子类(因为fresh的base基类里面的headLayout和footLayout都是LoadingLayout类型,所以不适合再做父类,或者做了父类后就要把基类的head foot的对应类型改成父类),比如叫做 standerLoadinLayout(标准型Loading),然后Rotato和Flip两个都继承stantder,stander中,可以做一些原来的LoadingLayout的事情,比如拥有mHeaderText控件,在reset中可以处理settext等。我们需要自定义的时候,就可以继承简化的LoadingLayout,来做一个非常随意的独特布局,比如一个animations的帧动画。(比如下拉过程中,小人在走,松手后,小人开始跑步。比如下拉的时候火箭开始点火,松手就开始飞,加载完就飞出屏幕了等等)

到这里,基类基本就看完了,其实边写边看,感觉印象很深,写着写着也容易停下来思考问题,下边就开始看各个子类了,大体看了下,都非常简单,因为父类做了不少事情,子类只是提供了不同的临界判断标准而已。再有基类的方法有onSaveInstanceState,存储状态就不讲了,他自己存完必须的状态后,会调用子类的ab方法onPtrsave,方便不通的子类执行不通的存储,比如Listview可能希望存储当前是第几个index。webview更希望存储当前加载的哪个url。:)

你可能感兴趣的:(PullToRefresh的学习研究(二))