RecyclerView:アイテムのスクロール

投稿日:  更新日:

RecyclerViewに表示しきれなかったアイテムはスクロールを行うことで表示されるようになっています。

スクロールは「外部入力(指でスクリーン上をタッチしてスライド)によるスクロール」の他に、「プログラムによるスクロール」をすることも出来ます。

今回はこのアイテムのスクロールについてまとめてみました。

スポンサーリンク

外部入力によるスクロール

外部入力とは、指でスクリーン上をタッチしてスライドしたり、マウスなどのポインティングデバイスでスクリーン上をドラッグしてスライドしたり、何かしらの外部装置を使った操作のことです。

携帯端末はタッチスクリーンの装備が標準なので、前者の操作が主流でしょう。

指でタッチしてスライド

図は指を使ってスクロールする様子です。

勢いよくスワイプ後にスクリーンから指を離しても、スクロールはしばらく継続します。

末尾(または先頭)のアイテムに到達すると、EdgeEffectのアニメーションが表示されて、それ以上のスクロールが出来ないことを知らせます。

指でタッチしてスライド

スクロールの状態を取得(OnScrollListener)

アイテムのスクロールが行われている時に、登録されたリスナー(OnScrollListener)へコールバックが返り、スクロールの状態が通知されます。

スクロールの状態の意味はサンプル中のコメントに書いた通りです。

        ...
		
        val _listener = object : RecyclerView.OnScrollListener(){
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                Log.i("OnScrollListener", "onScrollStateChanged newState = %s"
                    .format(getStringOfScrollState(newState)))
            }
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                Log.i("OnScrollListener", "onScrolled           dx=%3d dy=%3d"
				    .format(dx, dy))
            }
        }
        rcySample.addOnScrollListener(_listener)
		
		...
// SCROLL_STATE_IDLE
//  スクロールしていない
// SCROLL_STATE_DRAGGING
//  タッチ入力などの外部入力によってドラッグされている
// SCROLL_STATE_SETTLING
//  外部からの制御下にはないが、最終的なポジションへスクロールしている
private fun getStringOfScrollState(state: Int): String {
    return when(state) {
        RecyclerView.SCROLL_STATE_IDLE -> "0:IDLE(スクロールしていない状態)"
        RecyclerView.SCROLL_STATE_DRAGGING -> "1:DRAGGING(ドラッグされている状態)"
        RecyclerView.SCROLL_STATE_SETTLING -> "2:SETTLING(スクロールしている状態)"
        else -> "UNKNOWN"
    }
}
...: onScrollStateChanged newState = 1:DRAGGING(ドラッグされている状態)
...: onScrolled           dx=  0 dy=  1
...: onScrolled           dx=  0 dy= 34
...: onScrolled           dx=  0 dy= 81
...: onScrolled           dx=  0 dy=144
...: onScrolled           dx=  0 dy= 41
...: onScrollStateChanged newState = 2:SETTLING(スクロールしている状態)
...: onScrolled           dx=  0 dy= 88
...: onScrolled           dx=  0 dy= 51
...: onScrollStateChanged newState = 0:IDLE(スクロールしていない状態)
スポンサーリンク

プログラムによるスクロール

スクロールを行う3つのメソッドが用意されています。

RecyclerView#scrollToPosition(ポジションへスクロール)

指定されたポジションへ移動します。

メソッド名に「Scroll」と付いていますがスクロールしません。指定されたポジションへ「Jump」と言った方が妥当な動作です。

使用方法:
    scrollToPosition(int position)
	
    position	移動先のポジション(0 ~ アイテム数-1)

【後方へ移動】

rcySample.scrollToPosition(8)

scrollToPosition 後方へ移動
【前方へ移動】

rcySample.scrollToPosition(2)

scrollToPosition 前方へ移動

スクロールしないので、OnScrollListenerによるスクロールの状態は通知されません。

RecyclerView#smoothScrollToPosition(ポジションへスクロール)

指定されたポジションへスクロールします。

使用方法:
    smoothScrollToPosition(int position)
	
    position	移動先のポジション(0 ~ アイテム数-1)

【後方へ移動】

rcySample.smoothScrollToPosition(32)
【前方へ移動】

rcySample.smoothScrollToPosition(2)

ドラッグしないので、OnScrollListenerによるスクロールの状態はSETTLINGとIDLEの2つが通知されます。

