Today’s article is one of my traditional UI tutorials, as well as a follow up to my Youtube Video that explains my approach to building UIs in Flutter.

As always you can find the whole code for this tutorial on GitHub: Source code.

Before getting started, go ahead and subscribe to my Youtube channel and follow me on Twitter.

If you want to support me, you can buy me a coffee.

The UI: Home Screen

In the home screen Scaffold, start by adding the BottomNavigationBar:

bottomNavigationBar: BottomNavigationBar( backgroundColor: Colors.transparent, elevation: 0, selectedItemColor: MyColors.accentColor, unselectedItemColor: Colors.black12, items: [ BottomNavigationBarItem( icon: Icon(Icons.home), title: Text("Home")), BottomNavigationBarItem( icon: Icon(Icons.person), title: Text("Profile")), BottomNavigationBarItem( icon: Icon(Icons.bookmark), title: Text("Bookmarked")), BottomNavigationBarItem( icon: Icon(Icons.shopping_basket), title: Text("Cart")), ], ),

Then, what you can do is write the basic widgets such as the Containers, Text Widgets, ListViews, and PageViews:

body: ListView( padding: const EdgeInsets.all(9), children: [ TextField( ), Text( "Discover", ), SizedBox( height: 51, child: MainCategoryItem(), ), SizedBox( height: MediaQuery.of(context).size.height / 3, child: PageView.builder( controller: PageController(viewportFraction: .75), itemCount: productsList.length, itemBuilder: (context, i) => GestureDetector( onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => DetailsScreen(id: i), ), ), child: FancyContainer(id: i), ), ), ), Row( children: [ IconButton( icon: Icon(Icons.tune), onPressed: () {}, ), Expanded( child: SizedBox( height: 51, child: Center( child: SubCategoryItem(), ), ), ), ], ), Text( "News", style: Theme.of(context).textTheme.title, ), ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: blogPosts.length, itemBuilder: (context, i) => ListTile( contentPadding: const EdgeInsets.all(9), leading: ClipRRect( child: Image.network( "${blogPosts[i].image}", fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(9), ), title: Text( "${blogPosts[i].title}", maxLines: 1, ), subtitle: Text( "${blogPosts[i].excerpt}", maxLines: 2, ), ), ) ], ),

Then use SizedBox widgets in order to separate between widgets.

After that, add the decoration to the TextField as well as the styles for the Text widgets:

TextField( decoration: InputDecoration( border: UnderlineInputBorder( borderSide: BorderSide(), ), prefixIcon: Icon(Icons.search), hintText: "Search for products...", ), ), /* other widgets */ Text( "Discover", style: Theme.of(context).textTheme.headline, ), /* other widgets */ Text( "News", style: Theme.of(context).textTheme.title, ),

Now is the right time to build your custom widgets.

Fancy Container:

