TurboRecyclerViewHelper之实现Recycl

2016-06-01  本文已影响0人  做麦西西不容易

自从使用了RecyclerView再也回不去了,什么ListView、GridView统统让他们退休了。必须安利起来,用了才能体会它的神奇!

根据使用RecyclerView以来,拓展的一些功能及对RecyclerView.Adapter的封装,想在这里跟大家分享一些经验,还望指正。

  1. 功能介绍
    ====
    基于对RecyclerView在使用过程中的一些痛点写了这个开源项目 TurboRecyclerViewHelper 。功能点详见README。
turbo_simple.gif

本次主要介绍针对TurboRecyclerView上拉/左滑的功能的实现及思路。

下面直接进入正题...

  1. 状态分析
    ====
    以上拉加载的过程作为本次分享的一个栗子。状态如下图:
turbo_state_analysis.png

我们所关心的是RecyclerView滑动到底部的状态,这个状态下是我们需要处理的临界状态
初始状态以及滑动中的状态我们不需要关心(交给RecyclerView本身处理即可)。

  1. 目标状态的条件限制
    ====
    废话不多说直接看代码:
  if (!mLoadEnabled || canScrollEnd() || mIsLoading || isEmpty()) {
        return super...;
  }

我们从两个方面来分析可以开始处理Touch事件的条件:

3.1 客观条件

所谓客观条件即是RecyclerView滑动到底部的这个状态的物理状态,体现在代码上就是

 private boolean canScrollEnd() {
       //判断在纵向是否还能向上滑动
       return ViewCompat.canScrollVertically(this, 1);
 }

这里为了简化只判断了纵向是否可以向下滑动,实际代码中这里是判断条件为ViewCompat.canScrollVertically(this, 1) || ViewCompat.canScrollHorizontally(this, 1)

这个条件返回值如果是false,则代表我们可以从这个临界状态开始处理Touch事件,否则不处理。

3.2 逻辑条件(主观条件)

逻辑条件(或者称为主观条件)是设计控件本身所考虑的限制条件。

判断条件如下:

    //是否允许上拉 || 是否正处于刷新状态 || 是否处理空状态
    if (!mLoadEnabled || mIsLoading || isEmpty())

在以上条件下我们认为是控件本身应处于不可上拉的状态,我们不做处理。

  1. Touch事件的处理
    ====
    在同时满足客观条件和逻辑条件下,我们就可以开始处理上拉的效果。

4.1 记录初始值

我们需要在MotionEvent.ACTION_DOWN MotionEvent.ACTION_POINTER_DOWN时记录初始值:

   mInitialMotionX = getMotionEventX(e, actionIndex);
   mInitialMotionY = getMotionEventY(e, actionIndex);

4.2 判断滑动是否符合预期值

MotionEvent.ACTION_MOVE时判断是否是上拉的状态:

    //LayoutManager中提供的判断是否纵向可以滑动的方法
    final boolean canScrollVertically = getLayoutManager().canScrollVertically();
    ...
    final int y = getMotionEventY(e, index);
    int deltaY = y - mInitialMotionY;
    if (canScrollVertically && Math.abs(deltaY) > mTouchSlop && deltaY < 0) { ... //处理上拉效果}

记录当前Y值,且判断是否手指在上滑状态。

4.3 实现上拉效果

在自定义控件中,实现上拉效果有多种途径,例如大家常用的利用Scroller配合scrollTo来实现滑动,但是在RecyclerView的实现中并不支持这种方式。这个方案Close!

    @Override
    public void scrollTo(int x, int y) {
        Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
                + "Use scrollToPosition instead");
    }

这里采用setTranslationY来实现上拉效果,根据手指移动的距离计算出移动距离来改变RecyclerView的位置。

    ...
    float targetEnd = -dampAxis(deltaY); //阻尼值的计算
    setTranslationY(targetEnd);
    return true;  //消费掉此事件

到这里上拉的效果已经实现完毕。

4.4 复位及刷新

距离成功只差一点点。
在用户手指松开以后,我们要考虑做两件事:RecyclerView的复位及是否可以处于刷新状态。
针对复位操作,我们只需要逆向setTranslationY值即可。这里我们采用属性动画来实现

    private void animateOffsetToEnd(final String propertyName, final Interpolator interpolator, float... value) {
        if (mResetAnimator == null) {
            mResetAnimator = new ObjectAnimator();
            mResetAnimator.setTarget(this);
        }
        mResetAnimator.cancel();
        mResetAnimator.setPropertyName(propertyName);
        mResetAnimator.setFloatValues(value);
        mResetAnimator.setInterpolator(interpolator);
        mResetAnimator.start();
 }
    
    ...
     
    if(canScrollVertically)
        animateOffsetToEnd("translationY", mInterpolator, 0f);
    ...

对于刷新我们要做的事情也比较简单,判断当前移动距离达到阈值后,回调监听事件并显示LOAING_VIEW。

    Log.i(TAG, "refreshing...");
    mIsLoading = true;
    dispatchOnLoadingMoreListeners();
    smoothScrollToPosition(mLastVisibleItemPosition + 1);

刷新完毕后,记得通知TurboRecyclerView更新状态哦!

    mTurboRecyclerView.addOnLoadingMoreListener(new OnLoadMoreListener() {
        @Override
        public void onLoadingMore() {
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mRecyclerView.loadMoreComplete(Arrays.asList(sCheeseStrings));
                }
            }, 2000);
        }
    });

至此整个上拉到复位刷新的过程完成。
完整代码详见 TurboRecyclerView.java

希望我的分享能让您能有所收获。也欢迎支持一下这个项目~ 持续维护~

下次准备介绍一下对RecyclerView.Adapter的封装,还请关注!😊

上一篇下一篇

猜你喜欢

热点阅读