RecyclerView#smoothScrollBy(指定した移動量をスクロール)

指定した移動量(ピクセル値)をスクロールします。

使用方法1:
    smoothScrollBy(
	    int dx, int dy, Interpolator interpolator, int duration)
	
	dx          	x方向の移動量[px]
	            	  +:後方/-:前方、VERTICALの時は0を指定
	dy          	y方向の移動量[px]
	            	  +:後方/-:前方、HORIZONTALの時は0を指定
	interpolator	アニメーションのタイプ
	            	  nullの時はデフォルトのInterpolator
	duration    	アニメーションの時間[ms]
	             	  duration > 0の時、dulationをそのまま使用
	                  UNDEFINED_DURATIONの時、自動計算
                 	  上記以外の時、スクロール無し

使用方法2:
    smoothScrollBy(int dx, int dy, Interpolator interpolator)
    -> smoothScrollBy(dx, dy, interpolator, UNDEFINED_DURATION)と同じ
	
使用方法3:
    smoothScrollBy(int dx, int dy)
    -> smoothScrollBy(dx, dy, null)と同じ

※デフォルトのInterpolator
 均等に減速していくような変化のアニメーションになる
※durationの自動計算
 スクロールが目で追える速度のアニメーションになる

【後方へ移動】

rcySample.smoothScrollBy(0, 1220, LinearInterpolator(), 700)
【前方へ移動】

rcySample.smoothScrollBy(0, -1220, LinearInterpolator(), 700)

ドラッグしないので、OnScrollListenerによるスクロールの状態はSETTLINGとIDLEの2つが通知されます。

スポンサーリンク

スクロール後の表示ポジションを検出

アイテム数が多くなると、スクロールを繰り返すうちに、全体に対する表示されているアイテムのポジションが分からなくなることがあります。

そのような時のために、アイテムの位置関係をユーザに知らせることができたら便利です。

表示されているアイテムのポジションは次のようにして検出できます。

        ...
		
        val _listener = object : RecyclerView.OnScrollListener(){
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 
			    ...
			}
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val _manager = recyclerView.layoutManager as LinearLayoutManager
                val _items = "%d - %d / %d".format(
                    _manager.findFirstVisibleItemPosition(),
                    _manager.findLastVisibleItemPosition(),
                    _manager.itemCount
                )
                txtItems.setText(_items)  // TextViewへアイテムの位置関係を表示
            }
        }
        rcySample.addOnScrollListener(_listener)
		
		...

表示ポジションを検出

Complete付きのメソッドは完全に表示されているアイテムのポジションになります。

スポンサーリンク

スクロールが先頭・末尾に到達したことを検出

スクロールによりアイテムが末尾または先頭に到達すると、EdgeEffectアニメーションが発動して、それ以上のアイテムが存在しないことをユーザへ知らせてくれます。

このタイミングでプログラムを動かしたい場合があります。

例えば、続きのアイテムデータを読み込む場合などです。

先頭・末尾に到達したことは次のようにして検出できます。

使用方法:
    View#canScrollVertically(int direction)  	縦スクロール
    View#canScrollHorizontally(int direction)	横スクロール
	
    direction	direction > 0の場合
             	 true:後方へスクロール可能、false:不可
             	direction ≦ 0の場合
              	 true:前方へスクロール可能、false:不可
        ...
		
        val _listener = object : RecyclerView.OnScrollListener(){
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 
			    ...
			}
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                if(! recyclerView.canScrollVertically(-1))
                    Toast.makeText(this@MainActivity,
                        "先頭のアイテム!", Toast.LENGTH_LONG).show()
                if(! recyclerView.canScrollVertically(1))
                    Toast.makeText(this@MainActivity,
                        "末尾のアイテム!", Toast.LENGTH_LONG).show()
            }
        }
        rcySample.addOnScrollListener(_listener)
		
		...

検出で使用したcanScrollVerticallyメソッドの中身は次のようになっています。

    ...
		
    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;
        }
    }
		
    ...

canScrollVerticallyの構成

引数のdirectionは名前が示す通り、+-でスクロールの向きを示しているに過ぎません。int型ですが、大きさは意味を持たないので注意が必要です。

スポンサーリンク

関連記事:

