天天看点

PullToReFresh 实现 RecycleView 横向滑动的刷新和加载更多

项目地址:https://github.com/moscoper/PullToRefresh.git

前言

一般的刷新和加载更多的效果都是竖直方向的(下拉刷新和上拉加载更多),本篇文章就利用 PullToReFresh 和 RecycleView 来实现水平方向的刷新和加载更多的效果。我们暂且叫它 PullToReshHorizontalRecycleView。

PullToReshHorizontalRecycleView

我们在 PullToRefresh 的扩展 这篇文章中介绍过如何利用 PullToReFresh 对其他可以滑动的控件添加下拉和上拉的效果。PullToReshHorizontalRecycleView 的实现和其类似。

继承 PullToRefreshBase

新建类

PullToReshHorizontalRecycleView

继承

PullToRefreshBase<RecyclerView>

。并实现

getPullToRefreshScrollDirection

createRefreshableView

,

isReadyForPullStart

isReadyForPullEnd

这四个方法。

getPullToRefreshScrollDirection

方法

getPullToRefreshScrollDirection

的返回值决定了滑动的方向。返回

Orientation.HORIZONTAL

时为水平滑动;返回

Orientation.VERTICAL

时为竖直滑动。这里我们需要的是水平方向的滑动所以返回

Orientation.HORIZONTAL

@Override public Orientation getPullToRefreshScrollDirection() {
    return Orientation.HORIZONTAL;
  }
           

createRefreshableView

方法

createRefreshableView

的返回值是要添加刷新效果的控件,这里我们需要为横向滑动的 RecycleView 添加刷新效果所以就返回横向滑动的 RecycleView。

@Override protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {
    RecyclerView recyclerView = new RecyclerView(context,attrs);
    LinearLayoutManager mannagerTwo = new LinearLayoutManager(context);
    mannagerTwo.setOrientation(LinearLayoutManager.HORIZONTAL);

    recyclerView.setLayoutManager(mannagerTwo);

    return recyclerView;
  }
           

isReadyForPullStart

方法

isReadyForPullStart

返回

true

时表示可以开始刷新的效果了;返回

false

时表示还不满足可以触发刷新的效果。我们这里触发展示刷新效果的条件是 RecycleView 的第一个 item 的左边缘在屏幕左边缘的右侧。所以

isReadyForPullStart

的具体实现可以如下:

@Override protected boolean isReadyForPullStart() {
    return isFirstItemVisible();
  }
           
private boolean isFirstItemVisible() {
    final RecyclerView.Adapter adapter = mRefreshableView.getAdapter();

    if (null == adapter || adapter.getItemCount() ==) {
      if (DEBUG) {
        Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
      }
      return true;
    } else {

      /**
       * This check should really just be:
       * mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
       * internally use a HeaderView which messes the positions up. For
       * now we'll just add one to account for it and rely on the inner
       * condition which checks getTop().
       */
      if (getFirstVisiblePosition() <= ) {
        final View firstVisibleChild = mRefreshableView.getChildAt();
        if (firstVisibleChild != null) {
          return firstVisibleChild.getLeft() >= mRefreshableView.getLeft();
        }
      }
    }

    return false;
  }
           

isReadyForPullEnd

方法

isReadyForPullEnd

返回

true

时表示满足触发加载更多效果的条件,否则表示不满足。这里满足触发加载更多效果的条件是 RecycleView 最后一个 item 的右边缘在屏幕右边缘的左边。所以

isReadyForPullEnd

的实现如下:

@Override protected boolean isReadyForPullEnd() {
    return isLastItemVisible();
  }
           
private boolean isLastItemVisible() {
    final RecyclerView.Adapter adapter = mRefreshableView.getAdapter();

    if (null == adapter || adapter.getItemCount()==) {
      if (DEBUG) {
        Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
      }
      return true;
    } else {
      final int lastItemPosition = adapter.getItemCount() - ;
      final int lastVisiblePosition = getLastVisiblePosition();

      if (DEBUG) {
        Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: "
            + lastItemPosition
            + " Last Visible Pos: "
            + lastVisiblePosition);
      }

      /**
       * This check should really just be: lastVisiblePosition ==
       * lastItemPosition, but PtRListView internally uses a FooterView
       * which messes the positions up. For me we'll just subtract one to
       * account for it and rely on the inner condition which checks
       * getBottom().
       */
      if (lastVisiblePosition >= lastItemPosition - ) {
        final int childIndex = lastVisiblePosition - getFirstVisiblePosition();
        final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
        if (lastVisibleChild != null) {
          return lastVisibleChild.getRight() <= mRefreshableView.getRight();
        }
      }
    }

    return false;
  }
           

