Flutter music player app — part 1 : Using multi widgets

Flutter — a mobile App SDK developed by Google eases the way we’ve been developing the iOS and Android apps which used almost a single code base. It’s a new contestant in the world of SDKs with cross-platform in which DART is used as a Programming Language. So the other programming languages such as Kotlin and Swift are least bothered.

Let’s dive right into my music app. This is the small music player app designed to understand the basic flutter layouts, widgets and small animations.

The UI contains three major elements:

  • The Tabbar Nav Bar or Tab Bar
  • Scrollable albums
  • List View for songs.

Nav Bar or Tab Bar : Top level actions

As you can see in the gist below, I’ve created a new one new Widget called HomeTap to be the type of Scaffold. By default Scaffolds will come with appBar . So, we’ve added the TabBar to bottom of the AppBar and left the title as empty and made elevation as zero.

You have to add the TabBarView with the same number of children. Here, we’ve used, GridView to show the albums list in the Albums tab. _albums is the local data class that holds the album information. We’ve to iterate the _albums and pass it to our custom widget AlbumWidget .

Reason we are choosing the GridView that we’ve have to show the multiple albums in scrolls. Let’s go for the PageView. PageView gives more rich look to your music player by showing one album page. But our idea is to showcase the albums list on the horizontal drag of the albums.

Widget HomeTap = new Scaffold(

appBar: new AppBar(
bottom: new TabBar(

indicatorColor: Colors.pinkAccent[100],
labelColor: Colors.pinkAccent[100],
indicatorWeight: 0.5,
unselectedLabelColor: Colors.grey[400],
tabs: [
new Tab(text: 'Album',),
new Tab(text: 'Artist'),
new Tab(text: 'Playlist'),
new Tab(text: 'Songs'),
new Tab(icon: new Icon(Icons.search))
]),
title: null,
elevation: 0.0,
),
body: new TabBarView(
children: [
new Container(
child: new GridView.builder(
itemBuilder: (context, i){
return new AlbumWidget(_albums.elementAt(i));
},

gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
childAspectRatio: 1.7,
),
scrollDirection: Axis.horizontal,
itemCount: _albums.length,
shrinkWrap: true,
),
),

....,
....,
],
),
);

We are using SliverGridDelegateWithFixedCrossAxisCount as we need to show single list of albums horizontally. So our crossAxisCount is ONE. We dynamically initialize the itemCount as : _albums.length. Now our tab bar is ready to sit on the top of the screen.

Sorry wait!!!

We have to add the tab controller. Otherwise, tabs can’t be updated on the Scoffald’s body. So our HomeTap is not yet ready to sit on the bottom of the AppBar.

Widget build(BuildContext context) {

_initAlbum();

return new DefaultTabController(
length: 5,
child: HomeTap
);
}

We are using the DefaultTabController . We could’ve written our own custom tab controller. But no big deal in our case. Now we can see our HomeTab added as the child of the tab controller.

class HomeWidget extends StatefulWidget {

@override
createState() => new HomeState();
}

class HomeState extends State<HomeWidget> {

_initAlbum(){
......
}
  @override
Widget build(BuildContext context) {

_initAlbum();
return new DefaultTabController(
length: 5,
child: HomeTap
);
}


}

Album

Here comes the main part of the music player design. As you can see the pictures, we have our own album design with album’s cover image and name , number of the songs along with the ‘Mark as Favourite’ option.

class AlbumWidget extends StatefulWidget {

String album;

AlbumWidget(String album) {
this.album = album;
}

@override
createState() => new AlbumState(album);
}

