Build a throwback UI inspired by the iPod Classic, featuring a click wheel scroller that can detect circular motion with an animated PageView. https://fireship.io/lessons/flutter-ipod/

Original Inspiration Tweet https://twitter.com/elvin_not_11/status/1199717678908366854
iPod Classic https://en.wikipedia.org/wiki/IPod_Classic
Flutter https://flutter.dev/

#flutter #ios #ipod

Install the quiz app 🤓

iOS https://itunes.apple.com/us/app/fireship/id1462592372?mt=8
Android https://play.google.com/store/apps/details?id=io.fireship.quizapp

Upgrade to Fireship PRO at https://fireship.io/pro
Use code lORhwXd2 for 25% off your first payment. 

My VS Code Theme

- Atom One Dark 
- vscode-icons
- Fira Code Font
2024-02-04T06:28:44.150Z
nova-2
2-general-nova
2024-01-09.29447
30089e05-99d1-4376-b32e-c263170674af
296393fa-c24b-4b75-bc13-57a84973afc4
a68168da451ed37b3919ddaca5b38d8fadc0c62cae085ba43b36b65899038a8a
67875a7f-c9c4-48a0-aa55-5bdb8a91c34a
deprecated
Over the last week or so, there's been this really awesome tweak going around that recreates the classic iPod using SwiftUI.
It contains a click wheel that you can rotate around to change the position of the albums in the scrollable view above it.
And if you've ever owned an iPod, it will definitely make you feel some nostalgia.
As soon as I saw this tweet, I knew that I was going to have to build it in Flutter, and that's what this video is all about.
If you're new here, like and subscribe, and grab the full source code from Buyership.
Io.
Let's start by taking a closer look at the app we're building in this video.
Now, I realize that everybody is sick of Flutter UI challenges, but this Video is really all about the UI.
We're not going to request access to the user's music or play actual audio files, but there are plenty of Flutter plugins out there if your goal is to build a full blown audio a player.
Now, if we look at the top of the UI, you'll see that I have a scrollable Page View widget.
It focuses an individual item in the center and then animates it into the view when it's moving.
The off screen images are given some perspective, so they look 3 d, and then rotated.
The rotation is calculated based on their position in the Page View, and that's how we get these cool animations when the scroll position changes.
Now, moving down to the click wheel, we can go from one item to the next by clicking the forward or backwards buttons.
But the real challenge with this demo was creating a click wheel that you can pan across either clockwise or counterclockwise and have the scroll view rotate in the proper direction.
So when the wheel is rotating clockwise, it should move the page view to the right.
When it's moving counterclockwise, it should move the page view back to the left.
And we can do all this with just a regular gesture detector and some basic math.
We don't need a custom painter or trigonometry or anything crazy like that.
In fact, we don't need a single dependency for this project, so let's go ahead and jump into a Flutter app.
The first thing you'll notice is that I have a list of image URLs to show the album covers, which is completely optional because those images sit on top of some colored containers, so you can omit them altogether if you want.
Now, down in our main Material app, We'll go ahead and add a Scaffold, and then inside that Scaffold, we'll add our Ipod widget.
The first thing I'll do is show you how to set up a Scrollable PageView.
In our StatefulWidget, we'll first instantiate a Page Controller, and we'll set the Viewport Fraction to 0.6.
This allows the next and previous page to overflow into the view, so you get a preview of what's coming next, which is similar to the classic Ipod UI.
If you have a viewport fraction of 1, which is the default, it will fill up the entire view.
The next thing we'll do is set up a stateful property called CurrentPage, which starts at 0.
And the reason we have this as state on the widget is because we want to update the UI anytime the current page changes.
And there's multiple ways the user can change that value, either by using the click wheel, by pressing a button, or by using the PageView directly.
We can update that value whenever a change occurs by adding a listener to the Page Controller.
So whenever the current page changes, it will trigger a re render on the component, which will be useful once we get to the animation part.
Now let's move down to our build method.
We'll start with a Safe Area widget that will prevent the PageView from interfering with the status bar at the top.
And that's actually an optimization I got from my friend Mawoolie Joe on Twitter, so thanks for that.
From there, we'll set up a column, and the first item in that column will be a container with a height of 300 and a black background.
The child of that container will be a PageView Builder.
The PageView Builder allows you to render all of the items in the PageView dynamically.
It's similar to a virtual scroll feature on the web, where you're only actually rendering the items that are visible in the view, so it can handle an infinitely large collection.
We'll pass it our Page Controller, then set the scroll direction to horizontal, then provide an Item Builder function that contains the current index of the page in the View.
Now, because we can have an infinite number of pages in the Page View, I'll go ahead and create a new stateless widget called an Album Card.
It takes a color as its input along with the current index and the current page.
Now, you might be wondering why we need the current page and the current index because intuitively, you might think that they are the same value.
And while they're almost the same value, the current index is an integer while the current page is a double.
As you'll see in just a second, we can compare these two values to determine how far offset one of the pages is relative to the center.
And we can use that relative position to create some cool three d transformations.
Now let's go ahead and create the album card stateless widget.
We'll start by passing in the properties that we just talked about.
Then we'll calculate this card's relative position from the center by subtracting the current page from the current index.
If the value is negative, we know the page is on the left side.
If it's Positive, we know it's on the right side.
From there, we'll set up a container with a child of a Transform widget.
Now I could probably do a whole video on transforms, so don't worry if this part feels a little overwhelming.
Our transformation starts as a 4 by 4 matrix.
Basically, this allows you to take a 3 d point in space and perform other transformations on it like translation, rotation, scale, and so on.
We produce the actual matrix by calling setEntry, and the typical starting arguments that you'll see here are 3, 2, and point 1.
But feel free to play around with these to get different effects.
From there, we'll change the scale or the size of the widget.
I had to play around with these values a little bit, but I found it looked best when I clamp the values to range between 0.6 and 1.0.
So the page in the center will be scaled to 1, but the pages on the sides will be scaled to around 0.6.
And lastly, we'll add a transformation to rotate around the y axis based on the relative position.
And if you wanna get really crazy with things, also go ahead and add a rotation to the z axis.
When it comes to the alignment of the individual pages, we want the items on the right side to be aligned center left and the items on the left side to be aligned center right.
Then everything below that is just your basic UI.
So we have a container that has a decoration image with a network image that points to one of the images in our list.
If we fire up our app at this point, we should have a PageView that looks something like this.
When we pan across the view, it cycles between the albums and animates the transformations between them.
Now that that's taken care of, we can move on to the fun part, which is building our radial click wheel.
It will take the page controller that we've already built and then allow the user to pan either clockwise or counterclockwise to control the position of the page view.
And we'll also to add velocity to it as well, so if the user pans faster, it will scroll through the items faster.
Let's head back up to the column that contains our page view builder.
Just below it, we'll go ahead and add a centered widget that contains a child of a stack.
The first thing we'll want in the stack is a gesture detector, and the gesture we care about is on Pan Update.
That event will fire continuously whenever the user is moving their finger across this container.
For now, I'm just going to add a placeholder function there called panhandler, and we'll implement that in just a minute.
Then we can create a circle by setting up a container with a width and a height, then we'll give it some box decoration, where the shape is box shaped circle.
Now, on the iPad, you have more of a donut shape or a circle within a circle.
And we only want to fire the pan event when the user is moving throughout the outside circle or the black circle.
If they pan across the inner gray circle, then nothing should happen.
Because we're inside of a stack that's aligned to the center, we can simply add another container that also has a box shape of circle.
And because it has a higher index in the stack, It will sit on top of the gesture detector and won't allow the events to get through when it is panned over.
Now, you'll also notice that we have different buttons inside the wheel itself.
I'm not going to implement all of these, only the forward and back buttons.
These buttons also sit inside their own stack, and they're positioned relative to the circle so they appear in the right locations.
The ones that are actually pressable are set up as an icon button.
When they're pressed, they'll take the page controller and then animate to the current page plus 1, or if we're going backwards, the current page minus 1.
So now when the user taps one of these buttons, it will animate to the next page or the previous page and give us a nice looking animation in the process.
And with that, we're ready to move on to the final challenge, and that's making our click wheel rotatable.
Let's start by making sure we understand how a wheel works.
We can break a wheel up into different quadrants.
We have the top right, the bottom right, the bottom left, and the top left.
So those are the different parts of the wheel, but then the user can actually pan with their finger in 4 different directions as well.
They can go up, down, left, or right.
And if we think of this as an x y axis, then we know that different movements or positions are mutually exclusive.
Like, the user can't be going x positive and x negative at the same time.
Or in other words, the user can't move left and right at the same time.
Now our Panhandler method will have access to the drag update details for every event, and that event contains things like the delta, or the amount of change since the user's last movement, as well as the local position of that user's movement on the widget itself.
To make our code readable, I'm going to go ahead and set up variables for all these different values.
Since our circle widget has a fixed width of 300 pixels, We'll go ahead and set up a radius value of a 150.
We can determine where the user is dragging by looking at the local position.
We know the user is on the top half of the circle if the local position on the y axis is less than or equal to the radius.
We know they're on the left side if the local position x axis is less than or equal to the radius.
Now we know if the user's on top, then they can't be on bottom, or if they're on the left side, they can't be on the right side.
So we'll grab those values by simply grabbing the opposite of their counterpart.
To determine the direction of the user's movement, we can look at the delta on the event.
Delta is just a fancy word for change.
It represents the amount of movement that the user has made since the last event.
In this case, we know that they're moving up if the y movement is less than or equal to 0, or they're moving left if the x movement is less than or equal to 0.
And then we can use a bang to get their opposites just for the sake of making our code a little more readable.
Now that we can pinpoint exactly how and where a user is panning around, we'll go ahead and set up values for the X change, and y change as their absolute values.
And now, we need to make this value negative or positive depending on which way they're rotating the wheel.
Let's start by thinking about how vertical movement should change the rotation of the wheel.
If they're on the right side and panning down or on the left side and panning up, then that y change should be positive or clockwise.
Otherwise, it'll be negative or counterclockwise.
So we just take the total change and multiply it by negative one.
Now we'll do the same thing for the horizontal rotation.
If we're on top and panning right or on the bottom and panning left, then the change is clockwise, otherwise counterclockwise.
So to get the total rotation value, all we have to do is add these two values together.
It's also a good idea to add velocity, so if the user is panning faster, it will scroll through the items faster.
And we can do that by multiplying by the delta distance.
And now that we have all the data we need, We just need to use our page controller to jump to a new position, and that new position is the current page controller offset plus the scroll offset change that we just calculated.
And that's all there is to it.
You can now use the same logic to implement a rotatable knob or some kind of wheel, or any other type of radial UI widget that the user has control over.
I'm gonna go ahead and wrap things up there.
If this video helped you, please like and subscribe, and consider becoming a pro member at Fireship IO to get access to the full Flutter course, along with a bunch of other advanced content for members only.
Thanks for watching and I will talk to you soon.

Speaker 0: Over the last week or so, there's been this really awesome tweak going around that recreates the classic iPod using SwiftUI. It contains a click wheel that you can rotate around to change the position of the albums in the scrollable view above it. And if you've ever owned an iPod, it will definitely make you feel some nostalgia. As soon as I saw this tweet, I knew that I was going to have to build it in Flutter, and that's what this video is all about. If you're new here, like and subscribe, and grab the full source code from Buyership.