完整代码

public class PullRefreshRecyclerView extends PullToRefreshBase<RecyclerView> {
  public PullRefreshRecyclerView(Context context, Mode mode) {
    super(context, mode);
  }

  public PullRefreshRecyclerView(Context context) {
    super(context);
  }

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

  public PullRefreshRecyclerView(Context context, Mode mode, AnimationStyle animStyle) {
    super(context, mode, animStyle);
  }

  @Override public Orientation getPullToRefreshScrollDirection() {
    return Orientation.HORIZONTAL;
  }

  @Override protected RecyclerView createRefreshableView(Context context, AttributeSet attrs) {
    RecyclerView recyclerView = new RecyclerView(context,attrs);
    LinearLayoutManager mannagerTwo = new LinearLayoutManager(context);
    mannagerTwo.setOrientation(LinearLayoutManager.HORIZONTAL);
    //recyclerView.addItemDecoration(
    //    new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL_LIST));
    recyclerView.setLayoutManager(mannagerTwo);

    return recyclerView;
  }

  public void addItemDecoration(RecyclerView.ItemDecoration itemDecoration){
    mRefreshableView.addItemDecoration(itemDecoration);
  }

  public void setAdapter(RecyclerView.Adapter adapter){
    mRefreshableView.setAdapter(adapter);
  }

  @Override protected boolean isReadyForPullEnd() {
    return isLastItemVisible();
  }

  @Override protected boolean isReadyForPullStart() {
    return isFirstItemVisible();
  }

  private boolean isLastItemVisible() {
    final RecyclerView.Adapter adapter = mRefreshableView.getAdapter();

    if (null == adapter || adapter.getItemCount()==) {
      if (DEBUG) {
        Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
      }
      return true;
    } else {
      final int lastItemPosition = adapter.getItemCount() - ;
      final int lastVisiblePosition = getLastVisiblePosition();

      if (DEBUG) {
        Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: "
            + lastItemPosition
            + " Last Visible Pos: "
            + lastVisiblePosition);
      }

      /**
       * This check should really just be: lastVisiblePosition ==
       * lastItemPosition, but PtRListView internally uses a FooterView
       * which messes the positions up. For me we'll just subtract one to
       * account for it and rely on the inner condition which checks
       * getBottom().
       */
      if (lastVisiblePosition >= lastItemPosition - ) {
        final int childIndex = lastVisiblePosition - getFirstVisiblePosition();
        final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
        if (lastVisibleChild != null) {
          return lastVisibleChild.getRight() <= mRefreshableView.getRight();
        }
      }
    }

    return false;
  }

  private int getFirstVisiblePosition(){
    int position = ;
   RecyclerView.LayoutManager manager =  mRefreshableView.getLayoutManager();
    if (manager instanceof LinearLayoutManager){
      LinearLayoutManager linearLayoutManager = (LinearLayoutManager) manager;
      return  linearLayoutManager.findFirstVisibleItemPosition();
    }

    return position;
  }

  private int getLastVisiblePosition(){
    int position = ;
    RecyclerView.LayoutManager manager =  mRefreshableView.getLayoutManager();
    if (manager instanceof LinearLayoutManager){
      LinearLayoutManager linearLayoutManager = (LinearLayoutManager) manager;
      return  linearLayoutManager.findLastVisibleItemPosition();
    }
    return position;
  }

  private boolean isFirstItemVisible() {
    final RecyclerView.Adapter adapter = mRefreshableView.getAdapter();

    if (null == adapter || adapter.getItemCount() ==) {
      if (DEBUG) {
        Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
      }
      return true;
    } else {

      /**
       * This check should really just be:
       * mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
       * internally use a HeaderView which messes the positions up. For
       * now we'll just add one to account for it and rely on the inner
       * condition which checks getTop().
       */
      if (getFirstVisiblePosition() <= ) {
        final View firstVisibleChild = mRefreshableView.getChildAt();
        if (firstVisibleChild != null) {
          return firstVisibleChild.getLeft() >= mRefreshableView.getLeft();
        }
      }
    }

    return false;
  }

}
           

结语

PullToReshHorizontalRecycleView 的用法和原有的 PullToReFresh 的控件是一样的,这里不再赘述。