import 'package:boat_shop/global.dart'; import 'package:flutter/material.dart'; class FancyContainer extends StatefulWidget { final int id; const FancyContainer({Key key, this.id}) : super(key: key); @override _FancyContainerState createState() => _FancyContainerState(); } class _FancyContainerState extends State { @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 15,vertical: 11), child: Stack( children: [ Positioned.fill( top: 50, child: Container( decoration: BoxDecoration( color: MyColors.mainColor, borderRadius: BorderRadius.circular(9), boxShadow: [ BoxShadow( color: MyColors.mainColor, offset: Offset(1, 1), blurRadius: 5, ), ], ), ), ), Positioned.fill( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Image.network( "${productsList[widget.id].image}", ), ), Padding( padding: const EdgeInsets.all(15), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${productsList[widget.id].name}", style: Theme.of(context).textTheme.subhead, ), Text( "${productsList[widget.id].price}", ), ], ), ), GestureDetector( onTap: () {}, child: Container( padding: const EdgeInsets.all(5.0), decoration: BoxDecoration( color: MyColors.accentColor, shape: BoxShape.circle, ), child: Icon( Icons.favorite, color: Colors.white, ), ), ) ], ), ) ], ), ), ], ), ); } }

SubCategoryItem:

import 'package:boat_shop/global.dart'; import 'package:flutter/material.dart'; class MainCategoryItem extends StatefulWidget { const MainCategoryItem({ Key key, }) : super(key: key); @override _MainCategoryItemState createState() => _MainCategoryItemState(); } class _MainCategoryItemState extends State { int _active = 0; @override Widget build(BuildContext context) { return ListView.builder( scrollDirection: Axis.horizontal, itemCount: mainCategories.length, itemBuilder: (context, i) => GestureDetector( onTap: () { setState(() { _active = i; }); }, child: Container( margin: const EdgeInsets.only(right: 15), child: Column( children: [ Text( "${mainCategories[i].name}", style: Theme.of(context).textTheme.button.copyWith( color: _active == i ? Colors.black : Colors.black87), ), _active == i ? Container( margin: const EdgeInsets.only(top: 5), height: 1.5, width: 51, decoration: BoxDecoration( color: MyColors.accentColor, borderRadius: BorderRadius.circular(5), ), ) : Container(), ], ), ), ), ); } }

MainCategoryItem:

import 'package:boat_shop/global.dart'; import 'package:flutter/material.dart'; class SubCategoryItem extends StatefulWidget { const SubCategoryItem({ Key key, }) : super(key: key); @override _SubCategoryItemState createState() => _SubCategoryItemState(); } class _SubCategoryItemState extends State { int _active = 0; @override Widget build(BuildContext context) { return ListView.builder( scrollDirection: Axis.horizontal, itemCount: subCategories.length, itemBuilder: (context, i) => Center( child: GestureDetector( onTap: (){ setState(() { _active = i; }); }, child: Container( margin: const EdgeInsets.only(right: 11), decoration: BoxDecoration( color: _active == i ? MyColors.accentColor : Colors.grey.withOpacity(.3), borderRadius: BorderRadius.circular(9), ), padding: const EdgeInsets.all(9), child: Text( "${subCategories[i].name}", style: Theme.of(context).textTheme.button.copyWith( color: _active == i ? Colors.white : Colors.blueGrey, ), ), ), ), ), ); } }

Product Screen:

This screen’s Scaffold has an AppBar, so start with it and make sure to add the two IconButton buttons as actions:

appBar: AppBar( iconTheme: IconThemeData(color: Colors.black45), backgroundColor: Colors.white, elevation: 0, actions: [ IconButton( icon: Icon(Icons.search), onPressed: () {}, ), IconButton( icon: Icon(Icons.shopping_basket), onPressed: () {}, ), ], ),

Inside the Body of the Scaffold start with an Expanded widget, then a GestureDetector that would serve as the buy now button:

GestureDetector( onTap: () => Navigator.pushNamed(context, 'cart'), child: Container( padding: const EdgeInsets.all(9), margin: const EdgeInsets.all(9), decoration: BoxDecoration( color: MyColors.accentColor, borderRadius: BorderRadius.circular(9), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Buy now", style: Theme.of(context) .textTheme .button .copyWith(color: Colors.white), ), Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(9), ), child: Icon(Icons.chevron_right), ) ], ), ), )

Inside the Expanded widget, you can add a ListView that will contain the product information and Image.network.

My Cart:

Finally, the Cart screen which is probably the easiest one in this app starts with the same app bar as the product’s screen.

Again the body of this screen is a Column. However, this time the first widget is a Text widget inside of a Padding widget. Then you can add an Expanded Container, give it a nice color and rounded top corners using the BorderRadius decoration.

Expanded( child: Container( padding: const EdgeInsets.all(9.0), decoration: BoxDecoration( color: MyColors.mainColor, borderRadius: BorderRadius.only( topRight: Radius.circular(25), topLeft: Radius.circular(25), ), child: Column(children: []), ), ), ),

Inside of the Container’s Column you can put an Expanded ListView.builder that will return all of the products your clients added to there cart. These products are represented by a ListTile.

Finally, after that add a Divider, then a Row showing the Subtotal Cost and lastly the same button that we used in the previous screen:

Divider(), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text("Subtotal (3 Items)"), SizedBox(width: 15), Text( "\$35,900", style: Theme.of(context).textTheme.title, ), ], ), GestureDetector( onTap: () => {}, child: Container( padding: const EdgeInsets.all(9), margin: const EdgeInsets.all(9), decoration: BoxDecoration( color: MyColors.accentColor, borderRadius: BorderRadius.circular(9), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Check out", style: Theme.of(context) .textTheme .button .copyWith(color: Colors.white), ), Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(9), ), child: Icon(Icons.chevron_right), ) ], ), ), )

Wrapping Up:

Please let me know in the comments or on Twitter if you want me to cover more stuff like this. Also, make sure to subscribe to my Youtube Channel where great content is coming.

If you like my work you can support me by buying a cup of coffee at BuyMeACoffee. Until next time, keep coding!