Io. Let's start by taking a closer look at the app we're building in this video. Now, I realize that everybody is sick of Flutter UI challenges, but this Video is really all about the UI. We're not going to request access to the user's music or play actual audio files, but there are plenty of Flutter plugins out there if your goal is to build a full blown audio a player. Now, if we look at the top of the UI, you'll see that I have a scrollable Page View widget.

It focuses an individual item in the center and then animates it into the view when it's moving. The off screen images are given some perspective, so they look 3 d, and then rotated. The rotation is calculated based on their position in the Page View, and that's how we get these cool animations when the scroll position changes. Now, moving down to the click wheel, we can go from one item to the next by clicking the forward or backwards buttons. But the real challenge with this demo was creating a click wheel that you can pan across either clockwise or counterclockwise and have the scroll view rotate in the proper direction.

So when the wheel is rotating clockwise, it should move the page view to the right. When it's moving counterclockwise, it should move the page view back to the left. And we can do all this with just a regular gesture detector and some basic math. We don't need a custom painter or trigonometry or anything crazy like that. In fact, we don't need a single dependency for this project, so let's go ahead and jump into a Flutter app.

The first thing you'll notice is that I have a list of image URLs to show the album covers, which is completely optional because those images sit on top of some colored containers, so you can omit them altogether if you want. Now, down in our main Material app, We'll go ahead and add a Scaffold, and then inside that Scaffold, we'll add our Ipod widget. The first thing I'll do is show you how to set up a Scrollable PageView. In our StatefulWidget, we'll first instantiate a Page Controller, and we'll set the Viewport Fraction to 0.6. This allows the next and previous page to overflow into the view, so you get a preview of what's coming next, which is similar to the classic Ipod UI.

If you have a viewport fraction of 1, which is the default, it will fill up the entire view. The next thing we'll do is set up a stateful property called CurrentPage, which starts at 0. And the reason we have this as state on the widget is because we want to update the UI anytime the current page changes. And there's multiple ways the user can change that value, either by using the click wheel, by pressing a button, or by using the PageView directly. We can update that value whenever a change occurs by adding a listener to the Page Controller.

So whenever the current page changes, it will trigger a re render on the component, which will be useful once we get to the animation part. Now let's move down to our build method. We'll start with a Safe Area widget that will prevent the PageView from interfering with the status bar at the top. And that's actually an optimization I got from my friend Mawoolie Joe on Twitter, so thanks for that. From there, we'll set up a column, and the first item in that column will be a container with a height of 300 and a black background.

The child of that container will be a PageView Builder. The PageView Builder allows you to render all of the items in the PageView dynamically. It's similar to a virtual scroll feature on the web, where you're only actually rendering the items that are visible in the view, so it can handle an infinitely large collection. We'll pass it our Page Controller, then set the scroll direction to horizontal, then provide an Item Builder function that contains the current index of the page in the View. Now, because we can have an infinite number of pages in the Page View, I'll go ahead and create a new stateless widget called an Album Card.

It takes a color as its input along with the current index and the current page. Now, you might be wondering why we need the current page and the current index because intuitively, you might think that they are the same value. And while they're almost the same value, the current index is an integer while the current page is a double. As you'll see in just a second, we can compare these two values to determine how far offset one of the pages is relative to the center. And we can use that relative position to create some cool three d transformations.

Now let's go ahead and create the album card stateless widget. We'll start by passing in the properties that we just talked about. Then we'll calculate this card's relative position from the center by subtracting the current page from the current index. If the value is negative, we know the page is on the left side. If it's Positive, we know it's on the right side.

From there, we'll set up a container with a child of a Transform widget. Now I could probably do a whole video on transforms, so don't worry if this part feels a little overwhelming. Our transformation starts as a 4 by 4 matrix. Basically, this allows you to take a 3 d point in space and perform other transformations on it like translation, rotation, scale, and so on. We produce the actual matrix by calling setEntry, and the typical starting arguments that you'll see here are 3, 2, and point 1.

But feel free to play around with these to get different effects. From there, we'll change the scale or the size of the widget. I had to play around with these values a little bit, but I found it looked best when I clamp the values to range between 0.6 and 1.0. So the page in the center will be scaled to 1, but the pages on the sides will be scaled to around 0.6. And lastly, we'll add a transformation to rotate around the y axis based on the relative position.

And if you wanna get really crazy with things, also go ahead and add a rotation to the z axis. When it comes to the alignment of the individual pages, we want the items on the right side to be aligned center left and the items on the left side to be aligned center right. Then everything below that is just your basic UI. So we have a container that has a decoration image with a network image that points to one of the images in our list. If we fire up our app at this point, we should have a PageView that looks something like this.

When we pan across the view, it cycles between the albums and animates the transformations between them. Now that that's taken care of, we can move on to the fun part, which is building our radial click wheel. It will take the page controller that we've already built and then allow the user to pan either clockwise or counterclockwise to control the position of the page view. And we'll also to add velocity to it as well, so if the user pans faster, it will scroll through the items faster. Let's head back up to the column that contains our page view builder.

Just below it, we'll go ahead and add a centered widget that contains a child of a stack. The first thing we'll want in the stack is a gesture detector, and the gesture we care about is on Pan Update. That event will fire continuously whenever the user is moving their finger across this container. For now, I'm just going to add a placeholder function there called panhandler, and we'll implement that in just a minute. Then we can create a circle by setting up a container with a width and a height, then we'll give it some box decoration, where the shape is box shaped circle.

Now, on the iPad, you have more of a donut shape or a circle within a circle. And we only want to fire the pan event when the user is moving throughout the outside circle or the black circle. If they pan across the inner gray circle, then nothing should happen. Because we're inside of a stack that's aligned to the center, we can simply add another container that also has a box shape of circle. And because it has a higher index in the stack, It will sit on top of the gesture detector and won't allow the events to get through when it is panned over.

Now, you'll also notice that we have different buttons inside the wheel itself. I'm not going to implement all of these, only the forward and back buttons. These buttons also sit inside their own stack, and they're positioned relative to the circle so they appear in the right locations. The ones that are actually pressable are set up as an icon button. When they're pressed, they'll take the page controller and then animate to the current page plus 1, or if we're going backwards, the current page minus 1.

So now when the user taps one of these buttons, it will animate to the next page or the previous page and give us a nice looking animation in the process. And with that, we're ready to move on to the final challenge, and that's making our click wheel rotatable. Let's start by making sure we understand how a wheel works. We can break a wheel up into different quadrants. We have the top right, the bottom right, the bottom left, and the top left.

So those are the different parts of the wheel, but then the user can actually pan with their finger in 4 different directions as well. They can go up, down, left, or right. And if we think of this as an x y axis, then we know that different movements or positions are mutually exclusive. Like, the user can't be going x positive and x negative at the same time. Or in other words, the user can't move left and right at the same time.

Now our Panhandler method will have access to the drag update details for every event, and that event contains things like the delta, or the amount of change since the user's last movement, as well as the local position of that user's movement on the widget itself. To make our code readable, I'm going to go ahead and set up variables for all these different values. Since our circle widget has a fixed width of 300 pixels, We'll go ahead and set up a radius value of a 150. We can determine where the user is dragging by looking at the local position. We know the user is on the top half of the circle if the local position on the y axis is less than or equal to the radius.

We know they're on the left side if the local position x axis is less than or equal to the radius. Now we know if the user's on top, then they can't be on bottom, or if they're on the left side, they can't be on the right side. So we'll grab those values by simply grabbing the opposite of their counterpart. To determine the direction of the user's movement, we can look at the delta on the event. Delta is just a fancy word for change.

It represents the amount of movement that the user has made since the last event. In this case, we know that they're moving up if the y movement is less than or equal to 0, or they're moving left if the x movement is less than or equal to 0. And then we can use a bang to get their opposites just for the sake of making our code a little more readable. Now that we can pinpoint exactly how and where a user is panning around, we'll go ahead and set up values for the X change, and y change as their absolute values. And now, we need to make this value negative or positive depending on which way they're rotating the wheel.

Let's start by thinking about how vertical movement should change the rotation of the wheel. If they're on the right side and panning down or on the left side and panning up, then that y change should be positive or clockwise. Otherwise, it'll be negative or counterclockwise. So we just take the total change and multiply it by negative one. Now we'll do the same thing for the horizontal rotation.

If we're on top and panning right or on the bottom and panning left, then the change is clockwise, otherwise counterclockwise. So to get the total rotation value, all we have to do is add these two values together. It's also a good idea to add velocity, so if the user is panning faster, it will scroll through the items faster. And we can do that by multiplying by the delta distance. And now that we have all the data we need, We just need to use our page controller to jump to a new position, and that new position is the current page controller offset plus the scroll offset change that we just calculated.