RecyclerViewは子Viewを並べて表示するコンテナタイプ(ConstraintLayoutと同じ)のViewです。 複数のデータをスクリーン上に一覧表示したい時、例えば電話帳のような「氏名+住所+電話番号」の一覧を表示する場合などに最適です。 アプリを開発していると一覧表示したいデータが多いことに気付きます。 なのでRecyclerViewはとても重要で重宝するViewです。 しかし、思い通りの表示を行わせるためのテクニックが多すぎて、使いこなしが難しいです。 今まで調べたテクニックを忘れないように、整理して書き残そうと思います。 今回は基本の「RecyclerViewの実装」です。 ...
RecyclerViewでアイテムのクリックイベントを取得し、処理を実行する方法を紹介します。 ...
RecyclerViewはアイテムのレイアウトをアイテム毎に変更できます。その時に使う値がViewTypeです。 ViewTypeでアイテムのレイアウトを変更する方法を紹介します。 ...
RecyclerViewは表示が変更される(アイテムの更新、スクロール)時、アイテムのViewをリサイクル(再生利用)します。 これにより余分なViewの作成が行われなくなり、メモリーの節約とパフォーマンスの向上が望めます。 リサイクルはCachedViewsとRecyclerPoolという2つのキャッシュで行われます。 このキャッシュを使ったリサイクルの動作を調べたので紹介します。 ...
RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。 ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。 キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。 そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。 ...
RecyclerViewはアイテムを一覧表示してくれます。 ただ一覧表示するだけではなく、「追加・削除・移動・切り替え」といったアイテムの表示を効率よく変更する仕組み持っています。 今回はこの仕組みを使ったアイテムの変更方法を紹介します。 ...
RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。 ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。 キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。 そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。 ...
RecyclerViewが空(アイテムが無い)の時、EmptyViewを表示する実装を行ったので紹介します。 ...
RecyclerView上のアイテムを選択する方法を紹介します。 外部ライブラリー(AndroidX)で提供されるrecyclerview-selection APIを用いた方法です。 ...
RecyclerViewはListView(RecyclerViewの前身)の時に存在していたChoiceModeがありません。 同様な機能が欲しければプログラマ側で実装しなければなりません。 RecyclerView.AdapterをカスタマイズしてChoiceModeを実装してみたので紹介します。 ...
RecyclerViewはアイテムへ装飾を付けることが出来るようになっています。 装飾とは、例えばアイテムの区切り線などです。 今回はアイテムへ装飾を付ける方法を紹介します。 ...
RecyclerViewはアイテムをスクロールさせて隠れたアイテムを表示できます。 スクロールを止める位置は任意です。 任意であるがゆえに、止めた位置によってはアイテムの一部が欠けてしまうこともあります。 携帯端末の画面は狭いので、効率よくコンテンツの表示を行いたいとアプリ開発者は考えます。 アイテムの一部が欠けてしまうことは、効率が良いとは言えません。 このような問題を解決するために、アイテムのスナップをRecyclerViewへ追加できます。 アイテムのスナップを追加する方法を紹介します。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第1回目「ItemAnimator、デフォルト」編です。デフォルト変更アニメーションの動作について説明します。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第2回目「SimpleItemAnimatorの継承、カスタム」編です。カスタム変更アニメーションの作り方を説明 ...
RecyclerViewへアイテムが表示されるとき、アニメーションはありません。。一瞬で表示されて終わりです。 「RecyclerViewへアイテムが表示される」ことを、ここでは「アイテムの出現」と言い表すことにします。 このアイテムの出現にアニメーションを付ける方法を紹介します。 アイテムの出現をアニメーションで演出することで、RecyclerViewに表示したい内容が際立つと思います。 ...
RecyclerViewで「画像ファイルをグリッド表示」する方法を、まとめます。 スマートフォンのアプリを作っていると、何度も遭遇するテクニックです。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 1     Kotlin 2.0.0     Compose Compilerプラグイン 2.0.0     androidx.recyclerview:recyclerview:1.1.0 ...
RecyclerViewで「グリッド表示をスムーズにスクロール」する方法について、まとめます。 画像ファイルをグリッド表示する場合は、スムーズなスクロールを行うための工夫が必要です。 その工夫について紹介します。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 1     Kotlin 2.0.0     Compose Compilerプラグイン 2.0.0     androidx.recyclerview:recyclerview:1.1.0 ...
スポンサーリンク