0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Androidで動画を長押しで早送りする機能について

Last updated at Posted at 2025-03-02

掲題の、Youtube等に搭載されている長押しで早送りする機能を作った時に得た知見について記録します。

動画再生ページ仕様

BottomSheetBehaviorを用いて動画表示部分を下にスワイプすると、指の動きに画面ごと追従しながら閉じること(詳細はこちらの記事の「透明Activityの作り方と活用方法」を参照ください)

実装時に起きた問題点

  1. 対象となる動画表示画面に対してFragmentでonLongPressを早送りとして実装しただけでは、長押し中に少しでも指を上下左右に動かしてしまうとonLongPress判定が外れてしまい、早送りしなくなってしまった

  2. 上記問題を解消するために、対象の動画表示画面のviewのsetOnTouchListenerでイベントをハンドリングすることで回避できるかを検証した
    具体的な簡易コードとしては以下

view.setOnTouchListener { _, event ->
    when (event.action) {
        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
            // ここにログを貼って、全てのEventについて検証した
            longPressListener.onLongPress(onLongPressing = false)
        }
        else -> {
            // ここにログを貼って、全てのEventについて検証した
        }
    }
    gestureDetector.onTouchEvent(event)
    true
}

長押し状態が外れてしまう原因

長押し中に少しでも指を上下左右に動かしてしまうとonLongPress判定が外れてしまう根本原因は上記の、下スワイプで画面を閉じる仕様が影響していた
上記のコード内で全てのMotionEventに対してログを貼って検証するとACTION_CANCELに入っていたことがわかりました

ACTION_CANCELが呼ばれるパターンとしてはざっくり以下となっています

  • 親Viewによるモーションの奪取
  • タッチイベントのキャンセル
  • タッチ範囲外への移動
  • onTouchメソッドでの処理

今回は対象画面の親Activityで定義した、下にスワイプすると指の動きに画面ごと追従しながら閉じるBottomSheetBehaviorの操作、つまり「親Viewによるモーションの奪取」によって起きていたことが根本原因でした

解決した実装

完全に仕様を同じのまま問題を解決した状態で動作させることは難しかったですが、以下のように実装しました。
ポイントとしては、長押し中のスワイプアクションを親Viewに奪われないようにrequestDisallowInterceptTouchEvent(true)を追加します。
また、それだけだと上記仕様の下スワイプで画面を閉じる、が死んでしまうので動画表示画面で下スワイプしたときに外からdownSwipeListenerを与えることでコントロールできるようにしました(下スワイプ時、指の動きに画面ごと追従させる、は実現できていません)

 val gestureDetector = GestureDetector(view.context, object : GestureDetector.SimpleOnGestureListener() {
            private val swipeThreshold = 100
            private val swipeVelocityThreshold = 100

override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
    singleTapListener.onSingleTap()
    return true
}

            override fun onLongPress(e: MotionEvent) {
                longPressListener.onLongPress(onLongPressing = true)
                view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
            }

            override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
                val diffY = e2.y - e1!!.y
                if (abs(diffY) > swipeThreshold && abs(velocityY) > swipeVelocityThreshold) {
                    if (diffY > 0) {
                        // 下スワイプ検知
                        downSwipeListener.onDownSwipe()
                    }
                }
                return super.onFling(e1, e2, velocityX, velocityY)
            }
        })

        view.setOnTouchListener { _, event ->
        // 親View側で処理を奪ってほしくないので以下を追加
            view.parent.requestDisallowInterceptTouchEvent(true)
            
            when (event.action) {
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                    // 指が画面から離れるか、異常を検知したらonLongPressingを終了
                    longPressListener.onLongPress(onLongPressing = false)
                }
            }
            gestureDetector.onTouchEvent(event)
            true
        }
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?