アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

RecyclerViewのスクロール後にクリックできなくなる問題

問題

以下の動画のように末尾や先頭にスクロールした後に一回クリックイベントが効かなくなります。しかし、暫く待てばクリックすることができます。

これはGooglePayアプリでも発生しています。
f:id:matsudamper:20190317222445p:plain:w250

再現環境

自分の場合はBottomSheetにFragmentとしてRecyclerViewを入れて使っていました。

<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/bottom_sheet_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>


in bottom_sheet

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

原因の特定

  1. スクロール後にRippleが発生していない → NestedScroll絡みの問題である。
  2. 親にandroid:nestedScrollingEnabled="false"を設定する → 直る(BottomSheetや、NestedScrollしたい場面では問題がある)
  3. スクロールの状態に合わせてisNestedScrollingEnabledを書き換えれば良いのではないかと考える。
  4. 下記のコードを書く
  5. 端までスクロールした際にSCROLL_STATE_IDLEが遅れて呼ばれていることに気がつく。更にゆっくりスクロールした場合にその時間が短いことに気がつく →見えない慣性スクロールが発生している
  6. 親のNestedScrollが終わっているのに(他にスクロールする物が無いのに)子のスクロールを止めていないのが原因だと判明する。
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        when (newState) {
            RecyclerView.SCROLL_STATE_DRAGGING -> {
                Log.d("TAG", "SCROLL_STATE_DRAGGING")
            }
            RecyclerView.SCROLL_STATE_SETTLING -> {
                Log.d("TAG", "SCROLL_STATE_SETTLING")
            }
            RecyclerView.SCROLL_STATE_IDLE -> {
                Log.d("TAG", "SCROLL_STATE_IDLE")
            }
        }
    }
})

解決方法

NestedScrollしない場合

android:nestedScrollingEnabled="false"を設定する

NestedScrollする場合

今回の場合
1. 子のRecyclerViewのスクロールが末尾まで行く
2. 親のNestedScrollが開始する。
3. (ここからが今回やること)親のViewを以下に差し替え、子のスクロールを停止する

class BottomSheetContainerLayout @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0,
        defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {

    override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int): Boolean {
        if (target is RecyclerView) {
            target.stopScroll()
        }
        return super.onStartNestedScroll(child, target, nestedScrollAxes)
    }
}

おわりに

Googleのアプリでもなってるし、DroidKaigiのアプリでもなってるし、他にも様々なアプリで同様の問題を発見したのでかなりハマる所だなと思った。解決まで苦労した。