Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Compose Transition Animation

Compose Transition Animation

Yuki Anzai

March 31, 2023
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. $PNQPTF5SBOTJUJPO
    "OJNBUJPO
    ͋Μ͍͟Ώ͖ !ZBO[N

    %SPJE,BJHJDPMMFDU\!5PLZP^

    View Slide

  2. :VLJ"O[BJ
    w (PPHMF%FWFMPQFS&YQFSUGPS"OESPJE
    w UXJUUFS!ZBO[N
    w CMPH:".ͷࡶهாZBO[NCMPHTQPUDPN
    w גࣜձࣾ΢ϑΟΧ

    View Slide

  3. https://play.google.com/store/apps/details?id=jp.co.smartbank.b43
    B/43

    View Slide

  4. var item by remember { mutableStateOf(Item.A) }
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier.fillMaxWidth().height(300.dp)
    ) {
    Spacer(
    modifier = Modifier
    .size(200.dp)
    .background(
    when (item) {
    Item.A -> Color.Red
    Item.B -> Color.Blue
    }
    )
    )
    }
    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
    Item.values().forEach {
    RadioButtonWithText(
    text = it.name,
    selected = item == it,
    onClick = { item = it }
    )
    }
    }
    ᶃJUFN͕มΘΔ
    ᶄSFDPNQPTF͕૸ͬͯมΘΔ

    View Slide

  5. "OJNBUFE$POUFOU
    w ঢ়ଶʢ4UBUFʣ͕มΘͬͨͱ͖ͷ੾Γସ͑ʢFOUFSFYJUʣΛΞχϝʔγϣϯ
    ͢Δ
    @ExperimentalAnimationApi
    @Composable
    fun AnimatedContent(
    targetState: S,

    content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
    ) {

    }

    View Slide

  6. "OJNBUFE$POUFOU
    w ঢ়ଶʢ4UBUFʣ͕มΘͬͨͱ͖ͷ੾Γସ͑ʢFOUFSFYJUʣΛΞχϝʔγϣϯ
    ͢Δ
    var myState by remember { mutableStateOf(Item.A) }
    AnimatedContent(
    label = "myState",
    targetState = myState
    ) {
    // content lambda ͷҾ਺ (it) Λ࢖͏


    // when(myState) ͸μϝ


    when(it) {
    ...
    }
    }

    View Slide

  7. var item by remember { mutableStateOf(Item.A) }
    AnimatedContent(
    label = "item",
    targetState = item,
    ) {
    Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
    .fillMaxWidth()
    .height(300.dp)
    ) {
    Spacer(
    modifier = Modifier
    .size(200.dp)
    .background(
    when (it) {
    Item.A -> Color.Red
    Item.B -> Color.Blue
    }
    )
    )
    }
    }

    "OJNBUFE$POUFOUΛ࢖͏
    DPOUFOUMBNCEBͷҾ਺Λ࢖͏

    View Slide

  8. var item by remember { mutableStateOf(Item.A) }
    AnimatedContent(
    label = "item",
    targetState = item,
    transitionSpec = {
    fadeIn(tween(300, 150)) + slideIntoContainer(
    towards = when (targetState) {
    Item.A -> SlideDirection.End
    Item.B -> SlideDirection.Start
    },
    initialOffset = {
    it / 4
    },
    animationSpec = tween(1000, 150)
    ) with fadeOut(tween(150))
    }
    ) {

    }

    NTͰGBEFPVU
    NT଴ͬͨ͋ͱʢGBEFPVU͕ऴΘͬͨޙʣ
    NTͰGBEFJO
    NT଴ͬͨ͋ͱ
    ʢGBEFPVU͕ऴΘͬͨޙʣ
    NTͰTMJEFJO

    View Slide

  9. Android Studio Gira
    ff
    e

    View Slide

  10. ը໘͕දࣔ͞Εͨͱ͖΋Ξχϝʔγϣϯ͍ͨ͠
    #VUUPOΛλοϓͯ͠ը໘͕දࣔ͞Εͨ
    ͱ͖ʢDPNQPTF࣌ʣ͸Ξχϝʔγϣϯ
    ͳ͠Ͱ੺࢛͕֯දࣔ͞Ε͍ͯΔ

    View Slide

  11. "OJNBUFE$POUFOUͷத਎ΛΈΔͱʜ
    @ExperimentalAnimationApi
    @Composable
    fun AnimatedContent(
    targetState: S,

    ) {
    val transition = updateTransition(targetState = targetState, label = label)
    transition.AnimatedContent(
    modifier,
    transitionSpec,
    contentAlignment,
    content = content
    )
    }
    VQEBUF5SBOTJUJPO
    Λ࢖͍ͬͯΔ
    5SBOTJUJPOͷ"OJNBUFE$POUFOUΛ࢖͍ͬͯΔ

    View Slide

  12. VQEBUF5SBOTJUJPO

    w ঢ়ଶʢ஋ʣͷมߋʹෳ਺ͷΞχϝʔγϣϯΛඥ͚͍ͮͨͱ͖ʹ࢖͏

    View Slide

  13. VQEBUF5SBOTJUJPO

    w ঢ়ଶʢ஋ʣͷมߋʹෳ਺ͷΞχϝʔγϣϯΛඥ͚͍ͮͨͱ͖ʹ࢖͏
    var item by remember { mutableStateOf(Item.A) }
    val itemTransition = updateTransition(targetState = item, label = "item")
    ঢ়ଶ FOVNDMBTT*UFN\" #^

    5SBOTJUJPOΠϯελϯε͕ฦΔ UBSHFU4UBUFʹ4UBUFͷ஋Λ౉͢

    View Slide

  14. VQEBUF5SBOTJUJPO

    w ঢ়ଶʢ஋ʣͷมߋʹෳ਺ͷΞχϝʔγϣϯΛඥ͚͍ͮͨͱ͖ʹ࢖͏
    var item by remember { mutableStateOf(Item.A) }
    val itemTransition = updateTransition(targetState = item, label = "item")
    val size by itemTransition.animateDp(label = "size") {
    when (it) {
    Item.A -> 100.dp
    Item.B -> 200.dp
    }
    }
    val color by itemTransition.animateColor(label = "color") {
    when (it) {
    Item.A -> Color.Red
    Item.B -> Color.Blue
    }
    }
    5SBOTJUJPOBOJNBUF
    ͰΞχϝʔγϣϯͷ4UBUF5Λऔಘ͢Δ

    View Slide

  15. var item by remember { mutableStateOf(Item.A) }
    val itemTransition = updateTransition(targetState = item, label = "item")
    val size by itemTransition.animateDp(label = "size") {
    when (it) {
    Item.A -> 100.dp
    Item.B -> 200.dp
    }
    }
    val color by itemTransition.animateColor(label = "color") {
    when (it) {
    Item.A -> Color.Red
    Item.B -> Color.Blue
    }
    }
    Spacer(
    modifier = Modifier
    .size(size)
    .background(color)
    )
    JUFN͕มΘΔͱTJ[Fͱ
    CBDLHSPVOE྆ํΞχϝʔγϣϯ͢Δ

    View Slide

  16. var item by remember { mutableStateOf(Item.A) }
    val itemTransition = updateTransition(targetState = item, label = "item")
    val size by itemTransition.animateDp(label = "size") {
    when (it) {
    Item.A -> 100.dp
    Item.B -> 200.dp
    }
    }
    val color by itemTransition.animateColor(label = "color") {
    when (it) {
    Item.A -> Color.Red
    Item.B -> Color.Blue
    }
    }
    Spacer(
    modifier = Modifier
    .size(size)
    .background(color)
    )

    View Slide

  17. 5SBOTJUJPOͷ"OJNBUJPO
    w BOJNBUF99
    w "OJNBUFE$POUFOU
    w "OJNBUFE7JTJCJMJUZ
    w $SPTTGBEF

    View Slide

  18. ը໘͕දࣔ͞Εͨͱ͖΋Ξχϝʔγϣϯ͍ͨ͠
    #VUUPOΛλοϓͯ͠ը໘͕දࣔ͞Εͨ
    ͱ͖ʢDPNQPTF࣌ʣ͸Ξχϝʔγϣϯ
    ͳ͠Ͱ੺࢛͕֯දࣔ͞Ε͍ͯΔ

    View Slide

  19. VQEBUF5SBOTJUJPO
    ͷத਎ΛΈΔͱʜ
    @Composable
    fun updateTransition(
    targetState: T,
    label: String? = null
    ): Transition {
    val transition = remember { Transition(targetState, label = label) }
    transition.animateTo(targetState)
    DisposableEffect(transition) {

    }
    return transition
    }
    5SBOTJUJPOΛੜ੒

    View Slide

  20. .VUBCMF5SBOTJUJPO4UBUFΛอ࣋
    @Composable
    fun updateTransition(
    targetState: T,
    label: String? = null
    ): Transition {
    val transition = remember { Transition(targetState, label = label) }
    transition.animateTo(targetState)
    DisposableEffect(transition) {

    }
    return transition
    }
    @Stable
    class Transition @PublishedApi internal constructor(
    private val transitionState: MutableTransitionState,
    val label: String? = null
    ) {
    internal constructor(
    initialState: S,
    label: String?
    ) : this(MutableTransitionState(initialState), label)

    }
    5SBOTJUJPOΛੜ੒

    View Slide

  21. @Stable
    class Transition @PublishedApi internal constructor(
    private val transitionState: MutableTransitionState,
    val label: String? = null
    ) {
    internal constructor(
    initialState: S,
    label: String?
    ) : this(MutableTransitionState(initialState), label)

    }
    JOJUJBM4UBUFͰ.VUBCMF5SBOTJUJPO4UBUFΛੜ੒
    @Composable
    fun updateTransition(
    targetState: T,
    label: String? = null
    ): Transition {
    val transition = remember { Transition(targetState, label = label) }
    transition.animateTo(targetState)
    DisposableEffect(transition) {

    }
    return transition
    }

    View Slide

  22. @Stable
    class Transition @PublishedApi internal constructor(
    private val transitionState: MutableTransitionState,
    val label: String? = null
    ) {
    internal constructor(
    initialState: S,
    label: String?
    ) : this(MutableTransitionState(initialState), label)

    }
    @Composable
    fun updateTransition(
    targetState: T,
    label: String? = null
    ): Transition {
    val transition = remember { Transition(targetState, label = label) }
    transition.animateTo(targetState)
    DisposableEffect(transition) {

    }
    return transition
    }
    ͬͪ͜ͷίϯετϥΫλΛ࢖͏VQEBUF5SBOTJUJPO
    ΋͋Δʁ

    View Slide

  23. @Stable
    class Transition @PublishedApi internal constructor(
    private val transitionState: MutableTransitionState,
    val label: String? = null
    ) {
    internal constructor(
    initialState: S,
    label: String?
    ) : this(MutableTransitionState(initialState), label)

    }
    @Composable
    fun updateTransition(
    targetState: T,
    label: String? = null
    ): Transition {
    val transition = remember { Transition(targetState, label = label) }
    transition.animateTo(targetState)
    DisposableEffect(transition) {

    }
    return transition
    }
    ͬͪ͜ͷίϯετϥΫλΛ࢖͏VQEBUF5SBOTJUJPO
    ΋͋Δʁ
    ͋Γ·͢

    View Slide

  24. @Stable
    class Transition @PublishedApi internal constructor(
    private val transitionState: MutableTransitionState,
    val label: String? = null
    ) {
    internal constructor(
    initialState: S,
    label: String?
    ) : this(MutableTransitionState(initialState), label)

    }
    @Composable
    fun updateTransition(
    transitionState: MutableTransitionState,
    label: String? = null
    ): Transition {
    val transition = remember(transitionState) {
    Transition(transitionState = transitionState, label)
    }
    transition.animateTo(transitionState.targetState)
    DisposableEffect(transition) {

    }
    return transition
    }
    .VUBCMF5SBOTJUJPO4UBUFΛͱΔVQEBUF5SBOTBDUJPO

    View Slide

  25. .VUBCMF5SBOTJUJPO4UBUF͕มΘͬͨΒ5SBOTJUJPO͸
    ੜ੒͠௚͞ΕΔ
    .VUBCMF5SBOTJUJPO4UBUFͷUBSHFU4UBUFΛ
    BOJNBUF5P
    ʹ౉͢
    @Composable
    fun updateTransition(
    transitionState: MutableTransitionState,
    label: String? = null
    ): Transition {
    val transition = remember(transitionState) {
    Transition(transitionState = transitionState, label)
    }
    transition.animateTo(transitionState.targetState)
    DisposableEffect(transition) {

    }
    return transition
    }

    View Slide

  26. .VUBCMF5SBOTJUJPO4UBUF
    class MutableTransitionState(initialState: S) {
    var currentState: S by mutableStateOf(initialState)
    internal set
    var targetState: S by mutableStateOf(initialState)
    val isIdle: Boolean
    get() = (currentState == targetState) && !isRunning
    internal var isRunning: Boolean by mutableStateOf(false)
    }
    5SBOTJUJPO͔Βߋ৽͞ΕΔ
    ͜ΕΛมߋ͢Δͱ5SBOTJUJPOUBSHFU4UBUF͕
    ߋ৽͞ΕͯΞχϝʔγϣϯ͕։࢝͞ΕΔ
    ʢΞχϝʔγϣϯऴྃ࣌ͳͲʣ5SBOTJUJPO͕৽͍͠
    ঢ়ଶʹͳͬͨͱ͖ʹ5SBOTJUJPO͔Βߋ৽͞ΕΔ

    View Slide

  27. .VUBCMF5SBOTJUJPO4UBUF
    Λ࢖͏

    View Slide

  28. var item by remember { mutableStateOf(Item.A) }
    val transitionState = remember { MutableTransitionState(item) }
    transitionState.targetState = item
    val itemTransition = updateTransition(transitionState = transitionState, label = "item")
    val itemTransition = updateTransition(targetState = item, label = "item")
    val size by itemTransition.animateDp(label = "size") {

    }
    val color by itemTransition.animateColor(label = "color") {

    }
    Spacer(
    modifier = Modifier
    .size(size)
    .background(color)
    )

    View Slide

  29. var item by remember { mutableStateOf(Item.A) }
    val transitionState = remember { MutableTransitionState(item) }
    transitionState.targetState = item
    val itemTransition = updateTransition(transitionState = transitionState, label = "item")
    val itemTransition = updateTransition(targetState = item, label = "item")
    val size by itemTransition.animateDp(label = "size") {

    }
    val color by itemTransition.animateColor(label = "color") {

    }
    Spacer(
    modifier = Modifier
    .size(size)
    .background(color)
    )
    .VUBCMF5SBOTJUJPO4UBUF͸SFNFNCFS͢Δ

    View Slide

  30. var item by remember { mutableStateOf(Item.A) }
    val transitionState = remember { MutableTransitionState(item) }
    transitionState.targetState = item
    val itemTransition = updateTransition(transitionState = transitionState, label = "item")
    val itemTransition = updateTransition(targetState = item, label = "item")
    val size by itemTransition.animateDp(label = "size") {

    }
    val color by itemTransition.animateColor(label = "color") {

    }
    Spacer(
    modifier = Modifier
    .size(size)
    .background(color)
    )
    .VUBCMF5SBOTJUJPO4UBUFͷUBSHFU4UBUFΛߋ৽͢Δ

    View Slide

  31. var item by remember { mutableStateOf(Item.A) }
    val transitionState = remember { MutableTransitionState(item) }
    transitionState.targetState = item
    val itemTransition = updateTransition(transitionState = transitionState, label = "item")
    val itemTransition = updateTransition(targetState = item, label = "item")
    val size by itemTransition.animateDp(label = "size") {

    }
    val color by itemTransition.animateColor(label = "color") {

    }
    Spacer(
    modifier = Modifier
    .size(size)
    .background(color)
    )
    VQEBUF5SBOTJUJPO
    ʹ.VUBCMF5SBOTJUJPO4UBUFΛ౉͢

    View Slide

  32. .VUBCMF5SBOTJUJPO4UBUFͳΒͰ͖Δ͜ͱ
    w ࠷ॳͷUBSHFU4UBUFͱ͸ผͷॳظঢ়ଶΛͱΕΔ
    w DPNQPTJUJPOʹೖͬͨͱ͖ʹΞχϝʔγϣϯͤ͞Δ͜ͱ͕Ͱ͖Δ
    var visible by remember { mutableStateOf(true) }
    val transitionState = remember { MutableTransitionState(false) }
    transitionState.targetState = visible
    val itemTransition = updateTransition(transitionState = transitionState, label = "item")
    DPNQPTJUJPO࣌ʹ
    ॳظঢ়ଶ͸GBMTFɺ
    UBSHFU4UBUF͸USVFͳͷͰ
    Ξχϝʔγϣϯ͕૸Δ

    View Slide

  33. .VUBCMF5SBOTJUJPO4UBUFͳΒͰ͖Δ͜ͱ
    w ࠷ॳͷUBSHFU4UBUFͱ͸ผͷॳظঢ়ଶΛͱΕΔ
    w DPNQPTJUJPOʹೖͬͨͱ͖ʹΞχϝʔγϣϯͤ͞Δ͜ͱ͕Ͱ͖Δ
    w .VUBCMF5SBOTJUJPO4UBUFΛ࠶ੜ੒͢Δ͜ͱͰɺΞχϝʔγϣϯͷϦελʔτ
    ΛτϦΨʔͰ͖Δ
    val transitionState = remember(trigger) { MutableTransitionState(false) }
    val itemTransition = updateTransition(transitionState = transitionState, label = "item")
    USJHHFS͕มΘΔͱ.VUBCMF5SBOTJUJPO4UBUF͕࡞Γ௚͞ΕΔͷͰ
    5SBOTJUJPO΋࡞Γ௚͞ΕΔ

    View Slide

  34. .VUBCMF5SBOTJUJPO4UBUF
    Λ࢖ͬͯDPNQPTF࣌ʹ΋
    Ξχϝʔγϣϯͤ͞Δ

    View Slide

  35. var item by remember { mutableStateOf(Item.A) }
    val transitionState = remember { MutableTransitionState(item) }
    transitionState.targetState = item
    val itemTransition = updateTransition(
    transitionState = transitionState,
    label = "item"
    )
    itemTransition.AnimatedContent(
    transitionSpec = {

    }
    ) {

    }
    VQEBUF5SBOTJUJPO
    Λ࢖͏
    5SBOTJUJPOͷ"OJNBUFE$POUFOUΛ࢖͏

    View Slide

  36. var item by remember { mutableStateOf(Item.A) }
    val transitionState = remember { MutableTransitionState(Item.B) }
    transitionState.targetState = item
    val itemTransition = updateTransition(
    transitionState = transitionState,
    label = "item"
    )

    μϝͳ৔߹
    DPNQPTJUJPO࣌ͷΞχϝʔγϣϯͰ
    #ͷͱ͖ͷ੨͍࢛͕֯Ұॠදࣔ͞Εͯ͠·͏

    View Slide

  37. var item by remember { mutableStateOf(Item.A) }
    val transitionState = remember { MutableTransitionState(null) }
    transitionState.targetState = item
    val itemTransition = updateTransition(
    transitionState = transitionState,
    label = "item"
    )
    itemTransition.AnimatedContent(
    transitionSpec = {

    }
    ) {
    Box(…) {
    if (it != null) {
    Spacer(…)
    }
    }
    }
    ॳظঢ়ଶΛOVMMʹ͢Δ
    DPNQPTJUJPO࣌ʹ͸
    OVMMˠ*UFN"
    ͷΞχϝʔγϣϯ͕૸Δ

    View Slide

  38. "OJNBUFE7JTJCJMJUZ4DPQFͷ
    5SBOTJUJPO

    View Slide

  39. @ExperimentalAnimationApi
    @Composable
    fun Transition.AnimatedContent(
    modifier: Modifier = Modifier,
    transitionSpec: AnimatedContentScope.() -> ContentTransform = {
    fadeIn(animationSpec = tween(220, delayMillis = 90)) +
    scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)) with
    fadeOut(animationSpec = tween(90))
    },
    contentAlignment: Alignment = Alignment.TopStart,
    contentKey: (targetState: S) -> Any? = { it },
    content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
    ) {

    View Slide

  40. interface AnimatedVisibilityScope {
    /**
    * [transition] allows custom enter/exit animations to be specified. It will run simultaneously
    * with the built-in enter/exit transitions specified in [AnimatedVisibility].
    */
    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
    @get:ExperimentalAnimationApi
    @ExperimentalAnimationApi
    val transition: Transition

    }

    View Slide

  41. itemTransition.AnimatedContent(

    ) {
    val rotationY by transition.animateFloat(
    label = "rotationY",
    transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) }
    ) { state ->
    when (state) {
    EnterExitState.PreEnter -> when (it) {
    Item.A -> 45f
    Item.B,
    null -> -45f
    }
    EnterExitState.Visible -> 0f
    EnterExitState.PostExit -> 0f
    }
    }
    Box(…) {
    if (it != null) {
    Spacer(
    modifier = Modifier
    .size(200.dp)
    .graphicsLayer {
    this.rotationY = rotationY
    this.cameraDistance = 40f
    }

    View Slide

  42. itemTransition.AnimatedContent(

    ) {
    val rotationY by transition.animateFloat(
    label = "rotationY",
    transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) }
    ) { state ->
    when (state) {
    EnterExitState.PreEnter -> when (it) {
    Item.A -> 45f
    Item.B,
    null -> -45f
    }
    EnterExitState.Visible -> 0f
    EnterExitState.PostExit -> 0f
    }
    }
    Box(…) {
    if (it != null) {
    Spacer(
    modifier = Modifier
    .size(200.dp)
    .graphicsLayer {
    this.rotationY = rotationY
    this.cameraDistance = 40f
    }

    "OJNBUFE7JTJCJMJUZ4DPQFͷUSBOTJUJPOͰ
    BOJNBUF99Λ࢖͏

    View Slide

  43. itemTransition.AnimatedContent(

    ) {
    val rotationY by transition.animateFloat(
    label = "rotationY",
    transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) }
    ) { state ->
    when (state) {
    EnterExitState.PreEnter -> when (it) {
    Item.A -> 45f
    Item.B,
    null -> -45f
    }
    EnterExitState.Visible -> 0f
    EnterExitState.PostExit -> 0f
    }
    }
    Box(…) {
    if (it != null) {
    Spacer(
    modifier = Modifier
    .size(200.dp)
    .graphicsLayer {
    this.rotationY = rotationY
    this.cameraDistance = 40f
    }

    FOUFS࣌ͷॳظ஋
    FOUFS࣌ͷUBSHFU஋FYJU࣌ͷॳظ஋
    FYJU࣌ͷUBSHFU஋

    View Slide

  44. itemTransition.AnimatedContent(

    ) {
    val rotationY by transition.animateFloat(
    label = "rotationY",
    transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) }
    ) { state ->
    when (state) {
    EnterExitState.PreEnter -> when (it) {
    Item.A -> 45f
    Item.B,
    null -> -45f
    }
    EnterExitState.Visible -> 0f
    EnterExitState.PostExit -> 0f
    }
    }
    Box(…) {
    if (it != null) {
    Spacer(
    modifier = Modifier
    .size(200.dp)
    .graphicsLayer {
    this.rotationY = rotationY
    this.cameraDistance = 40f
    }

    SPUBUJPO:ΛΞχϝʔγϣϯ

    View Slide

  45. ·ͱΊ
    w "OJNBUFE$POUFOU͸಺෦ͰVQEBUF5SBOTJUJPO
    Λ࢖͍ͬͯΔ
    w .VUBCMF5SBOTJUJPO4UBUFΛ࢖͏ͱ$PNQPTJUJPO࣌ʹ΋Ξχϝʔγϣϯͤ͞
    Δ͜ͱ͕Ͱ͖Δ
    w "OJNBUFE7JTJCJMJUZ4DPQFͷ5SBOTJUJPOΛ࢖͏ͱFOUFSFYJUʹಉظͨ͠
    ݸผͷΞχϝʔγϣϯΛ௥ՃͰ͖Δ

    View Slide