Slide 1

Slide 1 text

Shared Element Transition Shibuya.apk #48 2024.8.9

Slide 2

Slide 2 text

©MIXI 2 Android / iOS / Flutter Android 2014 @oidy & https://speakerdeck.com/mixi_engineers/2024-new-grad-training-android

Slide 3

Slide 3 text

Transition

Slide 4

Slide 4 text

©MIXI Transition 4 e.g. l / l /

Slide 5

Slide 5 text

Shared Element Transition

Slide 6

Slide 6 text

©MIXI Shared Element Transition 6

Slide 7

Slide 7 text

©MIXI Shared Element Transition 7

Slide 8

Slide 8 text

View

Slide 9

Slide 9 text

©MIXI View ActivityOptions.makeSceneTransitionAnimation() 9 Activity FragmentTransaction.addSharedElement() Fragment Transaction FragmentNavigatorExtras() Fragment Navigation Shared Element Transition ( )

Slide 10

Slide 10 text

Compose Animation

Slide 11

Slide 11 text

©MIXI Compose Animation 2022 6 : 1.3.0-alpha01 LookaheadLayout 2023 3 : 1.5.0-alpha01 LookaheadLayout LookaheadScope 11 Transition

Slide 12

Slide 12 text

©MIXI Compose Animation 2022 6 : 1.3.0-alpha01 LookaheadLayout 2023 3 : 1.5.0-alpha01 LookaheadLayout LookaheadScope 2024 4 : 1.7.0-alpha07 Shared Element Transition API 12 LookaheadScope

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

©MIXI 14

Slide 15

Slide 15 text

