Hanbit the Developer
[Android] Touch event to move & pinch zoom 본문
구현 결과
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
}
}
'Android' 카테고리의 다른 글
Android Library FiveStarsView has been released. (0) | 2023.02.13 |
---|---|
android/nowinandroid 구조 파헤치기 (0) | 2022.11.30 |
[Android] Saved image is not appearing in gallery immediately (0) | 2022.11.28 |
[Android] MVVM + Hilt + Retrofit2 + Recycler View 간단한 예시(+ Clean Architecture) (4) | 2022.10.11 |
모두의 PICK 서비스 Refactoring History (0) | 2022.09.09 |