fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

ViewPagerの中に入れたImageViewで拡大縮小やスクロールをするためにやったこと

UdonRoadではツイートに添付されている画像を表示させるため、ViewPagerの中にImageViewを入れて使っている。これまでは特に何も考えず表示させていたが、パノラマっぽい横長の画像やマンガの中の小さな文字などを詳しく見るために画像を拡大できるようにしたくなったので次のような処理を追加した。

github.com

ViewPagerは左右のフリックジェスチャを受け取ってページを切り替えるため、それと競合するような横方向のジェスチャを受け取って処理したいとなると工夫が必要である。具体的には次の二つ。

  • 真横方向のピンチイン/アウト
  • 真横方向のスクロール(ドラッグ)

タッチイベントを受け取った後の処理の流れを大雑把に説明すると次のような感じ。画像の拡大縮小、移動でMatrixを使っている理由は特にありません。

  • onTouchEvent
    • ScaleGestureDetector.onTouchEvent()MotionEventを渡してピンチイン/アウトのジェスチャを認識させる
    • GestureDetector.onTouchEvent()MotionEventを渡してスクロール(ドラッグやスワイプ)ジェスチャを認識させる
    • ピンチイン/アウトまたはスクロールイベントが起きたらスケールファクタや移動量を受け取り、"画像変換のためのMatrix“を更新する
    • ViewParent.requestDisallowInterceptTouchEvent(true)を呼ぶ
    • View.invalidate()を呼んでviewを更新する
  • onDraw
    • 画像の現在のMatrixImageView.getImageMatrix()で取得して"画像変換のためのMatrix“をMatrix.postConcat()で渡す
    • スケールの値が初期値より小さくならないよう補正する
    • 画像が縦横方向に移動しすぎてフレームアウトしないように(若しくは、はみ出すようなサイズのときに背景が見えないように)補正する
    • 補正したMatrixImageView.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)で確認した。