반응형 이미지?
완성 이미지 예시
유튜브를 보다가 아래 영상을 보게 되었다. 해당 영상에서는 사용자가 클릭함에 따라 이미지가 기울어져 반응형 이미지를 만드는 법을 보여준다.
https://youtu.be/YDCCauu4lIk?si=LiDetcR1AwOzSk-i
해당 영상에서 나오는 코드는 html
로 설명되어 있다. 즉 웹 기반으로 만드는 방법을 설명한 영상이다.
이 영상을 보고, 그러면 Android
에서 Compose
를 통해 한 번 만들어보자 하고 도전해보았다.
pointerInteropFilter
아마 가장 필요한 Modifier
함수라고 생각한다.
사용자가 터치를 하였을 때 해당 위치를 어떻게 인식하느냐
에서 해당 위치
알 수 있도록 터치 이벤트
를 다룰 수 있게 해준다.
오늘 사용할 터치 이벤트는 크게 4가지를 사용하였다.
ACTION_DOWN
: 사용자가 화면을 터치할 때 발생한다.ACTION_MOVE
: 사용자가 화면 위에서 손가락을 움직일 때 발생한다.ACTION_UP
: 사용자가 화면에서 손가락을 떼었을 때 발생한다.ACTION_CANCEL
: 현재 터치 이벤트가 취소되었을 때 발생한다.
터치 이벤트
의 로직은 다음과 같다.
Content사용자 클릭 -> 움직이기 -> 클릭을 때기
graphicsLayer
graphicsLayer
는 Modifier
함수 중 하나로, UI
의 Scale/Rotation/Alpha/Shadow
값을 변경해준다.
반응형 이미지를 만들기 위해서는 rotation
을 사용해야 한다.
modifier = Modifier.graphicsLayer( rotationX = 10f)
다음과 같은 코드가 있다면, ' X축
을 중심으로 10도
만큼 회전한다' 라는 뜻이다.
그러면 클릭한 위치에 따라 어떻게 rotation
값을 동적
으로 변경해야 하는 공식은 어떻게 될까?
공식에 필요한 값들은 다음과 같다.
tounchX
: 사용자가 화면을 터치한x
좌표 이다. 이는 이전에서 설명한pointerInteropFilter
를 통해 얻을 수 있다.centerX
: 이미지의 중앙x
좌표이다. 이는 이미지의 크기에 절반 값을 취해 얻을 수 있다.touchX - centerX
: 중앙으로부터 터치 위치가 떨어져 있는지 알 수 있다.(touchX - centerX) / centerX
: 떨어진 위치를 다시 중앙x
값으로 나누게 되면,-1~1
사이의 값의 비율 형태로 얻을 수 있다. 즉, 중앙을 기준으로 터치한 위치의 상대적 값을 알 수 있다.
코드
상태 변수 정의
가장 먼저 필요한 상태 변수를 정의합니다.rotationX, rotationY
3D
회전의 값을 저장합니다.
cardSize
는 카드의 크기를 저장하여, 터치 위치를 정하기 위해 사용합니다.
var rotationX by remember { mutableStateOf(0f) }
var rotationY by remember { mutableStateOf(0f) }
var cardSize by remember {
mutableStateOf(Rect.Zero)
}
이미지 만들기
화면에 포켓몬 이미지를 넣어야 하기 때문에, Image
를 사용해서 생성해줍니다.
이 때 png
형태의 이미지를 drawable
에 저장하여 사용하니, painterResource()
를 이용하여 이미지를 가져왔습니다.
다음은 이미지의 크기와 위치를 가져오기 위해 onGloballyPositioned
를 통해 이미지의 크기를 cardSize
에 저장을 해주고,
.onGloballyPositioned { coordinates ->
cardSize = coordinates.boundsInParent()
}
graphicsLayer
를 통해 3D
회전을 적용해줍니다.
.graphicsLayer {
this.rotationY = rotationY
this.rotationX = rotationX
cameraDistance = 12f * density
}
여기서 caameraDistance
는 3D
변환을 위한 카메라와 이미지 사이의 거리를 의미합니다.density
의 경우에 사용자의 디바이스
의 픽셀 수를 의미합니다. 이를 통해 다른 기기들의 각 해상도에 맞춰 변환 효과를 제공합니다.
pointerInteropFilter
는 터치 이벤트를 처리합니다.
위에서 설명한 공식을 적용하여, 사용자가 눌렀을 때 해당 클릭 위치를 기반으로 rotationX/rotationY
의 값을 설정해줍니다.
사용자가 클릭을 해제하면 다시 0f
로 설정하여 원래 이미지로 돌아오도록 하였습니다.
.pointerInteropFilter { motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
val touchX = motionEvent.x
val touchY = motionEvent.y
rotationY = ((touchX - cardSize.width / 2) / (cardSize.width / 2)) * 15f
rotationX =
((touchY - cardSize.height / 2) / (cardSize.height / 2)) * -15f
true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
rotationX = 0f
rotationY = 0f
true
}
else -> false
}
}
전체 코드
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ReactedcardTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ClickAnimateCard()
}
} } }
}
@OptIn(ExperimentalComposeUiApi::class)
@Preview
@Composable
private fun ClickAnimateCard(modifier: Modifier = Modifier){
var rotationX by remember { mutableStateOf(0f) }
var rotationY by remember { mutableStateOf(0f) }
var cardSize by remember {
mutableStateOf(Rect.Zero)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
.background(Color.DarkGray)
){
Image(
painter = painterResource(id = R.drawable.rukario),
contentDescription = "rokario",
modifier = Modifier
.fillMaxSize()
.padding(15.dp)
.onGloballyPositioned { coordinates ->
cardSize = coordinates.boundsInParent()
}
.graphicsLayer {
this.rotationY = rotationY
this.rotationX = rotationX
cameraDistance = 12f * density
}
.pointerInteropFilter { motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
val touchX = motionEvent.x
val touchY = motionEvent.y
rotationY = ((touchX - cardSize.width / 2) / (cardSize.width / 2)) * 15f
rotationX =
((touchY - cardSize.height / 2) / (cardSize.height / 2)) * -15f
true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
rotationX = 0f
rotationY = 0f
true
}
else -> false
}
}
)
}
}
깃허브 주소
https://github.com/Myeongcheol-shin/reacted-card
[GitHub - Myeongcheol-shin/reacted-card
Contribute to Myeongcheol-shin/reacted-card development by creating an account on GitHub.
github.com](https://github.com/Myeongcheol-shin/reacted-card)
'안드로이드 > Compose' 카테고리의 다른 글
[Kotlin/Compose] SnowFall Effect 만들기 (0) | 2023.12.24 |
---|---|
[Kotlin/Compose] 슬라이드를 이용한 공 던지기 모션 만들기 (0) | 2023.12.23 |
[Kotlin/Compose] Scroll Fade-In/Out animation (0) | 2023.12.22 |
[Kotlin/Compose] Slide-Unlock(밀어서 잠금해제) 기능 개발하기 (2) (0) | 2023.12.15 |
[Kotlin/Compose] Slide-Unlock(밀어서 잠금해제) 기능 개발하기 (1) (0) | 2023.12.15 |