RecyclerViewでリストの先頭or末尾検知をシンプルに実装する


初投稿します@u_nation です。

結論

以下のコードで実現できました。

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (!recyclerView.canScrollVertically(-1)) {
           /*リストの先頭に来た時の処理*/
         }

        if (!recyclerView.canScrollVertically(1)) {
           /*リストの末尾に来た時の処理*/
        }
    }
);

重要なのはrecyclerView.canScrollVertically(-1 or 1)の部分です。
View.javaJavadocを読むと負の数はスクロールアップを正の数はスクロールダウンをチェックするとあります。

引数に負の数を与えると先頭でfalseが返り、正の数を与えると末尾でfalseを返してくれます。

View.java
/**
  * Check if this view can be scrolled vertically in a certain direction.
  *
  * @param direction Negative to check scrolling up, positive to check scrolling down.
  * @return true if this view can be scrolled in the specified direction, false otherwise.
  */
public boolean canScrollVertically(int direction) {
     final int offset = computeVerticalScrollOffset();
     final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
     if (range == 0) return false;
     if (direction < 0) {
         return offset > 0;
     } else {
         return offset < range - 1;
     }
 }

使用例

1.末尾で次のデータをロード
よくあるパターンのアレです

2.SwipeRefreshLayout
ListViewの時はリストの先頭で下方向にスワイプするとよしなに動いてくれたSwipeRefreshLayoutですがRecyclerViewではリストの先頭以外ではsetEnabled(false)してあげないと意図しない位置でもonRefreshが呼ばれてしまいます。
recyclerView.addOnScrollListener内のonScrolledメソッドの中に以下のコードで適切に処理してくれます

if (!recyclerView.canScrollVertically(-1)) {
   swipeRefreshLayout.setEnabled(true);
} else if (swipeRefreshLayout.isEnabled()) {
   swipeRefreshLayout.setEnabled(false);
}

オマケ

末尾検知はAdapter内でもできます。
positionを使ったりデータをどうこうしたい時はこちらでもいいかもしれません。
ただonBindViewHolderメソッド内でリストの要素の変更をするときは
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
がでるのを防ぐためにHandlerを使ったほうが良いと思います。

@Override
public void onBindViewHolder(BindingHolder holder, int position) {
    if (position == getItemCount() - 1) {
       /*リストの末尾に来た時の処理*/

       handler.post(() -> {/*要素の変更をする場合*/})); 
    }
}

以上です。誤りがあれば訂正いたします(>_<)