Tap it! Shake it! Fling it! Sheep it! The Gesture Animations Dance! (2024)

Table of Contents
Tap it! Shake it! Fling it! Sheep it! The Gesture • 안드로이드 3년차 개발자 • SOPT IT 벤처창업 동아리 Android 오늘의 발표는? 오늘의 발표는? 오늘의 발표는? Compose Multiplatform Animation With Gesture And Sensor Jetpack Compose 란? Jetpack Compose 란? 제 생일도 7월 28일 !! 간단하게 Compose Mulitplatform에 대해 알아보자! 우리는 오늘 무엇을 중점적으로 알아볼 것인가? Gestures Sensors Animations Data 애니메이션을 어떻게 만들 것 인가? 1. What: 무엇을 애니메이션화 할 것인가? 2. When: 언제 애니메이션을 Scale Translation Rotation Alpha 1. What: 무엇을 애니메이션화 할 것인가? 1. What: 무엇을 애니메이션화 할 것인가? 2. When: 언제 애니메이션을 2. When: 언제 애니메이션을 발생시킬 것인가? https://developer.android.com/develop/ui/compose/touch-input/pointer-input/drag-swipe-fling Drag Swife 1. What: 무엇을 애니메이션화 할 것인가? 2. When: 언제 애니메이션을 3. How: 어떻게 애니메이션을 구현할 것인가? Animation Spec Compose Animation APIs! 어떻게 애니메이션을 구현할 것인가? 애니메이션을 어떻게 만들 것 인가? 1. What: 무엇을 애니메이션화 할 Gestures! Tab To Scale! Step #1 1. What? 크기 2. When? Tab To Scale! // 1. What? val scale = 1f Tab To Scale! // 1. What? val scale = 1f Tab To Scale! // 1. What? val scale = 1f 잠깐!! graphicsLayer 만 짚고 넘어가보자 https://developer.android.com/develop/ui/compose/graphics/draw/modifiers#graphics-modifiers Composition Layout Drawing Drawing Tab To Scale! // 1. What? val scale = 1f Tab To Scale! // 1. What? val scale = 1f Tab To Scale! // 1. What? val scale = 1f Tab To Scale! // 1. What? val scale = remember Tab To Scale! // 1. What? val scale = remember Tab To Scale! // 1. What? val scale = remember Tab To Scale! // 1. What? val scale = remember Tab To Scale! // 3. How? onClick = { coroutineScope.launch Tab To Scale! // 3. How? onClick = { coroutineScope.launch Animation Spec Spring ­ Stiff KeyFrame Spring ­ Nonstiff Tab To Scale! // 3. How? onClick = { coroutineScope.launch Tab To Scale! // 3. How? onClick = { coroutineScope.launch Reposition (Drag) ! Step #1 1. What? • 위치(translation) 2. Reposition (Drag) ! // 1. What? val translation = remember Reposition (Drag) ! // 1. What? val translation = remember Reposition (Drag) ! // 1. What? val translation = remember Reposition (Drag) ! // 1. What? val translation = remember Reposition (Drag) ! // 1. What? val translation = remember Reposition (Drag) ! // 1. What? val translation = remember Fling and Back Step #1 Screen_recording_20240625_210341-ezgif.com- video-to-gif-converter.gif 1. What? • Fling and Back // 1. What? val translation = remember Fling and Back // 2. When? ComposableKodee( modifier = Modifier Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>() Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>() Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>() Fling and Back // 3. How? // 2. If the Fling and Back // 3. How? // 2. If the Fling and Back // 3. How? // 2. If the Fling and Back // 3. How? // 2. If the Fling and Back // 3. How? // 2. If the Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>() 우리는 오늘 무엇을 중점적으로 알아볼 것인가? Gestures Sensors Animations Data Sensors! 센서 활용하기 3. Sensor Event 처리하기 1.필요한 Sensor를 선택하기 2. 센서..?? 센서 Android Sensor Manager iOS Sensor Manager Multiplatform Sensor Manager MultiPlatform Sensor Manager interface MultiplatformSensorManager { fun registerListener( sensorType: MultiplatformSensorType, MultiPlatform Sensor Manager // Android @Composable actual fun rememberSensorManager(): MultiplatformSensorManager Multiplatform Sensor Manager // Android class AndroidSensorManager(private val context: Context) Multiplatform Sensor Manager // Android class AndroidSensorManager(private val context: Context) Multiplatform Sensor Manager // Android class AndroidSensorManager(private val context: Context) Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager { Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager { Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager { Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager { Multiplatform Sensor Manager class iOSSensorManager : MultiplatformSensorManager { private val Rotation Shift 1. What? 회전 시키기 2. When? 디바이스의 방향이 Orientation - Android - Roll • Y 축 회전 • Orientation ­ iOS Yaw • Z축 회전 • -180º에서 180º Orientation ­ Shared Code data class DeviceOrientation( val azimuth: Float, Orientation ­ Shared Code data class DeviceOrientation() // Main interface Rotation Shift // 1. What? val rotationX = remember { Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager() Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager() Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager() Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager() Rotation Shift // 1. What? val rotationX = remember { Rotation Shift // 1. What? val rotationX = remember { 우리는 오늘 무엇을 중점적으로 알아볼 것인가? Gestures Sensors Animations Data 더 많은 것을 배우고 싶다면? • Play around with the Thank you References
  • Tap it! Shake it! Fling it! Sheep it! The Gesture

    Animations Dance! @KwakEuiJin KotlinConf’24 Korea

  • • 안드로이드 3년차 개발자 • SOPT IT 벤처창업 동아리 Android

    파트장 곽의진(KEZ) github.com/KwakEuiJin

  • 오늘의 발표는?

  • 오늘의 발표는?

  • 오늘의 발표는? Compose Multiplatform Animation With Gesture And Sensor

  • Jetpack Compose 란?

  • Jetpack Compose 란? 제 생일도 7월 28일 !!

  • 간단하게 Compose Mulitplatform에 대해 알아보자!

  • 우리는 오늘 무엇을 중점적으로 알아볼 것인가? Gestures Sensors Animations Data

  • 애니메이션을 어떻게 만들 것 인가?

  • 1. What: 무엇을 애니메이션화 할 것인가? 2. When: 언제 애니메이션을

    발생시킬 것인가? 3. How: 어떻게 애니메이션을 구현할 것인가?

  • Scale Translation Rotation Alpha 1. What: 무엇을 애니메이션화 할 것인가?

  • 1. What: 무엇을 애니메이션화 할 것인가? 2. When: 언제 애니메이션을

    발생시킬 것인가? 3. How: 어떻게 애니메이션을 구현할 것인가?

  • 2. When: 언제 애니메이션을 발생시킬 것인가? https://developer.android.com/develop/ui/compose/touch-input/pointer-input/drag-swipe-fling Drag Swife

  • 1. What: 무엇을 애니메이션화 할 것인가? 2. When: 언제 애니메이션을

    발생시킬 것인가? 3. How: 어떻게 애니메이션을 구현할 것인가?

  • 3. How: 어떻게 애니메이션을 구현할 것인가? Animation Spec

  • Compose Animation APIs! 어떻게 애니메이션을 구현할 것인가?

  • 애니메이션을 어떻게 만들 것 인가? 1. What: 무엇을 애니메이션화 할

    것인가? 2. When: 언제 애니메이션을 발생시킬 것인가? 3. How: 어떻게 애니메이션을 구현할 것인가? Animation을 만들기 위한 3원칙

  • Gestures!

  • Tab To Scale! Step #1 1. What? 크기 2. When?

    컴포저블이 터치되었을 때 3. How? 크기 1.0 <-> 1.2 Screen_recording_20240625_191219.gif

  • Tab To Scale! // 1. What? val scale = 1f

    // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale scaleY = scale } .clickable( onClick = { . . . }) . . . // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale }

  • Tab To Scale! // 1. What? val scale = 1f

    // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale scaleY = scale } .clickable( onClick = { . . . }) . . . // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale }

  • Tab To Scale! // 1. What? val scale = 1f

    // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale scaleY = scale } .clickable( onClick = { . . . }) . . . // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale } graphicsLayer?

  • 잠깐!! graphicsLayer 만 짚고 넘어가보자 https://developer.android.com/develop/ui/compose/graphics/draw/modifiers#graphics-modifiers Composition Layout Drawing Drawing

    명령어 변환

  • Tab To Scale! // 1. What? val scale = 1f

    // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale scaleY = scale } .clickable( onClick = { . . . }) . . . // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale }

  • Tab To Scale! // 1. What? val scale = 1f

    // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale scaleY = scale } .clickable( onClick = { . . . }) . . . // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale } No Animations!

  • Tab To Scale! // 1. What? val scale = 1f

    // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale.value scaleY = scale.value } .clickable(onClick = { . . . }) // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale }

  • Tab To Scale! // 1. What? val scale = remember

    { Animatable(1f) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale.value scaleY = scale.value } .clickable(onClick = { . . . }) // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale }

  • Tab To Scale! // 1. What? val scale = remember

    { Animatable(1f) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale.value scaleY = scale.value } .clickable(onClick = { . . . }) // 3. How? onClick = { val newScale = if (scale == 1f) 1.2f else 1f scale = newScale }

  • Tab To Scale! // 1. What? val scale = remember

    { Animatable(1f) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale.value scaleY = scale.value } .clickable(onClick = { . . . }) // 3. How? onClick = { coroutineScope.launch { val newScale = if (scale.value == 1f) 1.2f else 1f scale.animateTo(newScale) } }

  • Tab To Scale! // 1. What? val scale = remember

    { Animatable(1f) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { // Important: set scale in graphicsLayer(!) scaleX = scale.value scaleY = scale.value } .clickable(onClick = { . . . }) // 3. How? onClick = { coroutineScope.launch { val newScale = if (scale.value == 1f) 1.2f else 1f scale.animateTo(newScale) } }

  • Tab To Scale! // 3. How? onClick = { coroutineScope.launch

    { val newScale = if (scale.value == 1f) 1.2f else 1f scale.animateTo(newScale) } }

  • Tab To Scale! // 3. How? onClick = { coroutineScope.launch

    { val newScale = if (scale.value == 1f) 1.2f else 1f scale.animateTo( targetValue = newScale, animationSpec = // animation spec ) . . .

  • Animation Spec Spring ­ Stiff KeyFrame Spring ­ Nonstiff

  • Tab To Scale! // 3. How? onClick = { coroutineScope.launch

    { val newScale = if (scale.value == 1f) 1.2f else 1f scale.animateTo( targetValue = newScale, animationSpec = spring( dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessHigh, ) ) . . .

  • Tab To Scale! // 3. How? onClick = { coroutineScope.launch

    { val newScale = if (scale.value == 1f) 1.2f else 1f scale.animateTo( targetValue = newScale, animationSpec = spring( dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessHigh, ) ) . . .

  • Reposition (Drag) ! Step #1 1. What? • 위치(translation) 2.

    When? • 컴포저블을 드래그 했을 때 3. How? • 새로운 위치로 이동

  • Reposition (Drag) ! // 1. What? val translation = remember

    { Animatable(Offset(0f, 0f), Offset.VectorConverter) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { translationX = translation.value.x translationY = translation.value.y } .draggable2D(state = draggableState) ... ) // 3. How? val draggableState = rememberDraggable2DState { delta -> coroutineScope.launch { translation.snapTo(translation.value.plus(delta)) } }

  • Reposition (Drag) ! // 1. What? val translation = remember

    { Animatable(Offset(0f, 0f), Offset.VectorConverter) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { translationX = translation.value.x translationY = translation.value.y } .draggable2D(state = draggableState) ... ) // 3. How? val draggableState = rememberDraggable2DState { delta -> coroutineScope.launch { translation.snapTo(translation.value.plus(delta)) } }

  • Reposition (Drag) ! // 1. What? val translation = remember

    { Animatable(Offset(0f, 0f), Offset.VectorConverter) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { translationX = translation.value.x translationY = translation.value.y } .draggable2D(state = draggableState) ... ) // 3. How? val draggableState = rememberDraggable2DState { delta -> coroutineScope.launch { translation.snapTo(translation.value.plus(delta)) } }

  • Reposition (Drag) ! // 1. What? val translation = remember

    { Animatable(Offset(0f, 0f), Offset.VectorConverter) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { translationX = translation.value.x translationY = translation.value.y } .draggable2D(state = draggableState) ... ) // 3. How? val draggableState = rememberDraggable2DState { delta -> coroutineScope.launch { translation.snapTo(translation.value.plus(delta)) } }

  • Reposition (Drag) ! // 1. What? val translation = remember

    { Animatable(Offset(0f, 0f), Offset.VectorConverter) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { translationX = translation.value.x translationY = translation.value.y } .draggable2D(state = draggableState) ... ) // 3. How? val draggableState = rememberDraggable2DState { delta -> coroutineScope.launch { translation.snapTo(translation.value.plus(delta)) } }

  • Reposition (Drag) ! // 1. What? val translation = remember

    { Animatable(Offset(0f, 0f), Offset.VectorConverter) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { translationX = translation.value.x translationY = translation.value.y } .draggable2D(state = draggableState) ... ) // 3. How? val draggableState = rememberDraggable2DState { delta -> coroutineScope.launch { translation.snapTo(translation.value.plus(delta)) } }

  • Fling and Back Step #1 Screen_recording_20240625_210341-ezgif.com- video-to-gif-converter.gif 1. What? •

    위치(translation) 2. When? • 컴포저블을 던지는 드래그 모션이 발생했을 때 3. How? • 컴포저블이 위치를 되돌리거나 멈추기

  • Fling and Back // 1. What? val translation = remember

    { Animatable(Offset(0f, 0f), Offset.VectorConverter) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { translationX = translation.value.x translationY = translation.value.y } .draggable2D(state = draggableState) ... ) // 3. How? val draggableState = rememberDraggable2DState { delta -> coroutineScope.launch { translation.snapTo(translation.value.plus(delta)) } }

  • Fling and Back // 2. When? ComposableKodee( modifier = Modifier

    .draggable2D( state = draggableState, onDragStopped = { velocity -> doFlingMove(velocity) } ) )

  • Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>()

    fun doFlingMove(velocity: Velocity) { // 1. Calculate target offset based on velocity val velocityOffset = Offset(velocity.x / 2f, velocity.y / 2f) val targetOffset = decay.calculateTargetValue( typeConverter = Offset.VectorConverter, initialValue = translation.value, initialVelocity = velocityOffset, ) // 2. If the target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } }

  • Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>()

    fun doFlingMove(velocity: Velocity) { // 1. Calculate target offset based on velocity val velocityOffset = Offset(velocity.x / 2f, velocity.y / 2f) val targetOffset = decay.calculateTargetValue( typeConverter = Offset.VectorConverter, initialValue = translation.value, initialVelocity = velocityOffset, ) // 2. If the target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } Decay Animation Spec

  • Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>()

    fun doFlingMove(velocity: Velocity) { // 1. Calculate target offset based on velocity val velocityOffset = Offset(velocity.x / 2f, velocity.y / 2f) val targetOffset = decay.calculateTargetValue( typeConverter = Offset.VectorConverter, initialValue = translation.value, initialVelocity = velocityOffset, ) // 2. If the target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } Decay Animation Spec DBMDVMBUF5BSHFU7BMVF ৈӝࢲ ݥ୹ԅ

  • Fling and Back // 3. How? // 2. If the

    target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } else { coroutineScope.launch { val adjustedOffset = calculateBoundedOffset(targetOffset, screenSize) // Get farthest Offset translation.animateTo(adjustedOffset) // Animate to farthest point translation.animateTo(Offset(0f, 0f), ..) // Animate back to center } } }

  • Fling and Back // 3. How? // 2. If the

    target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } else { coroutineScope.launch { val adjustedOffset = calculateBoundedOffset(targetOffset, screenSize) // Get farthest Offset translation.animateTo(adjustedOffset) // Animate to farthest point translation.animateTo(Offset(0f, 0f), ..) // Animate back to center } } }

  • Fling and Back // 3. How? // 2. If the

    target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } else { coroutineScope.launch { val adjustedOffset = calculateBoundedOffset(targetOffset, screenSize) translation.animateTo(adjustedOffset) // Animate to farthest point translation.animateTo(Offset(0f, 0f), ..) // Animate back to center } } }

  • Fling and Back // 3. How? // 2. If the

    target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } else { coroutineScope.launch { val adjustedOffset = calculateBoundedOffset(targetOffset, screenSize) translation.animateTo(adjustedOffset) // Animate to farthest point translation.animateTo(Offset(0f, 0f), ..) // Animate back to center } } }

  • Fling and Back // 3. How? // 2. If the

    target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } else { // 3. If not, animate to farthest point within bounds and then animate back to center coroutineScope.launch { val adjustedOffset = calculateBoundedOffset(targetOffset, screenSize) translation.animateTo(adjustedOffset) // Animate to farthest point translation.animateTo(Offset(0f, 0f), ..) // Animate back to center } } }

  • Fling and Back // 3. How? val decay = rememberSplineBasedDecay<Offset>()

    fun doFlingMove(velocity: Velocity) { // 1. Calculate target offset based on velocity val velocityOffset = Offset(velocity.x / 2f, velocity.y / 2f) val targetOffset = decay.calculateTargetValue( typeConverter = Offset.VectorConverter, initialValue = translation.value, initialVelocity = velocityOffset, ) // 2. If the target offset is within bounds, animate to it if (isTargetInBounds(targetOffset, screenSize)) { coroutineScope.launch { translation.animateDecay(velocityOffset, decay) } } else { // 3. If not, animate to farthest point within bounds and then animate back to center coroutineScope.launch { val adjustedOffset = calculateBoundedOffset(targetOffset, screenSize) translation.animateTo(adjustedOffset) // Animate to farthest point translation.animateTo(Offset(0f, 0f), ..) // Animate back to center } } }

  • 우리는 오늘 무엇을 중점적으로 알아볼 것인가? Gestures Sensors Animations Data

  • Sensors!

  • 센서 활용하기 3. Sensor Event 처리하기 1.필요한 Sensor를 선택하기 2.

    Sensor Data 구독하기

  • 센서..??

  • 센서 Android Sensor Manager iOS Sensor Manager Multiplatform Sensor Manager

  • MultiPlatform Sensor Manager interface MultiplatformSensorManager { fun registerListener( sensorType: MultiplatformSensorType,

    onSensorChanged: (MultiplatformSensorEvent) -> Unit, ) fun unregisterAll() } @Composable expect fun rememberSensorManager(): MultiplatformSensorManager

  • MultiPlatform Sensor Manager // Android @Composable actual fun rememberSensorManager(): MultiplatformSensorManager

    { val context = LocalContext.current return remember(context) { AndroidSensorManager(context) } } // iOS @Composable actual fun rememberSensorManager(): MultiplatformSensorManager { return remember { iOSSensorManager() } } @Composable expect fun rememberSensorManager(): MultiplatformSensorManager

  • Multiplatform Sensor Manager // Android class AndroidSensorManager(private val context: Context)

    : MultiplatformSensorManager { private val sensorManager by lazy { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager } override fun registerListener(. . .) { } override fun unregisterAll() { } }

  • Multiplatform Sensor Manager // Android class AndroidSensorManager(private val context: Context)

    : MultiplatformSensorManager { private val sensorManager by lazy { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager } override fun registerListener(...) { sensorManager.getDefaultSensor(sensorType.toSensorType())?.let { sensor -> val sensorEventListener = object : SensorEventListener {...} sensorManager.registerListener(sensorEventListener, sensor) } } override fun unregisterAll() { listeners.forEach { (_, listener) -> sensorManager.unregisterListener(listener) } } }

  • Multiplatform Sensor Manager // Android class AndroidSensorManager(private val context: Context)

    : MultiplatformSensorManager { private val sensorManager by lazy { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager } override fun registerListener(...) { sensorManager.getDefaultSensor(sensorType.toSensorType())?.let { sensor -> val sensorEventListener = object : SensorEventListener {...} sensorManager.registerListener(sensorEventListener, sensor) } } override fun unregisterAll() { listeners.forEach { (_, listener) -> sensorManager.unregisterListener(listener) } } }

  • Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager {

    override fun registerListener(. . .) { } override fun unregisterAll() { }

  • Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager {

    private val motionManager = CMMotionManager() private val activityManager = CMMotionActivityManager() private val pedometerManager = CMPedometer() override fun registerListener(. . .) { } override fun unregisterAll() { }

  • Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager {

    private val motionManager = CMMotionManager() . . . override fun registerListener(. . .) { when (sensorType) { MultiplatformSensorType.ACCELEROMETER -> startAccelerometerUpdates(onSensorChanged) MultiplatformSensorType.STEP_COUNTER -> startPedometerUpdates(onSensorChanged) MultiplatformSensorType.STEP_DETECTOR -> startStepDetection(onSensorChanged) MultiplatformSensorType.LIGHT -> startLightUpdates(onSensorChanged) } } override fun unregisterAll() { }

  • Multiplatform Sensor Manager // iOS class iOSSensorManager : MultiplatformSensorManager {

    private val motionManager = CMMotionManager() . . . override fun registerListener(. . .) { when (sensorType) { MultiplatformSensorType.ACCELEROMETER -> startAccelerometerUpdates(onSensorChanged) . . . } } private fun startAccelerometerUpdates(onSensorChanged: (MultiplatformSensorEvent) -> Unit, ) { if (motionManager.isAccelerometerAvailable()) { motionManager.startAccelerometerUpdatesToQueue(. . .) { data, error -> . . . } } }

  • Multiplatform Sensor Manager class iOSSensorManager : MultiplatformSensorManager { private val

    motionManager = CMMotionManager() // For position related sensors private val activityManager = CMMotionActivityManager() // Detect activity (walking, driving, ...) private val pedometerManager = CMPedometer() // Step counter ... override fun registerListener(...) { when (sensorType) { MultiplatformSensorType.ACCELEROMETER -> startAccelerometerUpdates(onSensorChanged) ... } } override fun unregisterAll() { motionManager.stopDeviceMotionUpdates() motionManager.stopDeviceMotionUpdates() motionManager.stopGyroUpdates() motionManager.stopMagnetometerUpdates() motionManager.stopAccelerometerUpdates() pedometerManager.stopPedometerUpdates() pedometerManager.stopPedometerEventUpdates() activityManager.stopActivityUpdates() ... } }

  • Rotation Shift 1. What? 회전 시키기 2. When? 디바이스의 방향이

    변경 될 경우 3. How? 회전 Sensor의 value 0. Sensor Data이해하기 Orientation

  • Orientation - Android - Roll • Y 축 회전 •

    -180°에서 180° - Azimuth • Z 축 회전 • 0°에서 360° - Pitch • X 축 회전 • -90°에서 90°

  • Orientation ­ iOS Yaw • Z축 회전 • -180º에서 180º

    Pitch • X축 회전 • -180º에서 180º Roll • Y축 회전 • -180º에서 180º

  • • -180º에서 180º - Pitch • X축 회전 • -180º에서 180º - Roll • Y축 회전 • -180º에서 180º - Azimuth • Z 축 회전 • 0°에서 360° - Pitch • X 축 회전 • -90°에서 90° - Roll • Y 축 회전 • -180°에서 180°

  • Orientation ­ Shared Code data class DeviceOrientation( val azimuth: Float,

    // Orientation[0] 0 to 360 val pitch: Float, // Orientation[1] -90 to 90 val roll: Float, // Orientation[2] -180 to 180 ) { val azimuthDegrees = azimuth.toDegrees() val pitchDegrees = pitch.toDegrees() val rollDegrees = roll.toDegrees() }

  • Orientation ­ Shared Code data class DeviceOrientation() // Main interface

    MultiplatformSensorManager { fun registerListener( sensorType: MultiplatformSensorType, onSensorChanged: (MultiplatformSensorEvent) -> Unit ) fun observeOrientationChanges( onOrientationChanged: (DeviceOrientation) -> Unit ) fun unregisterAll() }

  • Rotation Shift // 1. What? val rotationX = remember {

    Animatable(0f) } val rotationY = remember { Animatable(0f) } val rotationZ = remember { Animatable(0f) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { this.rotationX = rotationX.value this.rotationY = rotationY.value this.rotationZ = rotationZ.value } ... ) // 3. How?

  • Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager()

    sensorManager.observeOrientationChanges { orientation -> coroutineScope.launch { rotationX.snapTo() } coroutineScope.launch { rotationY.snapTo() } coroutineScope.launch { rotationZ.snapTo() } }

  • Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager()

    sensorManager.observeOrientationChanges { orientation -> coroutineScope.launch { rotationX.snapTo() } coroutineScope.launch { rotationY.snapTo() } coroutineScope.launch { rotationZ.snapTo() } }

  • Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager()

    sensorManager.observeOrientationChanges { orientation -> coroutineScope.launch { rotationX.snapTo() } coroutineScope.launch { rotationY.snapTo() } coroutineScope.launch { rotationZ.snapTo() } }

  • Rotation Shift // 3. How? val sensorManager: MultiplatformSensorManager = rememberSensorManager()

    sensorManager.observeOrientationChanges { orientation -> coroutineScope.launch { rotationX.snapTo() } coroutineScope.launch { rotationY.snapTo() } coroutineScope.launch { rotationZ.snapTo() } }

  • Rotation Shift // 1. What? val rotationX = remember {

    Animatable(0f) } val rotationY = remember { Animatable(0f) } val rotationZ = remember { Animatable(0f) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { this.rotationX = rotationX.value this.rotationY = rotationY.value this.rotationZ = rotationZ.value } ... ) // 3. How? sensorManager.observeOrientationChanges { orientation -> coroutineScope.launch { rotation.snapTo(orientation.degrees) } ... }

  • Rotation Shift // 1. What? val rotationX = remember {

    Animatable(0f) } val rotationY = remember { Animatable(0f) } val rotationZ = remember { Animatable(0f) } // 2. When? ComposableKodee( modifier = Modifier .graphicsLayer { this.rotationX = rotationX.value this.rotationY = rotationY.value this.rotationZ = rotationZ.value } ... ) // 3. How? sensorManager.observeOrientationChanges { orientation -> coroutineScope.launch { rotation.snapTo(orientation.degrees) } ... }

  • 우리는 오늘 무엇을 중점적으로 알아볼 것인가? Gestures Sensors Animations Data

  • 더 많은 것을 배우고 싶다면? • Play around with the

    code! https://github.com/nicole-terc/sheepit-sensors- multiplatform • Rebecca Franks - Practical magic with animations in Jetpack Compose https://www.youtube.com/watch?v=HNSKJIQtb4c • Compose Animation Documentation https://developer.android.com/develop/ui/compose/animat ion/introduction • Android Sensor Documentation https://developer.android.com/develop/sensors-and- location/sensors/sensors_overview • iOS Sensor Documentation https://developer.apple.com/documentation/coremotion https://developer.apple.com/documentation/sensorkit https://github.com/nicole-terc/sheepit-sensors-multiplatform

  • Thank you

  • Tap it! Shake it!  Fling it! Sheep it!  The Gesture Animations Dance! (2024)

    References

    Top Articles
    Latest Posts
    Article information

    Author: Roderick King

    Last Updated:

    Views: 6283

    Rating: 4 / 5 (71 voted)

    Reviews: 94% of readers found this page helpful

    Author information

    Name: Roderick King

    Birthday: 1997-10-09

    Address: 3782 Madge Knoll, East Dudley, MA 63913

    Phone: +2521695290067

    Job: Customer Sales Coordinator

    Hobby: Gunsmithing, Embroidery, Parkour, Kitesurfing, Rock climbing, Sand art, Beekeeping

    Introduction: My name is Roderick King, I am a cute, splendid, excited, perfect, gentle, funny, vivacious person who loves writing and wants to share my knowledge and understanding with you.