前人未踏の領域へ アプリ開発編

Android, iOSアプリ開発に関する調査メモ置き場。はてなダイアリーから移行したため古い記事にはアプリ以外も含まれます。

[Android]RecyclerViewのスクロール後にアイテムのタップが 無視される件

課題

RecyclerViewを利用している画面で、最後(あるいは先頭)までスクロールした
すぐ後でアイテムをタップしても反応しない。2回タップしたり、しばらく待ってからタップすると反応する。

原因

RecyclerViewはスクロールに応じた3つの状態(SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING or SCROLL_STATE_SETTLING)を持つ。
この内、SCROLL_STATE_SETTLINGの状態はスクロールした最終的な位置に移動中であり、外部の制御下にはなくなる。
そのため、タップは無視され、タップしたことによりスクロールが停止するのでSCROLL_STATE_IDLEに戻り、次のタップには反応する。
最後までスクロールした状態のリストを強くフリングするとSCROLL_STATE_SETTLINGな状態が長くなり、タップが無視されたように感じる。

対策

canScrollVerticallyでスクロールが可能かをチェックし、falseになった場合はstopScroll()で止めてしまうのが無難な様子

override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                //これ以上スクロールできない場合は強制停止
                if ( !recyclerView!!.canScrollVertically(dy) ) {
                    listView?.stopScroll()
                }
}

但しこれだけだと既にこれ以上スクロールできない状態からフリングされるとロックがかかるので
さらにステータスもチェック

            override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                //上下どちらかにスクロールできない状態であれば強制停止
                if (newState == RecyclerView.SCROLL_STATE_SETTLING && (
                       (!recyclerView!!.canScrollVertically(0) || !recyclerView!!.canScrollVertically(1)) ) {
                        recyclerView?.stopScroll()
                    }
                }
            }

全部の画面で修正するのは面倒なのでRecyclerViewを拡張してしまった方がいいかもしれない。
これならLayout.xmlの修正だけで済む。

public class ExRecyclerView extends RecyclerView {
    public ExRecyclerView(Context context) {
        super(context);
    }

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

    public ExRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        //フリングすると止まるまでタップが無効化されるので最後までスクロールしたら強制停止
        if (state == RecyclerView.SCROLL_STATE_SETTLING
                && (!canScrollVertically(0) || !canScrollVertically(-1))) {
            stopScroll();
        }
    }

    @Override
    public void onScrolled(int dx, int dy) {
        super.onScrolled(dx, dy);
        //フリングすると止まるまでタップが無効化されるので最後までスクロールしたら強制停止
        if (!canScrollVertically(dy)) {
            stopScroll();
        }
    }
}