And that's all there is to it. You can now use the same logic to implement a rotatable knob or some kind of wheel, or any other type of radial UI widget that the user has control over. I'm gonna go ahead and wrap things up there. If this video helped you, please like and subscribe, and consider becoming a pro member at Fireship IO to get access to the full Flutter course, along with a bunch of other advanced content for members only. Thanks for watching and I will talk to you soon.
Over the last week or so, there's been this really awesome tweak going around that recreates the classic iPod using SwiftUI. It contains a click wheel that you can rotate around to change the position of the albums in the scrollable view above it. And if you've ever owned an iPod, it will definitely make you feel some nostalgia. As soon as I saw this tweet, I knew that I was going to have to build it in Flutter, and that's what this video is all about. If you're new here, like and subscribe, and grab the full source code from Buyership. Io. Let's start by taking a closer look at the app we're building in this video. Now, I realize that everybody is sick of Flutter UI challenges, but this Video is really all about the UI. We're not going to request access to the user's music or play actual audio files, but there are plenty of Flutter plugins out there if your goal is to build a full blown audio a player. Now, if we look at the top of the UI, you'll see that I have a scrollable Page View widget. It focuses an individual item in the center and then animates it into the view when it's moving. The off screen images are given some perspective, so they look 3 d, and then rotated. The rotation is calculated based on their position in the Page View, and that's how we get these cool animations when the scroll position changes. Now, moving down to the click wheel, we can go from one item to the next by clicking the forward or backwards buttons. But the real challenge with this demo was creating a click wheel that you can pan across either clockwise or counterclockwise and have the scroll view rotate in the proper direction.
video gaming
robotics
animation
youtube
visualization
audio
multimedia
streaming
music
So when the wheel is rotating clockwise, it should move the page view to the right. When it's moving counterclockwise, it should move the page view back to the left.
And we can do all this with just a regular gesture detector and some basic math. We don't need a custom painter or trigonometry or anything crazy like that. In fact, we don't need a single dependency for this project, so let's go ahead and jump into a Flutter app.
animation
robotics
The first thing you'll notice is that I have a list of image URLs to show the album covers, which is completely optional because those images sit on top of some colored containers, so you can omit them altogether if you want. Now, down in our main Material app, We'll go ahead and add a Scaffold, and then inside that Scaffold, we'll add our Ipod widget. The first thing I'll do is show you how to set up a Scrollable PageView. In our StatefulWidget, we'll first instantiate a Page Controller, and we'll set the Viewport Fraction to 0.6. This allows the next and previous page to overflow into the view, so you get a preview of what's coming next, which is similar to the classic Ipod UI. If you have a viewport fraction of 1, which is the default, it will fill up the entire view. The next thing we'll do is set up a stateful property called CurrentPage, which starts at 0. And the reason we have this as state on the widget is because we want to update the UI anytime the current page changes. And there's multiple ways the user can change that value, either by using the click wheel, by pressing a button, or by using the PageView directly. We can update that value whenever a change occurs by adding a listener to the Page Controller. So whenever the current page changes, it will trigger a re render on the component, which will be useful once we get to the animation part. Now let's move down to our build method. We'll start with a Safe Area widget that will prevent the PageView from interfering with the status bar at the top. And that's actually an optimization I got from my friend Mawoolie Joe on Twitter, so thanks for that. From there, we'll set up a column, and the first item in that column will be a container with a height of 300 and a black background.
The child of that container will be a PageView Builder. The PageView Builder allows you to render all of the items in the PageView dynamically. It's similar to a virtual scroll feature on the web, where you're only actually rendering the items that are visible in the view, so it can handle an infinitely large collection.
visualization
We'll pass it our Page Controller, then set the scroll direction to horizontal, then provide an Item Builder function that contains the current index of the page in the View. Now, because we can have an infinite number of pages in the Page View, I'll go ahead and create a new stateless widget called an Album Card. It takes a color as its input along with the current index and the current page. Now, you might be wondering why we need the current page and the current index because intuitively, you might think that they are the same value. And while they're almost the same value, the current index is an integer while the current page is a double. As you'll see in just a second, we can compare these two values to determine how far offset one of the pages is relative to the center. And we can use that relative position to create some cool three d transformations. Now let's go ahead and create the album card stateless widget. We'll start by passing in the properties that we just talked about. Then we'll calculate this card's relative position from the center by subtracting the current page from the current index. If the value is negative, we know the page is on the left side. If it's Positive, we know it's on the right side. From there, we'll set up a container with a child of a Transform widget.
Now I could probably do a whole video on transforms, so don't worry if this part feels a little overwhelming. Our transformation starts as a 4 by 4 matrix. Basically, this allows you to take a 3 d point in space and perform other transformations on it like translation, rotation, scale, and so on. We produce the actual matrix by calling setEntry, and the typical starting arguments that you'll see here are 3, 2, and point 1. But feel free to play around with these to get different effects.
geometry
From there, we'll change the scale or the size of the widget. I had to play around with these values a little bit, but I found it looked best when I clamp the values to range between 0.6 and 1.0. So the page in the center will be scaled to 1, but the pages on the sides will be scaled to around 0.6. And lastly, we'll add a transformation to rotate around the y axis based on the relative position. And if you wanna get really crazy with things, also go ahead and add a rotation to the z axis. When it comes to the alignment of the individual pages, we want the items on the right side to be aligned center left and the items on the left side to be aligned center right. Then everything below that is just your basic UI. So we have a container that has a decoration image with a network image that points to one of the images in our list.
If we fire up our app at this point, we should have a PageView that looks something like this. When we pan across the view, it cycles between the albums and animates the transformations between them. Now that that's taken care of, we can move on to the fun part, which is building our radial click wheel. It will take the page controller that we've already built and then allow the user to pan either clockwise or counterclockwise to control the position of the page view.
animation
robotics
And we'll also to add velocity to it as well, so if the user pans faster, it will scroll through the items faster. Let's head back up to the column that contains our page view builder. Just below it, we'll go ahead and add a centered widget that contains a child of a stack. The first thing we'll want in the stack is a gesture detector, and the gesture we care about is on Pan Update. That event will fire continuously whenever the user is moving their finger across this container. For now, I'm just going to add a placeholder function there called panhandler, and we'll implement that in just a minute.
Then we can create a circle by setting up a container with a width and a height, then we'll give it some box decoration, where the shape is box shaped circle. Now, on the iPad, you have more of a donut shape or a circle within a circle. And we only want to fire the pan event when the user is moving throughout the outside circle or the black circle. If they pan across the inner gray circle, then nothing should happen.
baking
Because we're inside of a stack that's aligned to the center, we can simply add another container that also has a box shape of circle. And because it has a higher index in the stack, It will sit on top of the gesture detector and won't allow the events to get through when it is panned over. Now, you'll also notice that we have different buttons inside the wheel itself. I'm not going to implement all of these, only the forward and back buttons. These buttons also sit inside their own stack, and they're positioned relative to the circle so they appear in the right locations. The ones that are actually pressable are set up as an icon button.
When they're pressed, they'll take the page controller and then animate to the current page plus 1, or if we're going backwards, the current page minus 1. So now when the user taps one of these buttons, it will animate to the next page or the previous page and give us a nice looking animation in the process. And with that, we're ready to move on to the final challenge, and that's making our click wheel rotatable. Let's start by making sure we understand how a wheel works. We can break a wheel up into different quadrants. We have the top right, the bottom right, the bottom left, and the top left.
animation
robotics
So those are the different parts of the wheel, but then the user can actually pan with their finger in 4 different directions as well. They can go up, down, left, or right. And if we think of this as an x y axis, then we know that different movements or positions are mutually exclusive. Like, the user can't be going x positive and x negative at the same time. Or in other words, the user can't move left and right at the same time. Now our Panhandler method will have access to the drag update details for every event, and that event contains things like the delta, or the amount of change since the user's last movement, as well as the local position of that user's movement on the widget itself. To make our code readable, I'm going to go ahead and set up variables for all these different values. Since our circle widget has a fixed width of 300 pixels, We'll go ahead and set up a radius value of a 150. We can determine where the user is dragging by looking at the local position. We know the user is on the top half of the circle if the local position on the y axis is less than or equal to the radius. We know they're on the left side if the local position x axis is less than or equal to the radius. Now we know if the user's on top, then they can't be on bottom, or if they're on the left side, they can't be on the right side. So we'll grab those values by simply grabbing the opposite of their counterpart. To determine the direction of the user's movement, we can look at the delta on the event. Delta is just a fancy word for change. It represents the amount of movement that the user has made since the last event. In this case, we know that they're moving up if the y movement is less than or equal to 0, or they're moving left if the x movement is less than or equal to 0.
And then we can use a bang to get their opposites just for the sake of making our code a little more readable. Now that we can pinpoint exactly how and where a user is panning around, we'll go ahead and set up values for the X change, and y change as their absolute values.
programming
logic
And now, we need to make this value negative or positive depending on which way they're rotating the wheel. Let's start by thinking about how vertical movement should change the rotation of the wheel. If they're on the right side and panning down or on the left side and panning up, then that y change should be positive or clockwise. Otherwise, it'll be negative or counterclockwise. So we just take the total change and multiply it by negative one. Now we'll do the same thing for the horizontal rotation. If we're on top and panning right or on the bottom and panning left, then the change is clockwise, otherwise counterclockwise. So to get the total rotation value, all we have to do is add these two values together. It's also a good idea to add velocity, so if the user is panning faster, it will scroll through the items faster. And we can do that by multiplying by the delta distance. And now that we have all the data we need, We just need to use our page controller to jump to a new position, and that new position is the current page controller offset plus the scroll offset change that we just calculated. And that's all there is to it. You can now use the same logic to implement a rotatable knob or some kind of wheel, or any other type of radial UI widget that the user has control over.
I'm gonna go ahead and wrap things up there. If this video helped you, please like and subscribe, and consider becoming a pro member at Fireship IO to get access to the full Flutter course, along with a bunch of other advanced content for members only. Thanks for watching and I will talk to you soon.
youtube
streaming
video gaming
Over the last week or so, there's been this really awesome tweak going around that recreates the classic iPod using SwiftUI. It contains a click wheel that you can rotate around to change the position of the albums in the scrollable view above it. And if you've ever owned an iPod, it will definitely make you feel some nostalgia. As soon as I saw this tweet, I knew that I was going to have to build it in Flutter, and that's what this video is all about. If you're new here, like and subscribe, and grab the full source code from Buyership. Io. Let's start by taking a closer look at the app we're building in this video. Now, I realize that everybody is sick of Flutter UI challenges, but this Video is really all about the UI. We're not going to request access to the user's music or play actual audio files, but there are plenty of Flutter plugins out there if your goal is to build a full blown audio a player. Now, if we look at the top of the UI, you'll see that I have a scrollable Page View widget. It focuses an individual item in the center and then animates it into the view when it's moving. The off screen images are given some perspective, so they look 3 d, and then rotated. The rotation is calculated based on their position in the Page View, and that's how we get these cool animations when the scroll position changes. Now, moving down to the click wheel, we can go from one item to the next by clicking the forward or backwards buttons. But the real challenge with this demo was creating a click wheel that you can pan across either clockwise or counterclockwise and have the scroll view rotate in the proper direction. So when the wheel is rotating clockwise, it should move the page view to the right. When it's moving counterclockwise, it should move the page view back to the left. And we can do all this with just a regular gesture detector and some basic math. We don't need a custom painter or trigonometry or anything crazy like that. In fact, we don't need a single dependency for this project, so let's go ahead and jump into a Flutter app. The first thing you'll notice is that I have a list of image URLs to show the album covers, which is completely optional because those images sit on top of some colored containers, so you can omit them altogether if you want. Now, down in our main Material app, We'll go ahead and add a Scaffold, and then inside that Scaffold, we'll add our Ipod widget. The first thing I'll do is show you how to set up a Scrollable PageView. In our StatefulWidget, we'll first instantiate a Page Controller, and we'll set the Viewport Fraction to 0.6. This allows the next and previous page to overflow into the view, so you get a preview of what's coming next, which is similar to the classic Ipod UI. If you have a viewport fraction of 1, which is the default, it will fill up the entire view. The next thing we'll do is set up a stateful property called CurrentPage, which starts at 0. And the reason we have this as state on the widget is because we want to update the UI anytime the current page changes. And there's multiple ways the user can change that value, either by using the click wheel, by pressing a button, or by using the PageView directly. We can update that value whenever a change occurs by adding a listener to the Page Controller. So whenever the current page changes, it will trigger a re render on the component, which will be useful once we get to the animation part. Now let's move down to our build method. We'll start with a Safe Area widget that will prevent the PageView from interfering with the status bar at the top. And that's actually an optimization I got from my friend Mawoolie Joe on Twitter, so thanks for that. From there, we'll set up a column, and the first item in that column will be a container with a height of 300 and a black background. The child of that container will be a PageView Builder. The PageView Builder allows you to render all of the items in the PageView dynamically. It's similar to a virtual scroll feature on the web, where you're only actually rendering the items that are visible in the view, so it can handle an infinitely large collection. We'll pass it our Page Controller, then set the scroll direction to horizontal, then provide an Item Builder function that contains the current index of the page in the View. Now, because we can have an infinite number of pages in the Page View, I'll go ahead and create a new stateless widget called an Album Card. It takes a color as its input along with the current index and the current page. Now, you might be wondering why we need the current page and the current index because intuitively, you might think that they are the same value. And while they're almost the same value, the current index is an integer while the current page is a double. As you'll see in just a second, we can compare these two values to determine how far offset one of the pages is relative to the center. And we can use that relative position to create some cool three d transformations. Now let's go ahead and create the album card stateless widget. We'll start by passing in the properties that we just talked about. Then we'll calculate this card's relative position from the center by subtracting the current page from the current index. If the value is negative, we know the page is on the left side. If it's Positive, we know it's on the right side. From there, we'll set up a container with a child of a Transform widget. Now I could probably do a whole video on transforms, so don't worry if this part feels a little overwhelming. Our transformation starts as a 4 by 4 matrix. Basically, this allows you to take a 3 d point in space and perform other transformations on it like translation, rotation, scale, and so on. We produce the actual matrix by calling setEntry, and the typical starting arguments that you'll see here are 3, 2, and point 1. But feel free to play around with these to get different effects. From there, we'll change the scale or the size of the widget. I had to play around with these values a little bit, but I found it looked best when I clamp the values to range between 0.6 and 1.0. So the page in the center will be scaled to 1, but the pages on the sides will be scaled to around 0.6. And lastly, we'll add a transformation to rotate around the y axis based on the relative position. And if you wanna get really crazy with things, also go ahead and add a rotation to the z axis. When it comes to the alignment of the individual pages, we want the items on the right side to be aligned center left and the items on the left side to be aligned center right. Then everything below that is just your basic UI. So we have a container that has a decoration image with a network image that points to one of the images in our list. If we fire up our app at this point, we should have a PageView that looks something like this. When we pan across the view, it cycles between the albums and animates the transformations between them. Now that that's taken care of, we can move on to the fun part, which is building our radial click wheel. It will take the page controller that we've already built and then allow the user to pan either clockwise or counterclockwise to control the position of the page view. And we'll also to add velocity to it as well, so if the user pans faster, it will scroll through the items faster. Let's head back up to the column that contains our page view builder. Just below it, we'll go ahead and add a centered widget that contains a child of a stack. The first thing we'll want in the stack is a gesture detector, and the gesture we care about is on Pan Update. That event will fire continuously whenever the user is moving their finger across this container. For now, I'm just going to add a placeholder function there called panhandler, and we'll implement that in just a minute. Then we can create a circle by setting up a container with a width and a height, then we'll give it some box decoration, where the shape is box shaped circle. Now, on the iPad, you have more of a donut shape or a circle within a circle. And we only want to fire the pan event when the user is moving throughout the outside circle or the black circle. If they pan across the inner gray circle, then nothing should happen. Because we're inside of a stack that's aligned to the center, we can simply add another container that also has a box shape of circle. And because it has a higher index in the stack, It will sit on top of the gesture detector and won't allow the events to get through when it is panned over. Now, you'll also notice that we have different buttons inside the wheel itself. I'm not going to implement all of these, only the forward and back buttons. These buttons also sit inside their own stack, and they're positioned relative to the circle so they appear in the right locations. The ones that are actually pressable are set up as an icon button. When they're pressed, they'll take the page controller and then animate to the current page plus 1, or if we're going backwards, the current page minus 1. So now when the user taps one of these buttons, it will animate to the next page or the previous page and give us a nice looking animation in the process. And with that, we're ready to move on to the final challenge, and that's making our click wheel rotatable. Let's start by making sure we understand how a wheel works. We can break a wheel up into different quadrants. We have the top right, the bottom right, the bottom left, and the top left. So those are the different parts of the wheel, but then the user can actually pan with their finger in 4 different directions as well. They can go up, down, left, or right. And if we think of this as an x y axis, then we know that different movements or positions are mutually exclusive. Like, the user can't be going x positive and x negative at the same time. Or in other words, the user can't move left and right at the same time. Now our Panhandler method will have access to the drag update details for every event, and that event contains things like the delta, or the amount of change since the user's last movement, as well as the local position of that user's movement on the widget itself. To make our code readable, I'm going to go ahead and set up variables for all these different values. Since our circle widget has a fixed width of 300 pixels, We'll go ahead and set up a radius value of a 150. We can determine where the user is dragging by looking at the local position. We know the user is on the top half of the circle if the local position on the y axis is less than or equal to the radius. We know they're on the left side if the local position x axis is less than or equal to the radius. Now we know if the user's on top, then they can't be on bottom, or if they're on the left side, they can't be on the right side. So we'll grab those values by simply grabbing the opposite of their counterpart. To determine the direction of the user's movement, we can look at the delta on the event. Delta is just a fancy word for change. It represents the amount of movement that the user has made since the last event. In this case, we know that they're moving up if the y movement is less than or equal to 0, or they're moving left if the x movement is less than or equal to 0. And then we can use a bang to get their opposites just for the sake of making our code a little more readable. Now that we can pinpoint exactly how and where a user is panning around, we'll go ahead and set up values for the X change, and y change as their absolute values. And now, we need to make this value negative or positive depending on which way they're rotating the wheel. Let's start by thinking about how vertical movement should change the rotation of the wheel. If they're on the right side and panning down or on the left side and panning up, then that y change should be positive or clockwise. Otherwise, it'll be negative or counterclockwise. So we just take the total change and multiply it by negative one. Now we'll do the same thing for the horizontal rotation. If we're on top and panning right or on the bottom and panning left, then the change is clockwise, otherwise counterclockwise. So to get the total rotation value, all we have to do is add these two values together. It's also a good idea to add velocity, so if the user is panning faster, it will scroll through the items faster. And we can do that by multiplying by the delta distance. And now that we have all the data we need, We just need to use our page controller to jump to a new position, and that new position is the current page controller offset plus the scroll offset change that we just calculated. And that's all there is to it. You can now use the same logic to implement a rotatable knob or some kind of wheel, or any other type of radial UI widget that the user has control over. I'm gonna go ahead and wrap things up there. If this video helped you, please like and subscribe, and consider becoming a pro member at Fireship IO to get access to the full Flutter course, along with a bunch of other advanced content for members only. Thanks for watching and I will talk to you soon.
Over
over
the
the
last
last
week
week
or
or
so,
so
there's
there's
been
been
this
this
really
really
awesome
awesome
tweak
tweak
going
going
around
around
that
that
recreates
recreates
the
the
classic
classic
iPod
ipod
using
using
SwiftUI.
swiftui
It
it
contains
contains
a
a
click
click
wheel
wheel
that
that
you
you
can
can
rotate
rotate
around
around
to
to
change
change
the
the
position
position
of
of
the
the
albums
albums
in
in
the
the
scrollable
scrollable
view
view
above
above
it.
it
And
and
if
if
you've
you've
ever
ever
owned
owned
an
an
iPod,
ipod
it
it
will
will
definitely
definitely
make
make
you
you
feel
feel
some
some
nostalgia.
nostalgia
As
as
soon
soon
as
as
I
i
saw
saw
this
this
tweet,
tweet
I
i
knew
knew
that
that
I
i
was
was
going
going
to
to
have
have
to
to
build
build
it
it
in
in
Flutter,
flutter
and
and
that's
that's
what
what
this
this
video
video
is
is
all
all
about.
about
If
if
you're
you're
new
new
here,
here
like
like
and
and
subscribe,
subscribe
and
and
grab
grab
the
the
full
full
source
source
code
code
from
from
Buyership.
buyership
Io.
io
Let's
let's
start
start
by
by
taking
taking
a
a
closer
closer
look
look
at
at
the
the
app
app
we're
we're
building
building
in
in
this
this
video.
video
Now,
now
I
i
realize
realize
that
that
everybody
everybody
is
is
sick
sick
of
of
Flutter
flutter
UI
ui
challenges,
challenges
but
but
this
this
Video
video
is
is
really
really
all
all
about
about
the
the
UI.
ui
We're
we're
not
not
going
going
to
to
request
request
access
access
to
to
the
the
user's
user's
music
music
or
or
play
play
actual
actual
audio
audio
files,
files
but
but
there
there
are
are
plenty
plenty
of
of
Flutter
flutter
plugins
plugins
out
out
there
there
if
if
your
your
goal
goal
is
is
to
to
build
build
a
a
full
full
blown
blown
audio
audio
a
a
player.
player
Now,
now
if
if
we
we
look
look
at
at
the
the
top
top
of
of
the
the
UI,
ui
you'll
you'll
see
see
that
that
I
i
have
have
a
a
scrollable
scrollable
Page
page
View
view
widget.
widget
It
it
focuses
focuses
an
an
individual
individual
item
item
in
in
the
the
center
center
and
and
then
then
animates
animates
it
it
into
into
the
the
view
view
when
when
it's
it's
moving.
moving
The
the
off
off
screen
screen
images
images
are
are
given
given
some
some
perspective,
perspective
so
so
they
they
look
look
3
3
d,
d
and
and
then
then
rotated.
rotated
The
the
rotation
rotation
is
is
calculated
calculated
based
based
on
on
their
their
position
position
in
in
the
the
Page
page
View,
view
and
and
that's
that's
how
how
we
we
get
get
these
these
cool
cool
animations
animations
when
when
the
the
scroll
scroll
position
position
changes.
changes
Now,
now
moving
moving
down
down
to
to
the
the
click
click
wheel,
wheel
we
we
can
can
go
go
from
from
one
one
item
item
to
to
the
the
next
next
by
by
clicking
clicking
the
the
forward
forward
or
or
backwards
backwards
buttons.
buttons
But
but
the
the
real
real
challenge
challenge
with
with
this
this
demo
demo
was
was
creating
creating
a
a
click
click
wheel
wheel
that
that
you
you
can
can
pan
pan
across
across
either
either
clockwise
clockwise
or
or
counterclockwise
counterclockwise
and
and
have
have
the
the
scroll
scroll
view
view
rotate
rotate
in
in
the
the
proper
proper
direction.
direction
So
so
when
when
the
the
wheel
wheel
is
is
rotating
rotating
clockwise,
clockwise
it
it
should
should
move
move
the
the
page
page
view
view
to
to
the
the
right.
right
When
when
it's
it's
moving
moving
counterclockwise,
counterclockwise
it
it
should
should
move
move
the
the
page
page
view
view
back
back
to
to
the
the
left.
left
And
and
we
we
can
can
do
do
all
all
this
this
with
with
just
just
a
a
regular
regular
gesture
gesture
detector
detector
and
and
some
some
basic
basic
math.
math
We
we
don't
don't
need
need
a
a
custom
custom
painter
painter
or
or
trigonometry
trigonometry
or
or
anything
anything
crazy
crazy
like
like
that.
that
In
in
fact,
fact
we
we
don't
don't
need
need
a
a
single
single
dependency
dependency
for
for
this
this
project,
project
so
so
let's
let's
go
go
ahead
ahead
and
and
jump
jump
into
into
a
a
Flutter
flutter
app.
app
The
the
first
first
thing
thing
you'll
you'll
notice
notice
is
is
that
that
I
i
have
have
a
a
list
list
of
of
image
image
URLs
urls
to
to
show
show
the
the
album
album
covers,
covers
which
which
is
is
completely
completely
optional
optional
because
because
those
those
images
images
sit
sit
on
on
top
top
of
of
some
some
colored
colored
containers,
containers
so
so
you
you
can
can
omit
omit
them
them
altogether
altogether
if
if
you
you
want.
want
Now,
now
down
down
in
in
our
our
main
main
Material
material
app,
app
We'll
we'll
go
go
ahead
ahead
and
and
add
add
a
a
Scaffold,
scaffold
and
and
then
then
inside
inside
that
that
Scaffold,
scaffold
we'll
we'll
add
add
our
our
Ipod
ipod
widget.
widget
The
the
first
first
thing
thing
I'll
i'll
do
do
is
is
show
show
you
you
how
how
to
to
set
set
up
up
a
a
Scrollable
scrollable
PageView.
pageview
In
in
our
our
StatefulWidget,
statefulwidget
we'll
we'll
first
first
instantiate
instantiate
a
a
Page
page
Controller,
controller
and
and
we'll
we'll
set
set
the
the
Viewport
viewport
Fraction
fraction
to
to
0.6.
0.6
This
this
allows
allows
the
the
next
next
and
and
previous
previous
page
page
to
to
overflow
overflow
into
into
the
the
view,
view
so
so
you
you
get
get
a
a
preview
preview
of
of
what's
what's
coming
coming
next,
next
which
which
is
is
similar
similar
to
to
the
the
classic
classic
Ipod
ipod
UI.
ui
If
if
you
you
have
have
a
a
viewport
viewport
fraction
fraction
of
of
1,
1
which
which
is
is
the
the
default,
default
it
it
will
will
fill
fill
up
up
the
the
entire
entire
view.
view
The
the
next
next
thing
thing
we'll
we'll
do
do
is
is
set
set
up
up
a
a
stateful
stateful
property
property
called
called
CurrentPage,
currentpage
which
which
starts
starts
at
at
0.
0
And
and
the
the
reason
reason
we
we
have
have
this
this
as
as
state
state
on
on
the
the
widget
widget
is
is
because
because
we
we
want
want
to
to
update
update
the
the
UI
ui
anytime
anytime
the
the
current
current
page
page
changes.
changes
And
and
there's
there's
multiple
multiple
ways
ways
the
the
user
user
can
can
change
change
that
that
value,
value
either
either
by
by
using
using
the
the
click
click
wheel,
wheel
by
by
pressing
pressing
a
a
button,
button
or
or
by
by
using
using
the
the
PageView
pageview
directly.
directly
We
we
can
can
update
update
that
that
value
value
whenever
whenever
a
a
change
change
occurs
occurs
by
by
adding
adding
a
a
listener
listener
to
to
the
the
Page
page
Controller.
controller
So
so
whenever
whenever
the
the
current
current
page
page
changes,
changes
it
it
will
will
trigger
trigger
a
a
re
re
render
render
on
on
the
the
component,
component
which
which
will
will
be
be
useful
useful
once
once
we
we
get
get
to
to
the
the
animation
animation
part.
part
Now
now
let's
let's
move
move
down
down
to
to
our
our
build
build
method.
method
We'll
we'll
start
start
with
with
a
a
Safe
safe
Area
area
widget
widget
that
that
will
will
prevent
prevent
the
the
PageView
pageview
from
from
interfering
interfering
with
with
the
the
status
status
bar
bar
at
at
the
the
top.
top
And
and
that's
that's
actually
actually
an
an
optimization
optimization
I
i
got
got
from
from
my
my
friend
friend
Mawoolie
mawoolie
Joe
joe
on
on
Twitter,
twitter
so
so
thanks
thanks
for
for
that.
that
From
from
there,
there
we'll
we'll
set
set
up
up
a
a
column,
column
and
and
the
the
first
first
item
item
in
in
that
that
column
column
will
will
be
be
a
a
container
container
with
with
a
a
height
height
of
of
300
300
and
and
a
a
black
black
background.
background
The
the
child
child
of
of
that
that
container
container
will
will
be
be
a
a
PageView
pageview
Builder.
builder
The
the
PageView
pageview
Builder
builder
allows
allows
you
you
to
to
render
render
all
all
of
of
the
the
items
items
in
in
the
the
PageView
pageview
dynamically.
dynamically
It's
it's
similar
similar
to
to
a
a
virtual
virtual
scroll
scroll
feature
feature
on
on
the
the
web,
web
where
where
you're
you're
only
only
actually
actually
rendering
rendering
the
the
items
items
that
that
are
are
visible
visible
in
in
the
the
view,
view
so
so
it
it
can
can
handle
handle
an
an
infinitely
infinitely
large
large
collection.
collection
We'll
we'll
pass
pass
it
it
our
our
Page
page
Controller,
controller
then
then
set
set
the
the
scroll
scroll
direction
direction
to
to
horizontal,
horizontal
then
then
provide
provide
an
an
Item
item
Builder
builder
function
function
that
that
contains
contains
the
the
current
current
index
index
of
of
the
the
page
page
in
in
the
the
View.
view
Now,
now
because
because
we
we
can
can
have
have
an
an
infinite
infinite
number
number
of
of
pages
pages
in
in
the
the
Page
page
View,
view
I'll
i'll
go
go
ahead
ahead
and
and
create
create
a
a
new
new
stateless
stateless
widget
widget
called
called
an
an
Album
album
Card.
card
It
it
takes
takes
a
a
color
color
as
as
its
its
input
input
along
along
with
with
the
the
current
current
index
index
and
and
the
the
current
current
page.
page
Now,
now
you
you
might
might
be
be
wondering
wondering
why
why
we
we
need
need
the
the
current
current
page
page
and
and
the
the
current
current
index
index
because
because
intuitively,
intuitively
you
you
might
might
think
think
that
that
they
they
are
are
the
the
same
same
value.
value
And
and
while
while
they're
they're
almost
almost
the
the
same
same
value,
value
the
the
current
current
index
index
is
is
an
an
integer
integer
while
while
the
the
current
current
page
page
is
is
a
a
double.
double
As
as
you'll
you'll
see
see
in
in
just
just
a
a
second,
second
we
we
can
can
compare
compare
these
these
two
two
values
values
to
to
determine
determine
how
how
far
far
offset
offset
one
one
of
of
the
the
pages
pages
is
is
relative
relative
to
to
the
the
center.
center
And
and
we
we
can
can
use
use
that
that
relative
relative
position
position
to
to
create
create
some
some
cool
cool
three
three
d
d
transformations.
transformations
Now
now
let's
let's
go
go
ahead
ahead
and
and
create
create
the
the
album
album
card
card
stateless
stateless
widget.
widget
We'll
we'll
start
start
by
by
passing
passing
in
in
the
the
properties
properties
that
that
we
we
just
just
talked
talked
about.
about
Then
then
we'll
we'll
calculate
calculate
this
this
card's
card's
relative
relative
position
position
from
from
the
the
center
center
by
by
subtracting
subtracting
the
the
current
current
page
page
from
from
the
the
current
current
index.
index
If
if
the
the
value
value
is
is
negative,
negative
we
we
know
know
the
the
page
page
is
is
on
on
the
the
left
left
side.
side
If
if
it's
it's
Positive,
positive
we
we
know
know
it's
it's
on
on
the
the
right
right
side.
side
From
from
there,
there
we'll
we'll
set
set
up
up
a
a
container
container
with
with
a
a
child
child
of
of
a
a
Transform
transform
widget.
widget
Now
now
I
i
could
could
probably
probably
do
do
a
a
whole
whole
video
video
on
on
transforms,
transforms
so
so
don't
don't
worry
worry
if
if
this
this
part
part
feels
feels
a
a
little
little
overwhelming.
overwhelming
Our
our
transformation
transformation
starts
starts
as
as
a
a
4
4
by
by
4
4
matrix.
matrix
Basically,
basically
this
this
allows
allows
you
you
to
to
take
take
a
a
3
3
d
d
point
point
in
in
space
space
and
and
perform
perform
other
other
transformations
transformations
on
on
it
it
like
like
translation,
translation
rotation,
rotation
scale,
scale
and
and
so
so
on.
on
We
we
produce
produce
the
the
actual
actual
matrix
matrix
by
by
calling
calling
setEntry,
setentry
and
and
the
the
typical
typical
starting
starting
arguments
arguments
that
that
you'll
you'll
see
see
here
here
are
are
3,
3
2,
2
and
and
point
point
1.
1
But
but
feel
feel
free
free
to
to
play
play
around
around
with
with
these
these
to
to
get
get
different
different
effects.
effects
From
from
there,
there
we'll
we'll
change
change
the
the
scale
scale
or
or
the
the
size
size
of
of
the
the
widget.
widget
I
i
had
had
to
to
play
play
around
around
with
with
these
these
values
values
a
a
little
little
bit,
bit
but
but
I
i
found
found
it
it
looked
looked
best
best
when
when
I
i
clamp
clamp
the
the
values
values
to
to
range
range
between
between
0.6
0.6
and
and
1.0.
1.0
So
so
the
the
page
page
in
in
the
the
center
center
will
will
be
be
scaled
scaled
to
to
1,
1
but
but
the
the
pages
pages
on
on
the
the
sides
sides
will
will
be
be
scaled
scaled
to
to
around
around
0.6.
0.6
And
and
lastly,
lastly
we'll
we'll
add
add
a
a
transformation
transformation
to
to
rotate
rotate
around
around
the
the
y
y
axis
axis
based
based
on
on
the
the
relative
relative
position.
position
And
and
if
if
you
you
wanna
wanna
get
get
really
really
crazy
crazy
with
with
things,
things
also
also
go
go
ahead
ahead
and
and
add
add
a
a
rotation
rotation
to
to
the
the
z
z
axis.
axis
When
when
it
it
comes
comes
to
to
the
the
alignment
alignment
of
of
the
the
individual
individual
pages,
pages
we
we
want
want
the
the
items
items
on
on
the
the
right
right
side
side
to
to
be
be
aligned
aligned
center
center
left
left
and
and
the
the
items
items
on
on
the
the
left
left
side
side
to
to
be
be
aligned
aligned
center
center
right.
right
Then
then
everything
everything
below
below
that
that
is
is
just
just
your
your
basic
basic
UI.
ui
So
so
we
we
have
have
a
a
container
container
that
that
has
has
a
a
decoration
decoration
image
image
with
with
a
a
network
network
image
image
that
that
points
points
to
to
one
one
of
of
the
the
images
images
in
in
our
our
list.
list
If
if
we
we
fire
fire
up
up
our
our
app
app
at
at
this
this
point,
point
we
we
should
should
have
have
a
a
PageView
pageview
that
that
looks
looks
something
something
like
like
this.
this
When
when
we
we
pan
pan
across
across
the
the
view,
view
it
it
cycles
cycles
between
between
the
the
albums
albums
and
and
animates
animates
the
the
transformations
transformations
between
between
them.
them
Now
now
that
that
that's
that's
taken
taken
care
care
of,
of
we
we
can
can
move
move
on
on
to
to
the
the
fun
fun
part,
part
which
which
is
is
building
building
our
our
radial
radial
click
click
wheel.
wheel
It
it
will
will
take
take
the
the
page
page
controller
controller
that
that
we've
we've
already
already
built
built
and
and
then
then
allow
allow
the
the
user
user
to
to
pan
pan
either
either
clockwise
clockwise
or
or
counterclockwise
counterclockwise
to
to
control
control
the
the
position
position
of
of
the
the
page
page
view.
view
And
and
we'll
we'll
also
also
to
to
add
add
velocity
velocity
to
to
it
it
as
as
well,
well
so
so
if
if
the
the
user
user
pans
pans
faster,
faster
it
it
will
will
scroll
scroll
through
through
the
the
items
items
faster.
faster
Let's
let's
head
head
back
back
up
up
to
to
the
the
column
column
that
that
contains
contains
our
our
page
page
view
view
builder.
builder
Just
just
below
below
it,
it
we'll
we'll
go
go
ahead
ahead
and
and
add
add
a
a
centered
centered
widget
widget
that
that
contains
contains
a
a
child
child
of
of
a
a
stack.
stack
The
the
first
first
thing
thing
we'll
we'll
want
want
in
in
the
the
stack
stack
is
is
a
a
gesture
gesture
detector,
detector
and
and
the
the
gesture
gesture
we
we
care
care
about
about
is
is
on
on
Pan
pan
Update.
update
That
that
event
event
will
will
fire
fire
continuously
continuously
whenever
whenever
the
the
user
user
is
is
moving
moving
their
their
finger
finger
across
across
this
this
container.
container
For
for
now,
now
I'm
i'm
just
just
going
going
to
to
add
add
a
a
placeholder
placeholder
function
function
there
there
called
called
panhandler,
panhandler
and
and
we'll
we'll
implement
implement
that
that
in
in
just
just
a
a
minute.
minute
Then
then
we
we
can
can
create
create
a
a
circle
circle
by
by
setting
setting
up
up
a
a
container
container
with
with
a
a
width
width
and
and
a
a
height,
height
then
then
we'll
we'll
give
give
it
it
some
some
box
box
decoration,
decoration
where
where
the
the
shape
shape
is
is
box
box
shaped
shaped
circle.
circle
Now,
now
on
on
the
the
iPad,
ipad
you
you
have
have
more
more
of
of
a
a
donut
donut
shape
shape
or
or
a
a
circle
circle
within
within
a
a
circle.
circle
And
and
we
we
only
only
want
want
to
to
fire
fire
the
the
pan
pan
event
event
when
when
the
the
user
user
is
is
moving
moving
throughout
throughout
the
the
outside
outside
circle
circle
or
or
the
the
black
black
circle.
circle
If
if
they
they
pan
pan
across
across
the
the
inner
inner
gray
gray
circle,
circle
then
then
nothing
nothing
should
should
happen.
happen
Because
because
we're
we're
inside
inside
of
of
a
a
stack
stack
that's
that's
aligned
aligned
to
to
the
the
center,
center
we
we
can
can
simply
simply
add
add
another
another
container
container
that
that
also
also
has
has
a
a
box
box
shape
shape
of
of
circle.
circle
And
and
because
because
it
it
has
has
a
a
higher
higher
index
index
in
in
the
the
stack,
stack
It
it
will
will
sit
sit
on
on
top
top
of
of
the
the
gesture
gesture
detector
detector
and
and
won't
won't
allow
allow
the
the
events
events
to
to
get
get
through
through
when
when
it
it
is
is
panned
panned
over.
over
Now,
now
you'll
you'll
also
also
notice
notice
that
that
we
we
have
have
different
different
buttons
buttons
inside
inside
the
the
wheel
wheel
itself.
itself
I'm
i'm
not
not
going
going
to
to
implement
implement
all
all
of
of
these,
these
only
only
the
the
forward
forward
and
and
back
back
buttons.
buttons
These
these
buttons
buttons
also
also
sit
sit
inside
inside
their
their
own
own
stack,
stack
and
and
they're
they're
positioned
positioned
relative
relative
to
to
the
the
circle
circle
so
so
they
they
appear
appear
in
in
the
the
right
right
locations.
locations
The
the
ones
ones
that
that
are
are
actually
actually
pressable
pressable
are
are
set
set
up
up
as
as
an
an
icon
icon
button.
button
When
when
they're
they're
pressed,
pressed
they'll
they'll
take
take
the
the
page
page
controller
controller
and
and
then
then
animate
animate
to
to
the
the
current
current
page
page
plus
plus
1,
1
or
or
if
if
we're
we're
going
going
backwards,
backwards
the
the
current
current
page
page
minus
minus
1.
1
So
so
now
now
when
when
the
the
user
user
taps
taps
one
one
of
of
these
these
buttons,
buttons
it
it
will
will
animate
animate
to
to
the
the
next
next
page
page
or
or
the
the
previous
previous
page
page
and
and
give
give
us
us
a
a
nice
nice
looking
looking
animation
animation
in
in
the
the
process.
process
And
and
with
with
that,
that
we're
we're
ready
ready
to
to
move
move
on
on
to
to
the
the
final
final
challenge,
challenge
and
and
that's
that's
making
making
our
our
click
click
wheel
wheel
rotatable.
rotatable
Let's
let's
start
start
by
by
making
making
sure
sure
we
we
understand
understand
how
how
a
a
wheel
wheel
works.
works
We
we
can
can
break
break
a
a
wheel
wheel
up
up
into
into
different
different
quadrants.
quadrants
We
we
have
have
the
the
top
top
right,
right
the
the
bottom
bottom
right,
right
the
the
bottom
bottom
left,
left
and
and
the
the
top
top
left.
left
So
so
those
those
are
are
the
the
different
different
parts
parts
of
of
the
the
wheel,
wheel
but
but
then
then
the
the
user
user
can
can
actually
actually
pan
pan
with
with
their
their
finger
finger
in
in
4
4
different
different
directions
directions
as
as
well.
well
They
they
can
can
go
go
up,
up
down,
down
left,
left
or
or
right.
right
And
and
if
if
we
we
think
think
of
of
this
this
as
as
an
an
x
x
y
y
axis,
axis
then
then
we
we
know
know
that
that
different
different
movements
movements
or
or
positions
positions
are
are
mutually
mutually
exclusive.
exclusive
Like,
like
the
the
user
user
can't
can't
be
be
going
going
x
x
positive
positive
and
and
x
x
negative
negative
at
at
the
the
same
same
time.
time
Or
or
in
in
other
other
words,
words
the
the
user
user
can't
can't
move
move
left
left
and
and
right
right
at
at
the
the
same
same
time.
time
Now
now
our
our
Panhandler
panhandler
method
method
will
will
have
have
access
access
to
to
the
the
drag
drag
update
update
details
details
for
for
every
every
event,
event
and
and
that
that
event
event
contains
contains
things
things
like
like
the
the
delta,
delta
or
or
the
the
amount
amount
of
of
change
change
since
since
the
the
user's
user's
last
last
movement,
movement
as
as
well
well
as
as
the
the
local
local
position
position
of
of
that
that
user's
user's
movement
movement
on
on
the
the
widget
widget
itself.
itself
To
to
make
make
our
our
code
code
readable,
readable
I'm
i'm
going
going
to
to
go
go
ahead
ahead
and
and
set
set
up
up
variables
variables
for
for
all
all
these
these
different
different
values.
values
Since
since
our
our
circle
circle
widget
widget
has
has
a
a
fixed
fixed
width
width
of
of
300
300
pixels,
pixels
We'll
we'll
go
go
ahead
ahead
and
and
set
set
up
up
a
a
radius
radius
value
value
of
of
a
a
150.
150
We
we
can
can
determine
determine
where
where
the
the
user
user
is
is
dragging
dragging
by
by
looking
looking
at
at
the
the
local
local
position.
position
We
we
know
know
the
the
user
user
is
is
on
on
the
the
top
top
half
half
of
of
the
the
circle
circle
if
if
the
the
local
local
position
position
on
on
the
the
y
y
axis
axis
is
is
less
less
than
than
or
or
equal
equal
to
to
the
the
radius.
radius
We
we
know
know
they're
they're
on
on
the
the
left
left
side
side
if
if
the
the
local
local
position
position
x
x
axis
axis
is
is
less
less
than
than
or
or
equal
equal
to
to
the
the
radius.
radius
Now
now
we
we
know
know
if
if
the
the
user's
user's
on
on
top,
top
then
then
they
they
can't
can't
be
be
on
on
bottom,
bottom
or
or
if
if
they're
they're
on
on
the
the
left
left
side,
side
they
they
can't
can't
be
be
on
on
the
the
right
right
side.
side
So
so
we'll
we'll
grab
grab
those
those
values
values
by
by
simply
simply
grabbing
grabbing
the
the
opposite
opposite
of
of
their
their
counterpart.
counterpart
To
to
determine
determine
the
the
direction
direction
of
of
the
the
user's
user's
movement,
movement
we
we
can
can
look
look
at
at
the
the
delta
delta
on
on
the
the
event.
event
Delta
delta
is
is
just
just
a
a
fancy
fancy
word
word
for
for
change.
change
It
it
represents
represents
the
the
amount
amount
of
of
movement
movement
that
that
the
the
user
user
has
has
made
made
since
since
the
the
last
last
event.
event
In
in
this
this
case,
case
we
we
know
know
that
that
they're
they're
moving
moving
up
up
if
if
the
the
y
y
movement
movement
is
is
less
less
than
than
or
or
equal
equal
to
to
0,
0
or
or
they're
they're
moving
moving
left
left
if
if
the
the
x
x
movement
movement
is
is
less
less
than
than
or
or
equal
equal
to
to
0.
0
And
and
then
then
we
we
can
can
use
use
a
a
bang
bang
to
to
get
get
their
their
opposites
opposites
just
just
for
for
the
the
sake
sake
of
of
making
making
our
our
code
code
a
a
little
little
more
more
readable.
readable
Now
now
that
that
we
we
can
can
pinpoint
pinpoint
exactly
exactly
how
how
and
and
where
where
a
a
user
user
is
is
panning
panning
around,
around
we'll
we'll
go
go
ahead
ahead
and
and
set
set
up
up
values
values
for
for
the
the
X
x
change,
change
and
and
y
y
change
change
as
as
their
their
absolute
absolute
values.
values
And
and
now,
now
we
we
need
need
to
to
make
make
this
this
value
value
negative
negative
or
or
positive
positive
depending
depending
on
on
which
which
way
way
they're
they're
rotating
rotating
the
the
wheel.
wheel
Let's
let's
start
start
by
by
thinking
thinking
about
about
how
how
vertical
vertical
movement
movement
should
should
change
change
the
the
rotation
rotation
of
of
the
the
wheel.
wheel
If
if
they're
they're
on
on
the
the
right
right
side
side
and
and
panning
panning
down
down
or
or
on
on
the
the
left
left
side
side
and
and
panning
panning
up,
up
then
then
that
that
y
y
change
change
should
should
be
be
positive
positive
or
or
clockwise.
clockwise
Otherwise,
otherwise
it'll
it'll
be
be
negative
negative
or
or
counterclockwise.
counterclockwise
So
so
we
we
just
just
take
take
the
the
total
total
change
change
and
and
multiply
multiply
it
it
by
by
negative
negative
one.
one
Now
now
we'll
we'll
do
do
the
the
same
same
thing
thing
for
for
the
the
horizontal
horizontal
rotation.
rotation
If
if
we're
we're
on
on
top
top
and
and
panning
panning
right
right
or
or
on
on
the
the
bottom
bottom
and
and
panning
panning
left,
left
then
then
the
the
change
change
is
is
clockwise,
clockwise
otherwise
otherwise
counterclockwise.
counterclockwise
So
so
to
to
get
get
the
the
total
total
rotation
rotation
value,
value
all
all
we
we
have
have
to
to
do
do
is
is
add
add
these
these
two
two
values
values
together.
together
It's
it's
also
also
a
a
good
good
idea
idea
to
to
add
add
velocity,
velocity
so
so
if
if
the
the
user
user
is
is
panning
panning
faster,
faster
it
it
will
will
scroll
scroll
through
through
the
the
items
items
faster.
faster
And
and
we
we
can
can
do
do
that
that
by
by
multiplying
multiplying
by
by
the
the
delta
delta
distance.
distance
And
and
now
now
that
that
we
we
have
have
all
all
the
the
data
data
we
we
need,
need
We
we
just
just
need
need
to
to
use
use
our
our
page
page
controller
controller
to
to
jump
jump
to
to
a
a
new
new
position,
position
and
and
that
that
new
new
position
position
is
is
the
the
current
current
page
page
controller
controller
offset
offset
plus
plus
the
the
scroll
scroll
offset
offset
change
change
that
that
we
we
just
just
calculated.
calculated
And
and
that's
that's
all
all
there
there
is
is
to
to
it.
it
You
you
can
can
now
now
use
use
the
the
same
same
logic
logic
to
to
implement
implement
a
a
rotatable
rotatable
knob
knob
or
or
some
some
kind
kind
of
of
wheel,
wheel
or
or
any
any
other
other
type
type
of
of
radial
radial
UI
ui
widget
widget
that
that
the
the
user
user
has
has
control
control
over.
over
I'm
i'm
gonna
gonna
go
go
ahead
ahead
and
and
wrap
wrap
things
things
up
up
there.
there
If
if
this
this
video
video
helped
helped
you,
you
please
please
like
like
and
and
subscribe,
subscribe
and
and
consider
consider
becoming
becoming
a
a
pro
pro
member
member
at
at
Fireship
fireship
IO
io
to
to
get
get
access
access
to
to
the
the
full
full
Flutter
flutter
course,
course
along
along
with
with
a
a
bunch
bunch
of
of
other
other
advanced
advanced
content
content
for
for
members
members
only.
only
Thanks
thanks
for
for
watching
watching
and
and
I
i
will
will
talk
talk
to
to
you
you
soon.
soon
success
The speakers discuss various tasks they need to complete in building a Flutter UI app, including setting up a scrollable page view widget, creating a album card stateless widget, and breaking a wheel into different quadrants. They also explain the process of building a radial click wheel for a web app, including frame detector, gesture detector, and a circle widget. They emphasize the importance of understanding how a wheel works and provide examples of the process. They end with a recommendation to use a web app and encourage viewers to like and subscribe to their channel.
iPod Classic UI with Flutter
Over the last week or so, there's been this really awesome tweak going around that recreates the classic iPod using SwiftUI. It contains a click wheel that you can rotate around to change the position of the albums in the scrollable view above it. And if you've ever owned an iPod, it will definitely make you feel some nostalgia. As soon as I saw this tweet, I knew that I was going to have to build it in Flutter, and that's what this video is all about. If you're new here, like and subscribe, and grab the full source code from Buyership. Io. Let's start by taking a closer look at the app we're building in this video. Now, I realize that everybody is sick of Flutter UI challenges, but this Video is really all about the UI. We're not going to request access to the user's music or play actual audio files, but there are plenty of Flutter plugins out there if your goal is to build a full blown audio a player. Now, if we look at the top of the UI, you'll see that I have a scrollable Page View widget. It focuses an individual item in the center and then animates it into the view when it's moving. The off screen images are given some perspective, so they look 3 d, and then rotated. The rotation is calculated based on their position in the Page View, and that's how we get these cool animations when the scroll position changes. Now, moving down to the click wheel, we can go from one item to the next by clicking the forward or backwards buttons. But the real challenge with this demo was creating a click wheel that you can pan across either clockwise or counterclockwise and have the scroll view rotate in the proper direction. So when the wheel is rotating clockwise, it should move the page view to the right. When it's moving counterclockwise, it should move the page view back to the left. And we can do all this with just a regular gesture detector and some basic math. We don't need a custom painter or trigonometry or anything crazy like that. In fact, we don't need a single dependency for this project, so let's go ahead and jump into a Flutter app. The first thing you'll notice is that I have a list of image URLs to show the album covers, which is completely optional because those images sit on top of some colored containers, so you can omit them altogether if you want. Now, down in our main Material app, We'll go ahead and add a Scaffold, and then inside that Scaffold, we'll add our Ipod widget. The first thing I'll do is show you how to set up a Scrollable PageView. In our StatefulWidget, we'll first instantiate a Page Controller, and we'll set the Viewport Fraction to 0.6. This allows the next and previous page to overflow into the view, so you get a preview of what's coming next, which is similar to the classic Ipod UI. If you have a viewport fraction of 1, which is the default, it will fill up the entire view. The next thing we'll do is set up a stateful property called CurrentPage, which starts at 0. And the reason we have this as state on the widget is because we want to update the UI anytime the current page changes. And there's multiple ways the user can change that value, either by using the click wheel, by pressing a button, or by using the PageView directly. We can update that value whenever a change occurs by adding a listener to the Page Controller. So whenever the current page changes, it will trigger a re render on the component, which will be useful once we get to the animation part. Now let's move down to our build method. We'll start with a Safe Area widget that will prevent the PageView from interfering with the status bar at the top. And that's actually an optimization I got from my friend Mawoolie Joe on Twitter, so thanks for that. From there, we'll set up a column, and the first item in that column will be a container with a height of 300 and a black background. The child of that container will be a PageView Builder. The PageView Builder allows you to render all of the items in the PageView dynamically. It's similar to a virtual scroll feature on the web, where you're only actually rendering the items that are visible in the view, so it can handle an infinitely large collection. We'll pass it our Page Controller, then set the scroll direction to horizontal, then provide an Item Builder function that contains the current index of the page in the View. Now, because we can have an infinite number of pages in the Page View, I'll go ahead and create a new stateless widget called an Album Card. It takes a color as its input along with the current index and the current page. Now, you might be wondering why we need the current page and the current index because intuitively, you might think that they are the same value. And while they're almost the same value, the current index is an integer while the current page is a double. As you'll see in just a second, we can compare these two values to determine how far offset one of the pages is relative to the center. And we can use that relative position to create some cool three d transformations. Now let's go ahead and create the album card stateless widget. We'll start by passing in the properties that we just talked about. Then we'll calculate this card's relative position from the center by subtracting the current page from the current index. If the value is negative, we know the page is on the left side. If it's Positive, we know it's on the right side. From there, we'll set up a container with a child of a Transform widget. Now I could probably do a whole video on transforms, so don't worry if this part feels a little overwhelming. Our transformation starts as a 4 by 4 matrix. Basically, this allows you to take a 3 d point in space and perform other transformations on it like translation, rotation, scale, and so on. We produce the actual matrix by calling setEntry, and the typical starting arguments that you'll see here are 3, 2, and point 1. But feel free to play around with these to get different effects. From there, we'll change the scale or the size of the widget. I had to play around with these values a little bit, but I found it looked best when I clamp the values to range between 0.6 and 1.0. So the page in the center will be scaled to 1, but the pages on the sides will be scaled to around 0.6. And lastly, we'll add a transformation to rotate around the y axis based on the relative position. And if you wanna get really crazy with things, also go ahead and add a rotation to the z axis. When it comes to the alignment of the individual pages, we want the items on the right side to be aligned center left and the items on the left side to be aligned center right. Then everything below that is just your basic UI. So we have a container that has a decoration image with a network image that points to one of the images in our list. If we fire up our app at this point, we should have a PageView that looks something like this. When we pan across the view, it cycles between the albums and animates the transformations between them. Now that that's taken care of, we can move on to the fun part, which is building our radial click wheel. It will take the page controller that we've already built and then allow the user to pan either clockwise or counterclockwise to control the position of the page view. And we'll also to add velocity to it as well, so if the user pans faster, it will scroll through the items faster. Let's head back up to the column that contains our page view builder. Just below it, we'll go ahead and add a centered widget that contains a child of a stack. The first thing we'll want in the stack is a gesture detector, and the gesture we care about is on Pan Update. That event will fire continuously whenever the user is moving their finger across this container. For now, I'm just going to add a placeholder function there called panhandler, and we'll implement that in just a minute. Then we can create a circle by setting up a container with a width and a height, then we'll give it some box decoration, where the shape is box shaped circle. Now, on the iPad, you have more of a donut shape or a circle within a circle. And we only want to fire the pan event when the user is moving throughout the outside circle or the black circle. If they pan across the inner gray circle, then nothing should happen. Because we're inside of a stack that's aligned to the center, we can simply add another container that also has a box shape of circle. And because it has a higher index in the stack, It will sit on top of the gesture detector and won't allow the events to get through when it is panned over. Now, you'll also notice that we have different buttons inside the wheel itself. I'm not going to implement all of these, only the forward and back buttons. These buttons also sit inside their own stack, and they're positioned relative to the circle so they appear in the right locations. The ones that are actually pressable are set up as an icon button. When they're pressed, they'll take the page controller and then animate to the current page plus 1, or if we're going backwards, the current page minus 1. So now when the user taps one of these buttons, it will animate to the next page or the previous page and give us a nice looking animation in the process. And with that, we're ready to move on to the final challenge, and that's making our click wheel rotatable. Let's start by making sure we understand how a wheel works. We can break a wheel up into different quadrants. We have the top right, the bottom right, the bottom left, and the top left. So those are the different parts of the wheel, but then the user can actually pan with their finger in 4 different directions as well. They can go up, down, left, or right. And if we think of this as an x y axis, then we know that different movements or positions are mutually exclusive. Like, the user can't be going x positive and x negative at the same time. Or in other words, the user can't move left and right at the same time. Now our Panhandler method will have access to the drag update details for every event, and that event contains things like the delta, or the amount of change since the user's last movement, as well as the local position of that user's movement on the widget itself. To make our code readable, I'm going to go ahead and set up variables for all these different values. Since our circle widget has a fixed width of 300 pixels, We'll go ahead and set up a radius value of a 150. We can determine where the user is dragging by looking at the local position. We know the user is on the top half of the circle if the local position on the y axis is less than or equal to the radius. We know they're on the left side if the local position x axis is less than or equal to the radius. Now we know if the user's on top, then they can't be on bottom, or if they're on the left side, they can't be on the right side. So we'll grab those values by simply grabbing the opposite of their counterpart. To determine the direction of the user's movement, we can look at the delta on the event. Delta is just a fancy word for change. It represents the amount of movement that the user has made since the last event. In this case, we know that they're moving up if the y movement is less than or equal to 0, or they're moving left if the x movement is less than or equal to 0. And then we can use a bang to get their opposites just for the sake of making our code a little more readable. Now that we can pinpoint exactly how and where a user is panning around, we'll go ahead and set up values for the X change, and y change as their absolute values. And now, we need to make this value negative or positive depending on which way they're rotating the wheel. Let's start by thinking about how vertical movement should change the rotation of the wheel. If they're on the right side and panning down or on the left side and panning up, then that y change should be positive or clockwise. Otherwise, it'll be negative or counterclockwise. So we just take the total change and multiply it by negative one. Now we'll do the same thing for the horizontal rotation. If we're on top and panning right or on the bottom and panning left, then the change is clockwise, otherwise counterclockwise. So to get the total rotation value, all we have to do is add these two values together. It's also a good idea to add velocity, so if the user is panning faster, it will scroll through the items faster. And we can do that by multiplying by the delta distance. And now that we have all the data we need, We just need to use our page controller to jump to a new position, and that new position is the current page controller offset plus the scroll offset change that we just calculated. And that's all there is to it. You can now use the same logic to implement a rotatable knob or some kind of wheel, or any other type of radial UI widget that the user has control over. I'm gonna go ahead and wrap things up there. If this video helped you, please like and subscribe, and consider becoming a pro member at Fireship IO to get access to the full Flutter course, along with a bunch of other advanced content for members only. Thanks for watching and I will talk to you soon.
The speakers discuss various tasks they need to complete in building a Flutter UI app, including setting up a scrollable page view widget, creating a album card stateless widget, and breaking a wheel into different quadrants. They also explain the process of building a radial click wheel for a web app, including frame detector, gesture detector, and a circle widget. They emphasize the importance of understanding how a wheel works and provide examples of the process. They end with a recommendation to use a web app and encourage viewers to like and subscribe to their channel.

        [INST]
        You are youtuber called Fireship you make engaging high-intensity and entertaining coding tutorials and tech news. 
        you covers a wide range of topics relevant to programmers, aiming to help them learn and improve their skills quickly.
        
        Given the title of the video : iPod Classic UI with Flutter 
        and a small summary : The speakers discuss various tasks they need to complete in building a Flutter UI app, including setting up a scrollable page view widget, creating a album card stateless widget, and breaking a wheel into different quadrants. They also explain the process of building a radial click wheel for a web app, including frame detector, gesture detector, and a circle widget. They emphasize the importance of understanding how a wheel works and provide examples of the process. They end with a recommendation to use a web app and encourage viewers to like and subscribe to their channel.
        [/INST]
        
        Generate the video : Over the last week or so, there's been this really awesome tweak going around that recreates the classic iPod using SwiftUI. It contains a click wheel that you can rotate around to change the position of the albums in the scrollable view above it. And if you've ever owned an iPod, it will definitely make you feel some nostalgia. As soon as I saw this tweet, I knew that I was going to have to build it in Flutter, and that's what this video is all about. If you're new here, like and subscribe, and grab the full source code from Buyership. Io. Let's start by taking a closer look at the app we're building in this video. Now, I realize that everybody is sick of Flutter UI challenges, but this Video is really all about the UI. We're not going to request access to the user's music or play actual audio files, but there are plenty of Flutter plugins out there if your goal is to build a full blown audio a player. Now, if we look at the top of the UI, you'll see that I have a scrollable Page View widget. It focuses an individual item in the center and then animates it into the view when it's moving. The off screen images are given some perspective, so they look 3 d, and then rotated. The rotation is calculated based on their position in the Page View, and that's how we get these cool animations when the scroll position changes. Now, moving down to the click wheel, we can go from one item to the next by clicking the forward or backwards buttons. But the real challenge with this demo was creating a click wheel that you can pan across either clockwise or counterclockwise and have the scroll view rotate in the proper direction. So when the wheel is rotating clockwise, it should move the page view to the right. When it's moving counterclockwise, it should move the page view back to the left. And we can do all this with just a regular gesture detector and some basic math. We don't need a custom painter or trigonometry or anything crazy like that. In fact, we don't need a single dependency for this project, so let's go ahead and jump into a Flutter app. The first thing you'll notice is that I have a list of image URLs to show the album covers, which is completely optional because those images sit on top of some colored containers, so you can omit them altogether if you want. Now, down in our main Material app, We'll go ahead and add a Scaffold, and then inside that Scaffold, we'll add our Ipod widget. The first thing I'll do is show you how to set up a Scrollable PageView. In our StatefulWidget, we'll first instantiate a Page Controller, and we'll set the Viewport Fraction to 0.6. This allows the next and previous page to overflow into the view, so you get a preview of what's coming next, which is similar to the classic Ipod UI. If you have a viewport fraction of 1, which is the default, it will fill up the entire view. The next thing we'll do is set up a stateful property called CurrentPage, which starts at 0. And the reason we have this as state on the widget is because we want to update the UI anytime the current page changes. And there's multiple ways the user can change that value, either by using the click wheel, by pressing a button, or by using the PageView directly. We can update that value whenever a change occurs by adding a listener to the Page Controller. So whenever the current page changes, it will trigger a re render on the component, which will be useful once we get to the animation part. Now let's move down to our build method. We'll start with a Safe Area widget that will prevent the PageView from interfering with the status bar at the top. And that's actually an optimization I got from my friend Mawoolie Joe on Twitter, so thanks for that. From there, we'll set up a column, and the first item in that column will be a container with a height of 300 and a black background. The child of that container will be a PageView Builder. The PageView Builder allows you to render all of the items in the PageView dynamically. It's similar to a virtual scroll feature on the web, where you're only actually rendering the items that are visible in the view, so it can handle an infinitely large collection. We'll pass it our Page Controller, then set the scroll direction to horizontal, then provide an Item Builder function that contains the current index of the page in the View. Now, because we can have an infinite number of pages in the Page View, I'll go ahead and create a new stateless widget called an Album Card. It takes a color as its input along with the current index and the current page. Now, you might be wondering why we need the current page and the current index because intuitively, you might think that they are the same value. And while they're almost the same value, the current index is an integer while the current page is a double. As you'll see in just a second, we can compare these two values to determine how far offset one of the pages is relative to the center. And we can use that relative position to create some cool three d transformations. Now let's go ahead and create the album card stateless widget. We'll start by passing in the properties that we just talked about. Then we'll calculate this card's relative position from the center by subtracting the current page from the current index. If the value is negative, we know the page is on the left side. If it's Positive, we know it's on the right side. From there, we'll set up a container with a child of a Transform widget. Now I could probably do a whole video on transforms, so don't worry if this part feels a little overwhelming. Our transformation starts as a 4 by 4 matrix. Basically, this allows you to take a 3 d point in space and perform other transformations on it like translation, rotation, scale, and so on. We produce the actual matrix by calling setEntry, and the typical starting arguments that you'll see here are 3, 2, and point 1. But feel free to play around with these to get different effects. From there, we'll change the scale or the size of the widget. I had to play around with these values a little bit, but I found it looked best when I clamp the values to range between 0.6 and 1.0. So the page in the center will be scaled to 1, but the pages on the sides will be scaled to around 0.6. And lastly, we'll add a transformation to rotate around the y axis based on the relative position. And if you wanna get really crazy with things, also go ahead and add a rotation to the z axis. When it comes to the alignment of the individual pages, we want the items on the right side to be aligned center left and the items on the left side to be aligned center right. Then everything below that is just your basic UI. So we have a container that has a decoration image with a network image that points to one of the images in our list. If we fire up our app at this point, we should have a PageView that looks something like this. When we pan across the view, it cycles between the albums and animates the transformations between them. Now that that's taken care of, we can move on to the fun part, which is building our radial click wheel. It will take the page controller that we've already built and then allow the user to pan either clockwise or counterclockwise to control the position of the page view. And we'll also to add velocity to it as well, so if the user pans faster, it will scroll through the items faster. Let's head back up to the column that contains our page view builder. Just below it, we'll go ahead and add a centered widget that contains a child of a stack. The first thing we'll want in the stack is a gesture detector, and the gesture we care about is on Pan Update. That event will fire continuously whenever the user is moving their finger across this container. For now, I'm just going to add a placeholder function there called panhandler, and we'll implement that in just a minute. Then we can create a circle by setting up a container with a width and a height, then we'll give it some box decoration, where the shape is box shaped circle. Now, on the iPad, you have more of a donut shape or a circle within a circle. And we only want to fire the pan event when the user is moving throughout the outside circle or the black circle. If they pan across the inner gray circle, then nothing should happen. Because we're inside of a stack that's aligned to the center, we can simply add another container that also has a box shape of circle. And because it has a higher index in the stack, It will sit on top of the gesture detector and won't allow the events to get through when it is panned over. Now, you'll also notice that we have different buttons inside the wheel itself. I'm not going to implement all of these, only the forward and back buttons. These buttons also sit inside their own stack, and they're positioned relative to the circle so they appear in the right locations. The ones that are actually pressable are set up as an icon button. When they're pressed, they'll take the page controller and then animate to the current page plus 1, or if we're going backwards, the current page minus 1. So now when the user taps one of these buttons, it will animate to the next page or the previous page and give us a nice looking animation in the process. And with that, we're ready to move on to the final challenge, and that's making our click wheel rotatable. Let's start by making sure we understand how a wheel works. We can break a wheel up into different quadrants. We have the top right, the bottom right, the bottom left, and the top left. So those are the different parts of the wheel, but then the user can actually pan with their finger in 4 different directions as well. They can go up, down, left, or right. And if we think of this as an x y axis, then we know that different movements or positions are mutually exclusive. Like, the user can't be going x positive and x negative at the same time. Or in other words, the user can't move left and right at the same time. Now our Panhandler method will have access to the drag update details for every event, and that event contains things like the delta, or the amount of change since the user's last movement, as well as the local position of that user's movement on the widget itself. To make our code readable, I'm going to go ahead and set up variables for all these different values. Since our circle widget has a fixed width of 300 pixels, We'll go ahead and set up a radius value of a 150. We can determine where the user is dragging by looking at the local position. We know the user is on the top half of the circle if the local position on the y axis is less than or equal to the radius. We know they're on the left side if the local position x axis is less than or equal to the radius. Now we know if the user's on top, then they can't be on bottom, or if they're on the left side, they can't be on the right side. So we'll grab those values by simply grabbing the opposite of their counterpart. To determine the direction of the user's movement, we can look at the delta on the event. Delta is just a fancy word for change. It represents the amount of movement that the user has made since the last event. In this case, we know that they're moving up if the y movement is less than or equal to 0, or they're moving left if the x movement is less than or equal to 0. And then we can use a bang to get their opposites just for the sake of making our code a little more readable. Now that we can pinpoint exactly how and where a user is panning around, we'll go ahead and set up values for the X change, and y change as their absolute values. And now, we need to make this value negative or positive depending on which way they're rotating the wheel. Let's start by thinking about how vertical movement should change the rotation of the wheel. If they're on the right side and panning down or on the left side and panning up, then that y change should be positive or clockwise. Otherwise, it'll be negative or counterclockwise. So we just take the total change and multiply it by negative one. Now we'll do the same thing for the horizontal rotation. If we're on top and panning right or on the bottom and panning left, then the change is clockwise, otherwise counterclockwise. So to get the total rotation value, all we have to do is add these two values together. It's also a good idea to add velocity, so if the user is panning faster, it will scroll through the items faster. And we can do that by multiplying by the delta distance. And now that we have all the data we need, We just need to use our page controller to jump to a new position, and that new position is the current page controller offset plus the scroll offset change that we just calculated. And that's all there is to it. You can now use the same logic to implement a rotatable knob or some kind of wheel, or any other type of radial UI widget that the user has control over. I'm gonna go ahead and wrap things up there. If this video helped you, please like and subscribe, and consider becoming a pro member at Fireship IO to get access to the full Flutter course, along with a bunch of other advanced content for members only. Thanks for watching and I will talk to you soon.
        