Hanbit the Developer

[Android] Touch event to move & pinch zoom 본문

Mobile/Android

[Android] Touch event to move & pinch zoom

hanbikan 2022. 11. 30. 15:27

구현 결과

 

 

1. Touch to move

터치하여 움직이기를 구현하기 위해 저는 setOnTouchListener()를 다음과 같이 적용하였습니다.

// 이동
var startTouchX = 0.0f
var startTouchY = 0.0f
var startPoseImageX = 0.0f
var startPoseImageY = 0.0f

binding.root.setOnTouchListener { _, event ->
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            startTouchX = event.x
            startTouchY = event.y
            startPoseImageX = binding.imagePose.x
            startPoseImageY = binding.imagePose.y
        }
        MotionEvent.ACTION_MOVE -> {
            binding.imagePose.x = startPoseImageX + (event.x - startTouchX)
            binding.imagePose.y = startPoseImageY + (event.y - startTouchY)
            binding.imagePose.requestLayout()
        }
    }
    false
}

먼저 터치를 시작할 때(ACTION_DOWN), 터치한 지점의 좌표와 그때의 ImageView의 좌표를 저장합니다. 그 다음 손가락을 움직일 때(ACTION_MOVE) ImageView의 좌표에 다음과 같은 식을 적용합니다.

ImageView의 좌표 = ImageView 시작 좌표 + (현재 터치 좌표 - 시작 터치 좌표)

 

하지만 이렇게 했을 때 아래와 같은 버그가 발생하였습니다.

문제의 원인은, ACTION_DOWN은 터치를 처음으로 시작할 때만 호출되기 때문에 시작 좌표와 event의 좌표가 서로 다른 터치 포인터를 가리키게 되기 때문입니다.

따라서 ACTION_MOVE에서 터치 포인터가 추가될 경우 시작 좌표들을 초기화하여 문제를 해결하였습니다.

// 이동
var prevPointerCount = 0
var startTouchX = 0.0f
var startTouchY = 0.0f
var startPoseImageX = 0.0f
var startPoseImageY = 0.0f

binding.root.setOnTouchListener { _, event ->
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            startTouchX = event.x
            startTouchY = event.y
            startPoseImageX = binding.imagePose.x
            startPoseImageY = binding.imagePose.y
        }
        MotionEvent.ACTION_MOVE -> {
            if (event.pointerCount != prevPointerCount) {
                prevPointerCount = event.pointerCount
                startTouchX = event.x
                startTouchY = event.y
                startPoseImageX = binding.imagePose.x
                startPoseImageY = binding.imagePose.y
            }

            binding.imagePose.x = startPoseImageX + (event.x - startTouchX)
            binding.imagePose.y = startPoseImageY + (event.y - startTouchY)
            binding.imagePose.requestLayout()
        }
    }
    false
}

 

 

2. Pinch zoom

핀치줌은 의외로 간단합니다.

// 확대, 축소
var scaleFactor = 1.0f
poseImageScaleGestureDetector = ScaleGestureDetector(requireContext(), object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
    override fun onScale(detector: ScaleGestureDetector): Boolean {
        // scaleFactor 범위: [POSE_MIN_SCALE] ~ [POSE_MAX_SCALE]
        val nextScaleFactor = scaleFactor*(detector.scaleFactor)
        scaleFactor = Math.max(POSE_MIN_SCALE, Math.min(nextScaleFactor, POSE_MAX_SCALE))
        binding.imagePose.scaleX = scaleFactor
        binding.imagePose.scaleY = scaleFactor
        return true
    }
})

// ...

binding.root.setOnTouchListener { _, event ->
    // 확대, 축소 이벤트
    poseImageScaleGestureDetector.onTouchEvent(event)
    
    // ...

 

대상 뷰의 scale에 detector.scaleFactor를 넣어주는 ScaleGestureDetector 내부를 구성하고, ScaleGestureDetector의 onTouchEvent()를 터치 리스너 내부에서 실행시켜주면 됩니다.

다만 저는 POST_MIN_SCALE, POST_MAX_SCALE로 최대/최소 스케일 범위를 지정해주었습니다.

 

전체 코드

@SuppressLint("ClickableViewAccessibility")
    private fun setPreviewViewOnTouchListenerForPoseImage() {
        // 확대, 축소
        var scaleFactor = 1.0f
        poseImageScaleGestureDetector = ScaleGestureDetector(requireContext(), object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
            override fun onScale(detector: ScaleGestureDetector): Boolean {
                // scaleFactor 범위: [POSE_MIN_SCALE] ~ [POSE_MAX_SCALE]
                val nextScaleFactor = scaleFactor*(detector.scaleFactor)
                scaleFactor = Math.max(POSE_MIN_SCALE, Math.min(nextScaleFactor, POSE_MAX_SCALE))
                binding.imagePose.scaleX = scaleFactor
                binding.imagePose.scaleY = scaleFactor
                return true
            }
        })

        // 이동
        var prevPointerCount = 0
        var startTouchX = 0.0f
        var startTouchY = 0.0f
        var startPoseImageX = 0.0f
        var startPoseImageY = 0.0f

        binding.root.setOnTouchListener { _, event ->
            // 확대, 축소 이벤트
            poseImageScaleGestureDetector.onTouchEvent(event)

            // 터치하여 이동
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    startTouchX = event.x
                    startTouchY = event.y
                    startPoseImageX = binding.imagePose.x
                    startPoseImageY = binding.imagePose.y
                }
                MotionEvent.ACTION_MOVE -> {
                    if (event.pointerCount != prevPointerCount) {
                        prevPointerCount = event.pointerCount
                        startTouchX = event.x
                        startTouchY = event.y
                        startPoseImageX = binding.imagePose.x
                        startPoseImageY = binding.imagePose.y
                    }

                    binding.imagePose.x = startPoseImageX + (event.x - startTouchX)
                    binding.imagePose.y = startPoseImageY + (event.y - startTouchY)
                    binding.imagePose.requestLayout()
                }
            }
            false
        }
    }