【Kotlin】タッチイベントをフックする【Androidアプリ開発】

【Kotlin】タッチイベントをフックする【Androidアプリ開発】
【Kotlin】タッチイベントをフックする【Androidアプリ開発】

動画のように画面に指を触れて動かしたときの移動量を取得できるようなプログラムを作ってみた。

TouchableFragmentクラス

AndroidStudioのレイアウト画面
AndroidStudioのレイアウト画面

今回はフラグメントを使用してタッチイベントのフックを行った。 TouchableFragmentクラスを作成し、フラグメントのレイアウトファイルにイベントフック専用のビューであるtouchableViewを配置した。 このビューをフラグメントのonCreateViewで取得しリスナー登録を済ませておく。

kotlin
override fun onCreateView(
	inflater: LayoutInflater, container: ViewGroup?,
	savedInstanceState: Bundle?
): View? {

	val view = inflater.inflate(R.layout.fragment_touchable, container, false)
	view.findViewById<View>(R.id.touchableView).setOnTouchListener(this)
	return view
}

次にTouchableFragmentクラスにView.OnTouchListenerインタフェースを実装する。そしてそのメンバメソッドであるonTouchメソッドを次のようにオーバーライドする。

kotlin
private var yPrec = 0.0f

override fun onTouch(v: View?, event: MotionEvent?): Boolean {
	when (event?.actionMasked) {
		MotionEvent.ACTION_DOWN -> {
			yPrec = event.getY(0)
			listener?.actionDown()
		}
		MotionEvent.ACTION_MOVE -> {
			val dy = yPrec - event.getY(0)
			listener?.actionMove(dy)
		}
		MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
			listener?.actionUp()
		}
	}
	return true
}

今回は移動量を計算させているが他にもVelocityTrackerを使って指の速度を得ることもできるようになっている。詳しくはこちらを参考にしてもらいたい。

タップとポインタの動きのトラッキング - Androidデベロッパードキュメントガイド

さてTouchableFragmentクラスの全体のプログラム内容は次のようになっている。

kotlin
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

class TouchableFragment : Fragment(),View.OnTouchListener {
    private var param1: String? = null
    private var param2: String? = null
    private var listener: OnTouchableFragmentListener? = null

    private var yPrec = 0.0f

    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        when (event?.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                yPrec = event.getY(0)
                listener?.actionDown()
            }
            MotionEvent.ACTION_MOVE -> {
                val dy = yPrec - event.getY(0)
                listener?.actionMove(dy)
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                listener?.actionUp()
            }
        }
        return true
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val view = inflater.inflate(R.layout.fragment_touchable, container, false)
        view.findViewById<View>(R.id.touchableView).setOnTouchListener(this)
        return view
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnTouchableFragmentListener) {
            listener = context
        } else {
            throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    interface OnTouchableFragmentListener {
        fun actionDown()
        fun actionMove(dy: Float)
        fun actionUp()
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            TouchableFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}

MainActivityクラス

次にMainActivityクラスでの実装を行っていく。アクティビティのレイアウトファイルの左半分にはテキストビューを設置し、右半分にはフラグメントを入れ込むためにフレームレイアウトを設置した。

そしてMainActivityから動的にフラグメントを生成させるためのコードは次のようになる。

kotlin
private const val TOUCHABLE_FRAGMENT_TAG = "touchableFragment"

class MainActivity : AppCompatActivity(), TouchableFragment.OnTouchableFragmentListener {

    override fun actionDown() {
        @SuppressLint("SetTextI18n")
        resultTextView.text = "Action Down\n${resultTextView.text}"
    }

    override fun actionMove(dy: Float) {
        @SuppressLint("SetTextI18n")
        resultTextView.text = "dy: $dy\n" +
                "${resultTextView.text}"
    }

    override fun actionUp() {
        @SuppressLint("SetTextI18n")
        resultTextView.text = "\nAction Up\n" +
                "${resultTextView.text}"
    }

    lateinit var resultTextView:TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        resultTextView = findViewById(R.id.result)

        if (supportFragmentManager.findFragmentByTag(TOUCHABLE_FRAGMENT_TAG) == null) {
            supportFragmentManager.beginTransaction()
                .add(R.id.container, TouchableFragment.newInstance("hoge", "fuga"), TOUCHABLE_FRAGMENT_TAG)
                .commit()
        }
    }

}

フラグメントをファクトリメソッドで生成し引数を渡せるようにしている。またフラグメントからのコールバックを受けて取れるようにインターフェイスを使った実装を行っている。先頭のactionDownactionMoveactionUpメソッドはTouchableFragmentからのコールバック関数となる。actionMoveでは指の移動量が渡されるのでその値をテキストビューにセットして表示させている。

最後にプロジェクトを実行し、画面右半分の中で指を動かせばテキストビューに移動量が表示されるだろう。

AndroidStudioのレイアウト画面
AndroidStudioのレイアウト画面

タッチイベントの処理をアクティビティに直接書いてしまうとアクティビティが読みづらくなってしまうので、できるだけフラグメントに追い出して管理したい。

指で画面に触れながらタッチイベントを取得
指で画面に触れながらタッチイベントを取得

参考

タップとポインタの動きのトラッキング - Androidデベロッパードキュメントガイド android: move a view on touch move (ACTION_MOVE) - Stack Overflow

▼ こんな記事も書いてます。

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム

関連記事