class AlbumState extends State<AlbumWidget> {

String album;
bool liked = false;

double _height = 100.00;
double _width = 300.00;

AlbumState(String album) {
this.album = album;
}

The core of the album layout the top cover and the bottom info bar. Yeah! What you are guessing is right. We’ve chosen Column.. We have added one column with two children.

new Padding(
padding: const EdgeInsets.only(
top: 45.00, bottom: 45.00, left: 10.00),

child: new Column(children: <Widget>[
       .....
]

Now our top cover needs some decorations. We’ve just fixed the size of the container and leave the rest of the burdens to decoration .

As you can see, the properties shape and boxShadow are set based on our design needs. Notable thing is that the, borderRadius has been updated only for the top edges as our bottom info widget will stick to the bottom of this container.

Another thing is that we’ve added both LinearGradient and image, as some of albums might not have cover images.

new Container(

height: 300.00,
width: 300.00,
decoration: new BoxDecoration(

shape: BoxShape.rectangle,
boxShadow: [
new BoxShadow(
color: Colors.grey[400],
blurRadius: 2.0,
spreadRadius: 1.0,
offset: const Offset(1.0, 1.0)
)
],

gradient: new LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.grey[800], Colors.black]),
border: new Border.all(
color: Colors.grey[900],
width: 1.5
),
borderRadius: new BorderRadius.only(
topLeft: new Radius.circular(20.00),
topRight: new Radius.circular(20.00)),

image: new DecorationImage(
image: new NetworkImage(album),
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(
Colors.black, BlendMode.softLight)

),
),
),

For the bottom info bar also, we go for the same decorations with different colours and sizes.

We’ve added the ListTitle to show the Album title and subtitle.and subtitle and subtitle. And favourite icon is added to the trailing property of the list title.

new Container(
height: _height,
width: _width,
decoration: new BoxDecoration(

color: Colors.white,
shape: BoxShape.rectangle,
boxShadow: [
new BoxShadow(
color: Colors.grey[400],
blurRadius: 10.0,
spreadRadius: 2.0,
offset: const Offset(1.0, 1.0)
)
],

borderRadius: new BorderRadius.only(
bottomRight: new Radius.circular(20.00),
bottomLeft: new Radius.circular(20.00)),
),
child: new ListTile(
leading: new Icon(Icons.audiotrack),
title: new Text(.....),
subtitle: new Text(....),
trailing: new Row(
children: <Widget>[
new Icon(Icons.more_horiz, color: Colors.black,),
new Icon(
_liked ? Icons.favorite : Icons.favorite_border,
color: Colors.redAccent,),
],

),
),

),

_liked is used to show the flavour of the favorite. who will change the value of _liked ? So, we need to make some changes in our bottom info bar.

new GestureDetector(
onDoubleTap: () {
setState(() {
_liked = _liked ? false : true;
});
},

onTap: (){

Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
return new SongsListWidget(album);
})
);
},
child: new Container(

Our container has been moved to GestureDetector and hence the value of _liked will be changed on the onDoubleTap and the navigation to the SongsListWidget will take place with the onTap .

class SongsListWidget extends StatefulWidget {


String album;

SongsListWidget(String album) {
this.album = album;
}

@override
createState() => new SongsListState(album);
}

class SongsListState extends State<SongsListWidget> {

Set _songs = new Set();
String album;


SongsListState(String album) {
this.album = album;
}


_loadSongs() {
......
}

@override
Widget build(BuildContext context) {
_loadSongs();
return new Scaffold(

appBar: null,
body: new Padding(padding: const EdgeInsets.all(0.0),

child: new Column(
children: <Widget>[

new GestureDetector(

child: new Container(
......

In the songs list screen, we have a top cover and a songs list. So, a container with similar decoration will be added or reused for the top cover i.e first child of the column. And the second child is Expanded as the remaining screen will loaded with the songs list.

new Expanded(
child: new ListView.builder(
itemBuilder: (context, i) {

if (i< _songs.length) {
return new ListTile(
leading: new Image.network(album),
title: new Text(_songs.elementAt(i)),
subtitle: new Text("...."),
trailing: new Icon(Icons.more_vert),
);
}
}),
)

That’s all about initial design of the music player app, we’ll explore the animations and other designs and more functionalities of Flutter in later posts.

Thanks for Reading…!!!

esu

Like what you read? Give esu baggins a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.