Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

今こそ始める Shared Element Transition

oidy
August 09, 2024

今こそ始める Shared Element Transition

oidy

August 09, 2024
Tweet

More Decks by oidy

Other Decks in Programming

Transcript

  1. ©MIXI 2 Android / iOS / Flutter Android 2014 @oidy

    & https://speakerdeck.com/mixi_engineers/2024-new-grad-training-android
  2. ©MIXI Compose Animation 2022 6 : 1.3.0-alpha01 LookaheadLayout 2023 3

    : 1.5.0-alpha01 LookaheadLayout LookaheadScope 11 Transition
  3. ©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
  4. ©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(...) // ▪ } } }
  5. ©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
  6. ©MIXI key 17 Box( Modifier .sharedElement( state = rememberSharedContentState(key =

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

    "circle"), animatedVisibilityScope = animatedVisibilityScope, ) .clip(CircleShape) ) Unresolved reference Unresolved reference
  8. ©MIXI 2 Scope 19 SharedTransitionLayout { AnimatedContent(targetState = checked) {

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

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

    targetState -> if (!targetState) { Box( Modifier .sharedElement( state = rememberSharedContentState(key = "circle"), animatedVisibilityScope = this@AnimatedContent, ) ... ) } else { ... } } } AnimatedVisibilityScope
  11. ©MIXI 22 if (!targetState) { Row { Box(Modifier.sharedElement(...)...) Box(Modifier.sharedElement(...)...) }

    } else { Column { Box(Modifier.sharedElement(...)...) Box(Modifier.sharedElement(...)...) } }
  12. ©MIXI Composable 23 ... if (!targetState) { StartContent() } else

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

    { EndContent() } } @Composable StartContent() { ... } @Composable EndContent() { ... } 2 Scope l l CompositionLocal l Context Receiver
  14. ©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, )
  15. ©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
  16. ©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!!
  17. ©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 ) } }
  18. ©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
  19. ©MIXI Context Receiver Scope 30 context(SharedTransitionScope, AnimatedVisibilityScope) @Composable fun StartContent()

    { ... Modifier .sharedBounds( animatedVisibilityScope = this@AnimatedVisibilityScope, ... } SharedTransitionScope AnimatedVisibilityScope StartContent()
  20. ©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
  21. ©MIXI Navigation Compose 33 val navController = rememberNavController() NavHost( navController

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

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

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

    NavHost( navController = navController, startDestination = Start, ) { composable<Start> { StartContent(onClick = { navController.navigate(End) }) } composable<End> { EndContent() } } }
  25. ©MIXI TopAppBar( modifier = Modifier .renderInSharedTransitionScopeOverlay( zIndexInOverlay = 1f )

    .animateEnterExit( enter = ..., exit = ..., ), ... ) UI 45 TopAppBar ( )
  26. ©MIXI Shared Element Transition l Compose Animation 1.7.0 46 :

    l Scope l Deep Link beta l Navigation Compose l or