PullToReFreshはRecycleViewの横スライドのリフレッシュとロードを実現

14908 ワード

プロジェクトのアドレス:https://github.com/moscoper/PullToRefresh.git
前言
一般的なリフレッシュとロードの効果は縦方向(ドロップダウン・リフレッシュとアップ・ロードが多い)であり、この記事ではPullToReFreshとRecycleViewを利用して水平方向のリフレッシュとロードの効果をより多く実現します.私たちはしばらくそれをPullToReshHorizontalRecycleViewと呼んでいます.
PullToReshHorizontalRecycleView
PullToRefreshの拡張この記事では、PullToRefreshを使用して他のスライド可能なコントロールにドロップダウンとプルアップの効果を追加する方法について説明しています.PullToReshHorizontalRecycleViewの実装はそれと類似している.
PullToRefreshBaseの継承
新規クラスPullToReshHorizontalRecycleView継承PullToRefreshBase.getPullToRefreshScrollDirection,createRefreshableView,isReadyForPullStartisReadyForPullEndの4つの方法が実現される.
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() ==0) {
      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() <= 1) {
        final View firstVisibleChild = mRefreshableView.getChildAt(0);
        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()==0) {
      if (DEBUG) {
        Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
      }
      return true;
    } else {
      final int lastItemPosition = adapter.getItemCount() - 1;
      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 - 1) {
        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 {
  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()==0) {
      if (DEBUG) {
        Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
      }
      return true;
    } else {
      final int lastItemPosition = adapter.getItemCount() - 1;
      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 - 1) {
        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 = 0;
   RecyclerView.LayoutManager manager =  mRefreshableView.getLayoutManager();
    if (manager instanceof LinearLayoutManager){
      LinearLayoutManager linearLayoutManager = (LinearLayoutManager) manager;
      return  linearLayoutManager.findFirstVisibleItemPosition();
    }

    return position;
  }

  private int getLastVisiblePosition(){
    int position = 0;
    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() ==0) {
      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() <= 1) {
        final View firstVisibleChild = mRefreshableView.getChildAt(0);
        if (firstVisibleChild != null) {
          return firstVisibleChild.getLeft() >= mRefreshableView.getLeft();
        }
      }
    }

    return false;
  }

}

締めくくり
PullToReshHorizontalRecycleViewの使い方は、従来のPullToReFreshのコントロールと同じですが、ここではこれ以上説明しません.