ViewPagerの中に入れたImageViewで拡大縮小やスクロールをするためにやったこと
UdonRoadではツイートに添付されている画像を表示させるため、ViewPager
の中にImageView
を入れて使っている。これまでは特に何も考えず表示させていたが、パノラマっぽい横長の画像やマンガの中の小さな文字などを詳しく見るために画像を拡大できるようにしたくなったので次のような処理を追加した。
ViewPager
は左右のフリックジェスチャを受け取ってページを切り替えるため、それと競合するような横方向のジェスチャを受け取って処理したいとなると工夫が必要である。具体的には次の二つ。
- 真横方向のピンチイン/アウト
- 真横方向のスクロール(ドラッグ)
タッチイベントを受け取った後の処理の流れを大雑把に説明すると次のような感じ。画像の拡大縮小、移動でMatrix
を使っている理由は特にありません。
onTouchEvent
ScaleGestureDetector.onTouchEvent()
にMotionEvent
を渡してピンチイン/アウトのジェスチャを認識させるGestureDetector.onTouchEvent()
にMotionEvent
を渡してスクロール(ドラッグやスワイプ)ジェスチャを認識させる- ピンチイン/アウトまたはスクロールイベントが起きたらスケールファクタや移動量を受け取り、"画像変換のための
Matrix
“を更新する ViewParent.requestDisallowInterceptTouchEvent(true)
を呼ぶView.invalidate()
を呼んでviewを更新する
onDraw
- 画像の現在の
Matrix
をImageView.getImageMatrix()
で取得して"画像変換のためのMatrix
“をMatrix.postConcat()
で渡す - スケールの値が初期値より小さくならないよう補正する
- 画像が縦横方向に移動しすぎてフレームアウトしないように(若しくは、はみ出すようなサイズのときに背景が見えないように)補正する
- 補正した
Matrix
をImageView.setImageMatrix()
で渡す - “画像変換のための
Matrix
"をリセットする
- 画像の現在の
肝はViewParent.requestDisallowInterceptTouchEvent(true)
で、ImageView
で消費したいイベントが起きた時にこれを呼ばないと親のView
(ここではViewPager
)にイベントを取られてしまう。
あとは細かい処理やハマりポイントとして、次の2点を挙げておく。
- 隣のページに行きたそうなドラッグは
ImageView
で受け付けずに親に渡す(shouldGoNextPage()
みたいな名前のメソッドで実装している) ScaleGestureDetector.OnScareGestureListener.onScale()
にfalse
を返してもScaleGestureDetector.onTouchEvent()
はtrue
を返してくる。スクロールが起きているかどうかを知りたかったらScaleGestureDetector.isInProgress()
を呼ぶ
この記事の全ての事象はNexus5X(Android 7.1.2)で確認した。