Apple Music » Transitioning from the “Now Playing” screen to the mini-player

Transitioning from the “Now Playing” screen to the mini-player

Everything looks okay. So we can hide the mini-player, for now.

Mini_Player.opacity = 0

We use opacity because we’ll want to animate it.

But we also don’t want it to be tapped by accident when the user swipes down the “Now Playing” screen, so we also switch off its visible.

Mini_Player.visible = no

Later we’ll need to know when the mini-player is in use (you’ll see why). We create a variable for that, miniPlayerActive, which at this point will still be ‘no’.

miniPlayerActive = no

Listening to the scroll movement

To know when the user has dragged down the “Now Playing” screen we’ll listen to its onScrollEnd event. This event gets triggered the moment the user stops scrolling.

scroll_now_playing.onScrollEnd ->

Now we need to check if the user has scrolled down far enough. If they haven’t, we’ll just let the ScrollComponent bounce back.

In the original app, the user has to drag the “Now Playing” screen 121 points or more from the top of the screen to have it transition to the mini-player.

The “Now Playing” screen is already placed 33 points from the top, so we’ll start our animation when the user has scrolled down 88 points.

But since we’re scrolling down, actually beyond the edge of the content, we’re checking for a negative value of scrollY, the scroll distance.

(You can put a print maxi_player.scrollY in the event handler to test this.)

scroll_now_playing.onScrollEnd ->
    if scroll_now_playing.scrollY < -88

Freezing the scroll position

Now, when the user has scrolled this far down we can start the transition, but we face a problem: the “Now Playing” screen will be in a “scrolled down” state.

We’ll solve this by quickly resetting the ScrollComponent to its initial state, like this:

Before starting the animations, we’ll move the ScrollComponent down while moving its content up. We do it instantly, without animation.

Okay, step by step:

# The ScrollComponent jumps to its content’s position
scroll_now_playing.y = scroll_now_playing.content.y + 33

(Note that we’re not using scrollY here, but the content layer’s y position, which increases when scrolling down.)

So no matter how far the user has scrolled down, our ScrollComponent will always end up in the correct place.

Now we move the content back to the top:

# … and we reset the content to its initial position
scroll_now_playing.scrollToPoint
    y: 0
    no

The scrollToPoint() function does what it says: it lets you scroll to a certain point. By setting its ‘animate’ argument to no, this will happen instantly, without animation.

All together it should look like this:

scroll_now_playing.onScrollEnd ->
    if scroll_now_playing.scrollY < -88
        scroll_now_playing.y = scroll_now_playing.content.y + 33
        scroll_now_playing.scrollToPoint
            y: 0
            no

Please try it. You can scroll up and down all you want, but once you pull down far enough, it will stay at the spot where you released it.

Scrolling stops once you scroll down more than 88 points

Now get ready. We’ll have nine animations, with different timings, all run at the same time.

First set of animations

The first set of six animations starts immediately, and the duration for all of them will be a third of a second.

# - First set of animations: a third of a second - #
firstSetDuration = 0.3

In 0.3 seconds we’ll:

  • Show the mini-player (opacity)
  • Hide the transparent gray overlay behind it (opacity)
  • Reset the “Library” screen in the back (scaleX, y, borderRadius) …
  • … and do the same for the “For You” screen
  • Make the Status Bar black again (invert)
  • And move the Tab Bar up (y)

Here we go.

Showing the mini-player: We make it visible again and animate its opacity back to 1.

Mini_Player.visible = yes
Mini_Player.animate
    opacity: 1
    options:
        time: firstSetDuration

Hide the transparent gray overlay by animating its opacity to zero.

overlay.animate
    opacity: 0
    options:
        time: firstSetDuration

Next, we move scroll_library and scroll_for_you back to the top of the screen, reset their horizontal scale, and remove their border radius.

scroll_library.animate
    scaleX: 1
    y: 0
    borderRadius: 0
    options:
        time: firstSetDuration
scroll_for_you.animate
    scaleX: 1
    y: 0
    borderRadius: 0
    options:
        time: firstSetDuration

(Initially, we only changed scroll_library, but after use of the prototype either one of them might be in the background.)

Earlier we made the Status Bar white by changing its invert; we now dial it back to the default value: 0.

$.Status_Bar.animate
    invert: 0
    options:
        time: firstSetDuration

By setting the Tab Bar’s bottom, maxY, to the bottom of the screen, it will slide back up.

$.Tabs.animate
    maxY: Screen.height
    options:
        time: firstSetDuration

Because we used a variable, firstSetDuration, to set the duration for these animations, we can slow all of them down to better observe what’s happening.

Like having them animate with a duration of 3 seconds:

# - First set of animations: a third of a second - #
firstSetDuration = 0.3 * 10
Download Framer project
The first set of animations, slowed down

Second set of animations

The next two animations also start immediately but are slower, and they have a subtle bounce.

# - Second set of animations: 0.7 seconds - #
secondSetDuration = 0.7

In 0.7 seconds we’ll:

  • Move the whole “Now Playing” screen (which includes the mini-player) downwards (y, borderRadius)
  • Make the album cover fit on the mini-player (a state animation)

We don’t want to animate everything off-screen because the mini-player should still be visible, so we move the top of the “Now Playing” screen to the height of the Tab Bar + the height of the mini-player.

scroll_now_playing.animate
    y: Screen.height - $.Tabs.height - Mini_Player.height + 1
    borderRadius:
        topLeft: 0
        topRight: 0
    options:
        time: secondSetDuration
        curve: Spring(damping: 0.77)

(Apparently, we need to add 1 extra point to not have a small gap appear.)

We also get rid of the border radius because otherwise, we would have a mini-player with rounded corners.

The added Spring curve is only slightly bouncy, with a 0.77 damping instead of the default 0.5.

And we use this same spring curve when shrinking $.Album_Cover to its ”mini” state:

$.Album_Cover.animate "mini",
    time: secondSetDuration
    curve: Spring(damping: 0.77)
Download Framer project

We did not include animation options when creating ”mini” (as we did for the ”playing” and ”paused” states), but we can add the desired duration and curve here.

Here’s a video of all eight animations at a tenth of their speed:

The second set of animations

Last animation: Hiding the “Now Playing” screen

This last animation starts 0.5 seconds later because we want to be sure that the mini-player is in place before fading out the screen underneath it.

$.Now_Playing.animate
    opacity: 0
    options:
        delay: 0.5
        time: 0.5

(Here we’re animating the opacity of the $.Now_Playing Sketch layer that’s inside our ScrollComponent.)

Background Blur

Now that you can see the mini-player’s transparency you’ll notice something is missing: Background Blur. Whatever is underneath the mini-player should be blurred.

Go back to Design, select the mini-player, give it a Blur of 25, and then change this blur from Layer to Background.

Ah, and now that we switched to the mini-player we can also “flip the switch”:

# The mini-player is now active
miniPlayerActive = yes
Download Framer project
Transitioning from the “Now Playing” screen to the mini-player