こんにちは!ぱかぱかです!
お客様都合で今日もお休みを頂いているのですが、これでついに13連休も幕を閉じます…寂しいですね…
正月休みは小・中・高の友達にそれぞれ会ったり美味しいものを食べたりアプリを作ったり実家の猫と戯れたり、なかなか充実した時間になりました。
仕事が始まってもメリハリを付けていい時間の使い方をしていきたいですね。
今日は現在開発を進めている飲食店記録アプリ「グルメノート」で、RecyclerViewのセルをタップした際のイベントを検知する処理についてまとめていきたいと思います。
今回のゴール
行った店リスト(RecyclerView)のセルをタップした際にタップイベントを検知できるようにする。
次回以降でシングルタップ時に行った店の詳細画面に飛べるようにしたい。
ロングタップ時でメニュー表示も。
リスナーの実装
RecyclerViewのタップ処理実装方法は調べてみると色々なパターンがありそうでした。
実装方法の候補
・SimpleOnItemTouchListener
→単純なタップやロングタップに対応できる
・ItemTouchHelper
→ドラッグやスワイプに対応できる
・自前でリスナーのインターフェースを作りViewHolderに適用
→単純なタップ等
今回はSimpleOnItemTouchListenerを使ってみたいと思います。
今後はスワイプで削除とかもやりたいのでItemTouchHelperも使いたい…(実装した後に気づいた…)
両方併用することも可能そうなので今度試してみます。
SimpleOnItemTouchListenerの継承
SimpleOnItemTouchListenerというRecyclerViewのインターフェースを継承したリスナークラスを作成し、クリック処理を実装していきます。
RecyclerView.SimpleOnItemTouchListener | Android Developers
// SimpleOnItemTouchListenerを継承したリスナークラス class RecyclerItemClickListener() : RecyclerView.SimpleOnItemTouchListener() { }
onInterceptTouchEventの実装
onInterceptTouchEventはタップイベントを監視するメソッドです。
RecyclerView.SimpleOnItemTouchListener | Android Developers
実装することで、MotionEventを検知した際にRecyclerViewのスクロール動作に割り込んで実装したタップイベントを先に処理できるようになります。
// SimpleOnItemTouchListenerを継承したリスナークラス class RecyclerItemClickListener() : RecyclerView.SimpleOnItemTouchListener() { override fun onInterceptTouchEvent(recyclerView: RecyclerView, event: MotionEvent): Boolean { // タップイベント検知時の処理 } }
インターフェースの作成
リスナークラス内に実装するインターフェースを準備します。
今回はシングルタップ時とロングタップ時のメソッドを作ることにします。
// SimpleOnItemTouchListenerを継承したリスナークラス class RecyclerItemClickListener() : RecyclerView.SimpleOnItemTouchListener() { interface OnRecyclerClickListener { // シングルタップ用 fun onItemClick(view: View, position: Int) // ロングタップ用 fun onItemLongClick(view: View, position: Int) } override fun onInterceptTouchEvent(recyclerView: RecyclerView, event: MotionEvent): Boolean { // タップイベント検知時の処理 } }
GestureDetectorでタップイベントの種類を判別
検知したタップイベントをGestureDetectorに渡すことでシングルタップやダブルタップなどを判別できるようにします。
GestureDetector | Android Developers
参考までに、GestureDetector.SimpleOnGestureListenerのメソッドには以下のように様々な種類があります。
メソッド名 | 検知する操作 |
---|---|
onContextClick | コンテキストクリック(コンテキストメニューを表示するクリック、長押しなど) |
onDoubleTap | ダブルタップ |
onDoubleTapEvent | ダブルタップの開始、移動、終了(DOWN, MOVE, UP) |
onDown | 押下(DOWN) |
onFling | サッとなぞる |
onLongPress | ロングタップ |
onScroll | 押下して画面上を移動 |
onShowPress | 押下して少し待つ |
onSingleTapConfirmed | シングルタップ |
onSingleTapUp | 押下して指を離した時(UP) |
今回は一旦、シングルタップとロングタップを実装したいと思います。
GestureDetectorをインスタンス化し、シングルタップを検知するonSingleTapConfirmedとロングタップを検知するonLongPressを実装します。
class RecyclerItemClickListener( context: Context, recyclerView: RecyclerView, private val listener: OnRecyclerClickListener ) : RecyclerView.SimpleOnItemTouchListener() { ... // GestureDetectorのインスタンス化 private val gestureDetector = GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() { // シングルタップ override fun onSingleTapConfirmed(e: MotionEvent): Boolean { val childView = recyclerView.findChildViewUnder(e.x, e.y) if (childView != null) { listener.onItemClick( childView, recyclerView.getChildAdapterPosition(childView) ) } return true } // ロングタップ override fun onLongPress(e: MotionEvent) { val childView = recyclerView.findChildViewUnder(e.x, e.y) if (childView != null) { listener.onItemLongClick( childView, recyclerView.getChildAdapterPosition(childView) ) } super.onLongPress(e) } }) ... }
RecyclerItemClickListenerのコンストラクタ引数にContext, RecyclerView, OnRecyclerClickListenerを追加しています。
各overrideメソッドではクリックされたRecyclerView内の子ビューがある場合はそれを取得し、先ほど作成したOnRecyclerClickListenerインターフェースのメソッドに子ビューとそのポジションを渡します。
返り値がtrueの時、RecyclerViewやその子ビューに定義されているイベントを止めてタップイベントが挿入されます。
ただしonLongPressでは検知した後にRecyclerViewのスクロールができなくなてしまっては困るので、trueを返さないようにしています。
onTouchEventの呼び出し
onInterceptTouchEventの中でGestureDetectorのonTouchEventを呼び出すようにします。
class RecyclerItemClickListener( context: Context, recyclerView: RecyclerView, private val listener: OnRecyclerClickListener ) : RecyclerView.SimpleOnItemTouchListener() { interface OnRecyclerClickListener { ... } private val gestureDetector = GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() { ... }) override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { return gestureDetector.onTouchEvent(e) } }
これでタップイベントが以下の流れで処理されるようになります。
タップイベント発生
→onInterceptTouchEventで検知
→GestureDetector#onTouchEvent呼び出し
→該当するタップイベントのoverrideメソッド呼び出し
→OnRecyclerClickListenerインターフェースのメソッド呼び出し
というわけで最後はOnRecyclerClickListenerインターフェースのメソッドを実装するだけです。
インターフェースの実装
シングルタップ時に行った店の詳細画面に遷移するようにしたいのですが、その実装は次回の記事に回したいと思います。
今回はタップ時にログが出力されることを確かめます。
行った店リストを表示するVisitedRestaurantFragmentでインターフェースRecyclerItemClickListener.OnRecyclerClickListenerを継承し、それぞれログを出力するようメソッドを実装します。
class VisitedRestaurantFragment : Fragment(), RecyclerItemClickListener.OnRecyclerClickListener { ... // シングルタップ時の処理 override fun onItemClick(view: View, position: Int) { Log.d("VisitedRestaurantFragment", "Single tap position: $position") } // ロングタップ時の処理 override fun onItemLongClick(view: View, position: Int) { Log.d("VisitedRestaurantFragment", "Long tap position: $position") } }