Open1

自作AndroidアプリのTips

TA2YATA2YA

ズーム機能を実装する

概要

カメラの映像を表示しているViewに対してピンチイン・ピンチアウトによりズーム機能を実装する。
ズームを行い倍率が等倍より大きくなっている場ありのみドラッグによる画面の移動ができるものとし、ダブルタップにより倍率と表示位置のリセットを行う。

実装

ScaleGestureDetector

スケールの変更を検知するためにScaleGestureDetectorの実装を行う。
今回の要件では用意されている機能の一部のみ使用するため、公式ドキュメントの記載に従ってサブクラスであるSimpleOnScaleGestureListenerを継承する。

package com.tatsuya.babymonitor.ui.zoom

import android.view.ScaleGestureDetector
import android.view.View

class ZoomScaleGestureListener(private val view: View) : ScaleGestureDetector.SimpleOnScaleGestureListener() {
    private var scaleFactor = 1.0f
    private var focusX = 0f
    private var focusY = 0f

    /**
     * スケールの変更を検知した時点で1度だけ発火
     * 倍率変更時の基準となる位置を記憶
     */
    override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
        focusX = detector.focusX
        focusY = detector.focusY
        return true
    }

    /**
     * スケールの変更が行われている間定期的に発火
     * 倍率の指定と位置の固定(デフォルト値から変更しない)を行う
     */
    override fun onScale(detector: ScaleGestureDetector): Boolean {
        scaleFactor *= detector.scaleFactor
        scaleFactor = 1.0f.coerceAtLeast(scaleFactor.coerceAtMost(5.0f))
        view.scaleX = scaleFactor
        view.scaleY = scaleFactor
        view.pivotX = focusX
        view.pivotY = focusY
        return true
    }
}

GestureDetector

ドラッグとダブルタップの検知のためにGestureDetectorの実装を行う。今回の要件では用意されている機能の一部のみ使用するため、公式ドキュメントの記載に従ってサブクラスであるSimpleOnGestureListenerを継承する。

package com.tatsuya.babymonitor.ui.zoom

import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View

class ZoomGestureListener(private val view: View) : GestureDetector.SimpleOnGestureListener() {
    /**
     * タッチの検知開始イベント(何もしない)
     */
    override fun onDown(e: MotionEvent): Boolean {
        return true
    }
    
    /**
     * ダブルタップを検知して、倍率と位置をリセットする
     */
    override fun onDoubleTap(e: MotionEvent): Boolean {
        view.scaleX = 1.0f
        view.scaleY = 1.0f
        view.translationX = 0f
        view.translationY = 0f
        return true
    }

    /**
     * ドラッグ(スクロール)を検知して、表示位置を変更する
     * ただし、倍率が等倍以下の場合は移動させない
     */
    override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        if (view.scaleX <= 1.0f && view.scaleY <= 1.0f) {
            return true
        }

        view.translationX -= distanceX
        view.translationY -= distanceY
        return true
    }
}