©MIXI ver. 15 Column { var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, ) if (!checked) { Row { Box(...) // ● Box(...) // ■ } } else { Column { Box(...) // ● Box(...) // ■ } } }

Slide 16

Slide 16 text

©MIXI 16 Column { var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, ) if (!checked) { Row { Box(...) // key = "circle" Box(...) // key = "rectangle" } } else { Column { Box(...) // key = "circle" Box(...) // key = "rectangle" } } } Shared Element key

Slide 17

Slide 17 text

©MIXI key 17 Box( Modifier .sharedElement( state = rememberSharedContentState(key = "circle"), animatedVisibilityScope = animatedVisibilityScope, ) .clip(CircleShape) )

Slide 18

Slide 18 text

©MIXI key 18 Box( Modifier .sharedElement( state = rememberSharedContentState(key = "circle"), animatedVisibilityScope = animatedVisibilityScope, ) .clip(CircleShape) ) Unresolved reference Unresolved reference

Slide 19

Slide 19 text

©MIXI 2 Scope 19 SharedTransitionLayout { AnimatedContent(targetState = checked) { targetState -> if (!targetState) { Box( Modifier .sharedElement( state = rememberSharedContentState(key = "circle"), animatedVisibilityScope = this@AnimatedContent, ) ... ) } else { ... } } }

Slide 20

Slide 20 text

©MIXI 2 Scope 20 SharedTransitionLayout { AnimatedContent(targetState = checked) { targetState -> if (!targetState) { Box( Modifier .sharedElement( state = rememberSharedContentState(key = "circle"), animatedVisibilityScope = this@AnimatedContent, ) ... ) } else { ... } } } SharedTransitionScope SharedTransitionScope

Slide 21

Slide 21 text

©MIXI 2 Scope 21 SharedTransitionLayout { AnimatedContent(targetState = checked) { targetState -> if (!targetState) { Box( Modifier .sharedElement( state = rememberSharedContentState(key = "circle"), animatedVisibilityScope = this@AnimatedContent, ) ... ) } else { ... } } } AnimatedVisibilityScope

Slide 22

Slide 22 text

©MIXI 22 if (!targetState) { Row { Box(Modifier.sharedElement(...)...) Box(Modifier.sharedElement(...)...) } } else { Column { Box(Modifier.sharedElement(...)...) Box(Modifier.sharedElement(...)...) } }

Slide 23

Slide 23 text

©MIXI Composable 23 ... if (!targetState) { StartContent() } else { EndContent() } } @Composable StartContent() { ... } @Composable EndContent() { ... }

Slide 24

Slide 24 text

©MIXI Composable 24 ... if (!targetState) { StartContent() } else { EndContent() } } @Composable StartContent() { ... } @Composable EndContent() { ... } 2 Scope l l CompositionLocal l Context Receiver

Slide 25

Slide 25 text

©MIXI Scope 25 if (!targetState) { StartContent( sharedTransitionScope = this@SharedTransitionLayout, animatedVisibilityScope = this@AnimatedContent, ) } else { EndContent( sharedTransitionScope = this@SharedTransitionLayout, animatedVisibilityScope = this@AnimatedContent, ) } @Composable fun StartContent( sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope, )

Slide 26

Slide 26 text

©MIXI CompositionLocal Scope 26 SharedTransitionLayout { CompositionLocalProvider(LocalSharedTransitionScope provides this) { AnimatedContent(targetState = checked) { targetState -> CompositionLocalProvider(LocalAnimatedVisibilityScope provides this) { if (!targetState) { StartContent() } else { EndContent() } } } } } @Composable fun StartContent() { val sharedTransitionScope = LocalSharedTransitionScope.current!! val animatedVisibilityScope = LocalAnimatedVisibilityScope.current!! CompositionLocal Scope Scope

Slide 27

Slide 27 text

©MIXI CompositionLocal Scope 27 SharedTransitionLayout { CompositionLocalProvider(LocalSharedTransitionScope provides this) { AnimatedContent(targetState = checked) { targetState -> CompositionLocalProvider(LocalAnimatedVisibilityScope provides this) { if (!targetState) { StartContent() } else { EndContent() } } } } } @Composable fun StartContent() { val sharedTransitionScope = LocalSharedTransitionScope.current!! val animatedVisibilityScope = LocalAnimatedVisibilityScope.current!!

Slide 28

Slide 28 text

©MIXI CompositionLocal Scope 28 @Composable fun Modifier.trySharedElement(key: String): Modifier { val sharedTransitionScope = LocalSharedTransitionScope.current val animatedVisibilityScope = LocalAnimatedVisibilityScope.current if (sharedTransitionScope == null || animatedVisibilityScope == null) { // return this } return with(sharedTransitionScope) { sharedElement( state = rememberSharedContentState(key), animatedVisibilityScope = animatedVisibilityScope ) } }

Slide 29

Slide 29 text

©MIXI CompositionLocal Scope 29 @Composable fun Modifier.trySharedElement(key: String): Modifier { val sharedTransitionScope = LocalSharedTransitionScope.current val animatedVisibilityScope = LocalAnimatedVisibilityScope.current if (sharedTransitionScope == null || animatedVisibilityScope == null) { // return this } return with(sharedTransitionScope) { sharedElement( state = rememberSharedContentState(key), animatedVisibilityScope = animatedVisibilityScope ) } } : Deep Link Shared Element

Slide 30

Slide 30 text

©MIXI Context Receiver Scope 30 context(SharedTransitionScope, AnimatedVisibilityScope) @Composable fun StartContent() { ... Modifier .sharedBounds( animatedVisibilityScope = this@AnimatedVisibilityScope, ... } SharedTransitionScope AnimatedVisibilityScope StartContent()

Slide 31

Slide 31 text

©MIXI l Compose Animation 1.7.0-alpha07 l SharedTransitionLayout AnimatedContent UI l SharedTransitionScope AnimatedVisibilityScope Modifier.sharedElement() l Shared Element key 31 Compose Shared Element Transition

Slide 32

Slide 32 text

Navigation

Slide 33

Slide 33 text

©MIXI Navigation Compose 33 val navController = rememberNavController() NavHost( navController = navController, startDestination = Start, ) { composable { StartContent(onClick = { navController.navigate(End) }) } composable { EndContent() } }

Slide 34

Slide 34 text

©MIXI Navigation Compose 34 val navController = rememberNavController() NavHost( navController = navController, startDestination = Start, ) { composable { StartContent(onClick = { navController.navigate(End) }) } composable { EndContent() } } Navigation 2.8.0 type safe route @Serializable object Start @Serializable object End

Slide 35

Slide 35 text

©MIXI Navigation Compose 35 val navController = rememberNavController() NavHost( navController = navController, startDestination = Start, ) { composable { StartContent(onClick = { navController.navigate(End) }) } composable { EndContent() } } AnimatedVisibilityScope

Slide 36

Slide 36 text

©MIXI SharedTransitionScope OK 36 val navController = rememberNavController() SharedTransitionLayout { NavHost( navController = navController, startDestination = Start, ) { composable { StartContent(onClick = { navController.navigate(End) }) } composable { EndContent() } } }

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

©MIXI API e.g. 38 Modifier.sharedElement() Shared Element content content Modifier.sharedBounds()

Slide 39

Slide 39 text

©MIXI 39 Modifier .sharedElement( clipInOverlayDuringTransition = OverlayClip( clipShape = ... )

Slide 40

Slide 40 text

©MIXI 40 Modifier .sharedElement( clipInOverlayDuringTransition = OverlayClip( clipShape = ... )

Slide 41

Slide 41 text

©MIXI 41 Modifier .sharedElement( clipInOverlayDuringTransition = OverlayClip( clipShape = ... )

Slide 42

Slide 42 text

©MIXI UI 43 Top Bar FAB

Slide 43

Slide 43 text

©MIXI UI 44 TopAppBar( modifier = Modifier .renderInSharedTransitionScopeOverlay( zIndexInOverlay = 1f ) .animateEnterExit( enter = ..., exit = ..., ), ... )

Slide 44

Slide 44 text

©MIXI TopAppBar( modifier = Modifier .renderInSharedTransitionScopeOverlay( zIndexInOverlay = 1f ) .animateEnterExit( enter = ..., exit = ..., ), ... ) UI 45 TopAppBar ( )

Slide 45

Slide 45 text

©MIXI Shared Element Transition l Compose Animation 1.7.0 46 : l Scope l Deep Link beta l Navigation Compose l or

Slide 46

Slide 46 text

No content