This tutorial will explore how to use a Listview in a master-detail scenario. This is a context where whereby we list data in a listview and when a single item is clicked we open a new screen or page to show the details of that item.
In that case the master is the list of items in our listview while the detail is the detail page. We will look at several examples of such type of project.
Example 1: Flutter Recipe App - Master Detail
This project is a recipe app template that teaches master detail in flutter. The master page comprises a list of recipes in a listview as well as a bottomNavigationBar widget. The detail page covers a single a recipe in detail.
Here are the screenshot demos for the project:
Here is the master page:
And here is the detail page:
Step 1: Create Project
Start by creating an empty Android Studio
project.
Step 2: Dependencies
We will need some basic widgets as dependencies. Declare them in your pubspec.yaml
as follows:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
intl: ^0.16.1
percent_indicator: ^2.1.1
Then flutter pub get
to fetch them.
Step 3: Create Meal Model
Next you need to create a model class to represent a single alongside it's properties like name, image, calories count, preparation as well as ingredients:
/model/meal.dart
class Meal {
final String mealTime, name, imagePath, kiloCaloriesBurnt, timeTaken;
final String preparation;
final List ingredients;
Meal({this.mealTime, this.name, this.imagePath, this.kiloCaloriesBurnt, this.timeTaken, this.preparation, this.ingredients});
}
List<Meal> meals = [
Meal(
mealTime: "BREAKFAST",
name: "Fruit Granola",
kiloCaloriesBurnt: "271",
timeTaken: "10",
imagePath: "assets/fruit_granola.jpg",
ingredients: [
"1 cup of granola",
"1 banana",
"1/2 cup of raisins",
"1 tbsp of honey",
],
preparation:
'''Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do nunc sed id. Mauris ultrices eros in cursus turpis.'''),
Meal(
mealTime: "DINNER",
name: "Pesto Pasta",
kiloCaloriesBurnt: "612",
timeTaken: "15",
imagePath: "assets/pesto_pasta.jpg",
ingredients: [
"1 cup of granola",
"1 banana",
"1/2 cup of raisins",
"1 tbsp of honey",
],
preparation:
'''Lorem ipsum dolor sit amet, c purus id. Mauris ultrices eros in cursus turpis.'''),
Meal(
mealTime: "SNACK",
name: "Keto Snack",
kiloCaloriesBurnt: "414",
timeTaken: "16",
imagePath: "assets/keto_snack.jpg",
ingredients: [
"1 cup of granola",
"1 banana",
"1/2 cup of raisins",
"1 tbsp of honey",
],
preparation:
'''Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do nunc sed id. Mauris ultrices eros in cursus turpis.'''),
];
Step 4: Create Details page
You now need to create a detail page for our app. In this case it will be created a statelesswidget;
meal_details.dart
import 'package:flutter/material.dart';
import 'main.dart';
import 'model/meal.dart';
class MealDetail extends StatelessWidget {
final Meal meal;
const MealDetail({Key key, this.meal}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: ListView(
children: <Widget>[
Stack(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width - 70,
decoration: BoxDecoration(
borderRadius:
BorderRadius.vertical(bottom: Radius.circular(30)),
boxShadow: [
BoxShadow(
offset: Offset(0, 2),
color: Colors.black54,
blurRadius: 6)
]),
child: ClipRRect(
borderRadius:
BorderRadius.vertical(bottom: Radius.circular(30)),
child: Image(
image: AssetImage(meal.imagePath),
fit: BoxFit.cover,
),
)),
Positioned(
top: 0,
left: 0,
child: IconButton(
icon: Icon(Icons.arrow_back),
iconSize: 25,
color: Colors.black,
onPressed: () {
Navigator.pop(context);
},
),
),
],
),
SizedBox(height: 8),
ListTile(
title: Text(meal.mealTime,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
color: Colors.black)),
subtitle: Text(
meal.name,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w800,
color: Colors.black87),
),
trailing: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// SizedBox(
// width: 30,
// ),
Text(
"${meal.kiloCaloriesBurnt} kcal",
style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600, fontSize: 15),
),
],
),
SizedBox(
height: 4,
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.access_time,
color: Colors.grey,
size: 20,
),
SizedBox(
width: 5,
),
Text(
"${meal.timeTaken} mins",
style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600, fontSize: 14),
)
],
),
],
),
),
SizedBox(height: 8,),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
"INGREDIENTS :",
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 15,
color: Colors.blueGrey,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(meal.ingredients[0]),
Text(meal.ingredients[1]),
Text(meal.ingredients[2]),
Text(meal.ingredients[3]),
],
),
),
SizedBox(height: 10,),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
"PREPARATION :",
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 15,
color: Colors.blueGrey,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 16,right: 16,bottom: 20),
child: Text(
meal.preparation,
style: TextStyle(
fontSize: 15,
),
),
)
],
),
);
}
}
Step 4: Create Main class
This class will be responsible for constructing the master page with the listview as well as BottomNavigationBar.
Create it as follows:
main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutterui/meal_details.dart';
import 'package:vector_math/vector_math_64.dart' as math;
import 'model/meal.dart';
import 'package:intl/intl.dart';
import 'package:percent_indicator/percent_indicator.dart';
void main() {
runApp(MaterialApp(
title: "flutter demo",
debugShowCheckedModeBanner: false,
home: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
final heightOfMedia = MediaQuery.of(context).size.height;
final widthOfMedia = MediaQuery.of(context).size.width;
final today = DateTime.now();
return Scaffold(
backgroundColor: Colors.grey[300],
bottomNavigationBar: ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(25)),
child: BottomNavigationBar(
iconSize: 22,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("Home")),
BottomNavigationBarItem(
icon: Icon(Icons.dashboard), title: Text("dashboard")),
BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text("profile"))
],
),
),
body: Stack(
children: <Widget>[
Positioned(
top: 0,
height: heightOfMedia * 0.35,
left: 0,
right: 0,
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
bottom: const Radius.circular(30)),
child: Container(
padding:
EdgeInsets.only(top: 33, left: 17, bottom: 5, right: 15),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
title: Text("${DateFormat("EEEE").format(today)},${DateFormat("d MMMM").format(today)} ",
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
color: Colors.black)),
subtitle: Text(
"Hello Saif",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
color: Colors.black87),
),
trailing:
ClipOval(child: Image(
image: AssetImage("assets/saif.jpg"),
fit: BoxFit.contain,
)),
),
Row(
children: <Widget>[
_RadialProgress(
width: widthOfMedia * 0.35,
height: widthOfMedia * 0.35,
progress:0.7),
SizedBox(
width: 12.5,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start
,children: <Widget>[
Padding(
padding: const EdgeInsets.only(left:8.0),
child: Text("Protein"),
),
Row(
children: <Widget>[
LinearPercentIndicator(
width: widthOfMedia*0.37,
animation: true,
progressColor: Colors.blue,
lineHeight: 10,
percent: 0.7,
),
Text("30 % left",style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w100
),)
],
)
],
),
),
],
),
SizedBox(
height: 13,
),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start
,children: <Widget>[
Padding(
padding: const EdgeInsets.only(left:8.0),
child: Text("Carbs"),
),
Row(
children: <Widget>[
LinearPercentIndicator(
width: widthOfMedia*0.37,
animation: true,
progressColor: Colors.yellow,
lineHeight: 10,
percent: 0.4,
),
Text("60 % left",style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w100
),)
],
)
],
),
),
SizedBox(
height: 13,
),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left:8.0),
child: Text("Fat"),
),
Row(
children: <Widget>[
LinearPercentIndicator(
width: widthOfMedia*0.38,
animation: true,
progressColor: Colors.red,
lineHeight: 10,
percent: 0.5,
),
Text("50 % left",style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w100
),)
],
)
],
),
)
],
)
],
)
],
),
),
),
),
Positioned(
top: heightOfMedia * 0.37,
left: 0,
right: 0,
child: Container(
height: heightOfMedia * 0.55,
//color: Colors.grey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 25, bottom: 4),
child: Text(
"Meals",
style: TextStyle(fontSize: 19, color: Colors.black87),
),
),
Expanded(
child: meal_card(),
),
SizedBox(
height: 8,
),
Expanded(
child: Container(
margin: EdgeInsets.only(left: 15, right: 15, bottom: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
gradient: LinearGradient(
colors: [Color(0xFF9CCC65), Color(0xFF558B2F)])),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 15,
),
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 5),
child: Text(
"Home Workout",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white),
),
),
Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(
"Upper Body",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
color: Colors.white),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Image.asset("assets/chest.png"),
color: Colors.blue,
iconSize: 65,
),
IconButton(
icon: Image.asset("assets/back.png"),
color: Colors.blue,
iconSize: 65,
),
IconButton(
icon: Image.asset("assets/biceps.png"),
color: Colors.blue,
iconSize: 65,
)
],
)
],
),
),
)
],
),
),
)
],
),
);
}
}
class _LinearProgress extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
class _RadialProgress extends StatelessWidget {
final double height, width,progress;
const _RadialProgress({Key key, this.height, this.width,this.progress}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return CustomPaint(
painter: _RadialPaint(progress: 0.7),
child: Container(
height: height,
width: width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("30 %",style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 21,
color: Colors.black87
),),
Text("Left",style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 16,
color: Colors.black54
),)
],
),
),
);
}
}
class _RadialPaint extends CustomPainter {
final double progress;
_RadialPaint({this.progress});
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
Paint paint = Paint()
..strokeWidth = 10
..color = Color(0xFF558B2F)
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
Offset centre = Offset(size.width / 2, size.height / 2);
// canvas.drawCircle(centre, size.width / 2, paint);
double relativeProgress=360*progress;
canvas.drawArc(Rect.fromCircle(center: centre, radius: size.width / 2),
math.radians(-90), math.radians(-relativeProgress), false, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class meal_card extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
margin: EdgeInsets.only(left: 20),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: meals.length,
itemBuilder: (BuildContext context, int index) {
Meal meal = meals[index];
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context)=>MealDetail(meal: meal,)));
},
child: Container(
margin: EdgeInsets.only(right: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
//mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
fit: FlexFit.tight,
child: ClipRRect(
borderRadius:
BorderRadius.vertical(top: Radius.circular(20)),
child: Image.asset(meal.imagePath,
width: 150, fit: BoxFit.cover),
),
),
Flexible(
fit: FlexFit.tight,
child: Padding(
padding: const EdgeInsets.only(left: 13),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(
height: 5,
),
Text(meal.mealTime,
style: TextStyle(color: Colors.grey, fontSize: 13)),
Text(meal.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87)),
Text("${meal.kiloCaloriesBurnt} kcal",
style: TextStyle(
color: Colors.blueGrey, fontSize: 13)),
Row(
children: <Widget>[
Icon(
Icons.timer,
size: 17,
color: Colors.grey,
),
Text("${meal.timeTaken} min",
style: TextStyle(
color: Colors.blueGrey, fontSize: 13)),
],
),
SizedBox(height: 5)
],
),
),
)
],
),
),
);
},
),
);
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
Example 2: Flutter Moveis App - Listview Master Detail
This is yet another beautiful app that teaches you implementation of Master Detail with ListView in flutter. The master page will list the movies while when a single movie item in our listview is clicked we open the detail page where we render the details of that movie.
Here are the demo in screenshots in iOS:
Here is the master page:
and here is the detail page:
Step 1: Create Project
Start by creating an empty flutter project.
Step 2: Dependencies
No third party dependency is needed.
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
Step 3: Create Helper classes
Start by creating helper classes and files such as:
(a). /constant/Constant.dart
String SPLASH_SCREEN='SPLASH_SCREEN';
String HOME_SCREEN='HOME_SCREEN';
String ITEM_DETAILS_SCREEN='ITEM_DETAILS_SCREEN';
String LISTVIEW_ITEM_DETAILS_SCREEN='LISTVIEW_ITEM_DETAILS_SCREEN';
Step 4: Create Model class
Create the model class that will represent a single Movie:
/model/Item.dart
import 'package:meta/meta.dart';
class Item {
int id;
String name;
String category;
String releaseDate;
String releaseDateDesc;
String directors;
String runtime;
String desc;
double rating;
String imageUrl;
String bannerUrl;
String trailerImg1;
String trailerImg2;
String trailerImg3;
Item({
@required this.id,
@required this.name,
@required this.category,
@required this.directors,
@required this.releaseDate,
@required this.releaseDateDesc,
@required this.runtime,
@required this.desc,
@required this.rating,
@required this.imageUrl,
@required this.bannerUrl,
@required this.trailerImg1,
@required this.trailerImg2,
@required this.trailerImg3,
});
}
Step 5: Create Screens
Create screens as follows:
(a). /screen/SplashScreen.dart
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_listview_app/constant/Constant.dart';
class SplashScreen extends StatefulWidget {
@override
SplashScreenState createState() => new SplashScreenState();
}
class SplashScreenState extends State<SplashScreen>
with SingleTickerProviderStateMixin {
var _visible = true;
AnimationController animationController;
Animation<double> animation;
startTime() async {
var _duration = new Duration(seconds: 3);
return new Timer(_duration, navigationPage);
}
void navigationPage() {
Navigator.of(context).pushReplacementNamed(HOME_SCREEN);
}
@override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this,
duration: new Duration(seconds: 2),
);
animation =
new CurvedAnimation(parent: animationController, curve: Curves.easeOut);
animation.addListener(() => this.setState(() {}));
animationController.forward();
setState(() {
_visible = !_visible;
});
startTime();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 30.0),
child: new Image.asset(
'assets/images/powered_by.png',
height: 25.0,
fit: BoxFit.scaleDown,
),
)
],
),
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Image.asset(
'assets/images/logo.png',
width: animation.value * 250,
height: animation.value * 250,
),
],
),
],
),
);
}
}
(b). /screen/GetRatings.dart
import 'package:flutter/material.dart';
class GetRatings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 2.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
Icons.star,
size: 15.0,
color: Colors.yellow,
),
Icon(
Icons.star,
size: 15.0,
color: Colors.yellow,
),
Icon(
Icons.star,
size: 15.0,
color: Colors.yellow,
),
Icon(
Icons.star,
size: 15.0,
color: Colors.yellow,
),
Icon(
Icons.star_half,
size: 15.0,
color: Colors.yellow,
),
],
),
);
}
}
(c). /screen/ListViewItemDetails.dart
import 'package:flutter/material.dart';
import 'package:flutter_listview_app/model/Item.dart';
import 'package:flutter_listview_app/screen/GetRatings.dart';
class ListItemDetails extends StatelessWidget {
final Item item;
ListItemDetails(this.item);
@override
Widget build(BuildContext context) {
return Scaffold(
primary: true,
appBar: AppBar(
title: Text(item.name),
),
backgroundColor: Color(0xFF761322),
body: ListView(
children: <Widget>[
HeaderBanner(this.item),
GetTags(),
Container(
padding: const EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 20.0),
child: Text(
item.desc,
style: TextStyle(
fontSize: 13.0,
color: Colors.white,
),
),
),
InkWell(
onTap: () => {},
child: Container(
margin: EdgeInsets.fromLTRB(50.0, 5.0, 50.0, 5.0),
width: 80.0,
height: 40.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30.0),
),
child: Center(
child: Text(
'Watch Movies',
style: TextStyle(
fontSize: 18.0,
color: Color(0xFF761322),
fontWeight: FontWeight.bold,
),
),
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(10.0, 20.0, 10.0, 10.0),
child: Text(
'Trailers',
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
),
),
),
GetTrailers(this.item),
],
// ),
//],
),
);
}
}
class GetTags extends StatelessWidget {
GetTags();
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(5.0, 0.0, 5.0, 10.0),
height: 35.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
InkWell(
onTap: () => {},
child: Container(
width: 100.0,
height: 35.0,
margin: EdgeInsets.only(
left: 5.0,
right: 5.0,
),
decoration: BoxDecoration(
color: Color(0xFF761322),
border: Border.all(color: Colors.white, width: 1.0),
borderRadius: BorderRadius.circular(30.0),
),
child: Center(
child: Text(
'Action',
style: TextStyle(fontSize: 16.0, color: Colors.white),
),
),
),
),
InkWell(
onTap: () => {},
child: Container(
width: 100.0,
height: 35.0,
margin: EdgeInsets.only(
left: 5.0,
right: 5.0,
),
decoration: BoxDecoration(
color: Color(0xFF761322),
border: Border.all(color: Colors.white, width: 1.0),
borderRadius: BorderRadius.circular(30.0),
),
child: Center(
child: Text(
'Adventure',
style: TextStyle(fontSize: 16.0, color: Colors.white),
),
),
),
),
InkWell(
onTap: () => {},
child: Container(
width: 100.0,
height: 35.0,
margin: EdgeInsets.only(
left: 5.0,
right: 5.0,
),
decoration: BoxDecoration(
color: Color(0xFF761322),
border: Border.all(color: Colors.white, width: 1.0),
borderRadius: BorderRadius.circular(30.0),
),
child: Center(
child: Text(
'Fantasy',
style: TextStyle(fontSize: 16.0, color: Colors.white),
),
),
),
),
],
),
);
}
}
class SetTagsItem extends StatelessWidget {
final String tag;
SetTagsItem(this.tag);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => {},
child: Container(
width: 100.0,
height: 35.0,
margin: EdgeInsets.only(
left: 5.0,
right: 5.0,
),
decoration: BoxDecoration(
color: Color(0xFF761322),
border: Border.all(color: Colors.white, width: 1.0),
borderRadius: BorderRadius.circular(30.0),
),
child: Center(
child: Text(
tag,
style: TextStyle(fontSize: 16.0, color: Colors.white),
),
),
),
);
}
}
class HeaderBanner extends StatelessWidget {
final Item item;
HeaderBanner(this.item);
@override
Widget build(BuildContext context) {
return Material(
elevation: 0.0,
child: Container(
height: 380.0,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
HeaderImage(this.item.bannerUrl),
HeaderContent(this.item),
],
),
),
);
}
}
class HeaderImage extends StatelessWidget {
final String bannerUrl;
HeaderImage(this.bannerUrl);
@override
Widget build(BuildContext context) {
return Image.asset(
bannerUrl,
width: 600.0,
height: 380.0,
fit: BoxFit.cover,
);
}
}
class HeaderContent extends StatelessWidget {
final Item item;
HeaderContent(this.item);
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomLeft,
child: Container(
//color: Colors.black.withOpacity(0.1),
constraints: BoxConstraints.expand(
height: 110.0,
),
child: Padding(
padding: const EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0),
child: Container(
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(bottom: 1.0),
child: Text(
item.name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 26.0,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
GetRatings(),
Container(
margin: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
child: Text(
item.directors,
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
),
),
),
Container(
//margin: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
child: Text(
item.releaseDateDesc,
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
),
),
),
],
),
),
],
),
),
//child:
),
),
);
}
}
class GetTrailers extends StatelessWidget {
final Item item;
GetTrailers(this.item);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 10.0),
height: 100.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
child: Image.asset(
item.trailerImg1,
width: 160.0,
height: 100.0,
fit: BoxFit.cover,
),
),
Container(
padding: const EdgeInsets.only(left: 5.0),
child: Image.asset(
item.trailerImg2,
width: 160.0,
height: 100.0,
fit: BoxFit.cover,
),
),
Container(
padding: const EdgeInsets.only(left: 5.0),
child: Image.asset(
item.trailerImg3,
width: 160.0,
height: 100.0,
fit: BoxFit.cover,
),
),
],
),
);
}
}
(d). /screen/ItemList.dart
import 'package:flutter/material.dart';
import 'package:flutter_listview_app/screen/GetRatings.dart';
import 'package:flutter_listview_app/screen/ListViewItemDetails.dart';
import 'package:flutter_listview_app/model/Item.dart';
class ItemList extends StatelessWidget {
final Item item;
const ItemList({@required this.item});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListItemDetails(this.item),
),
);
},
child: Card(
elevation: 1.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Container(
padding: EdgeInsets.all(8.0),
child: Row(
//crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Image.asset(
item.imageUrl,
height: 120.0,
width: 120.0,
fit: BoxFit.fitHeight,
),
Flexible(
//padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 12.0,
color: Color(0xFFD73C29),
fontWeight: FontWeight.bold,
),
),
Text(
item.category,
style: TextStyle(
color: Colors.black54,
fontSize: 9.0,
),
),
SizedBox(height: 0.0),
GetRatings(),
SizedBox(height: 2.0),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 4.0),
child: Column(
children: <Widget>[
Text(
'RELEASE DATE:',
style: TextStyle(
color: Colors.black38,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
Text(
item.releaseDate,
style: TextStyle(
color: Colors.black,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
Container(
margin: EdgeInsets.only(left: 4.0),
child: Column(
children: <Widget>[
Text(
'RUNTIME:',
style: TextStyle(
color: Colors.black38,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
Text(
item.runtime,
style: TextStyle(
color: Colors.black,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
],
),
),
),
],
),
),
),
);
}
}
class HeaderContent extends StatelessWidget {
final Item item;
HeaderContent(this.item);
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomLeft,
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.only(left: 10.0, top: 5.0, right: 10.0),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: TextStyle(
fontSize: 12.0,
color: Color(0xFFD73C29),
fontWeight: FontWeight.bold,
),
),
Text(
item.category,
style: TextStyle(
color: Colors.black54,
fontSize: 9.0,
),
),
GetRatings(),
MovieDesc(this.item),
],
),
),
],
),
),
);
}
}
class MovieDesc extends StatelessWidget {
final Item item;
MovieDesc(this.item);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Column(
children: <Widget>[
Text(
'RELEASE DATE:',
style: TextStyle(
color: Colors.black38,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
Text(
item.releaseDate,
style: TextStyle(
color: Colors.black,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
Container(
margin: EdgeInsets.only(left: 10.0, right: 10.0),
child: Column(
children: <Widget>[
Text(
'RUNTIME:',
style: TextStyle(
color: Colors.black38,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
Text(
item.runtime,
style: TextStyle(
color: Colors.black,
fontSize: 9.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
);
}
}
(e). /screen/HomeScreen.dart
import 'package:flutter/material.dart';
import 'package:flutter_listview_app/model/Item.dart';
import 'package:flutter_listview_app/screen/ItemList.dart';
class HomeScreen extends StatelessWidget {
List<Item> itemList;
@override
Widget build(BuildContext context) {
itemList = _itemList();
return Scaffold(
appBar: AppBar(
title: Text('Movies'),
),
body: _listView(),
);
}
Widget _listView() {
return Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(
child: ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children: itemList
.map(
(Item) => ItemList(item: Item),
)
.toList(),
)),
],
),
);
}
List<Item> _itemList() {
return [
Item(
id: 0,
name: 'Avengers: Infinity War',
category: 'Action, Adventure, Fantasy',
desc: 'The Avengers and their allies must be willing '
'to sacrifice all in an attempt to defeat '
'the powerful Thanos before his blitz of '
'devastation and ruin puts an end to the universe.'
'\nAs the Avengers and their allies have continued '
'to protect the world from threats too large for '
'any one hero to handle, a danger has emerged '
'from the cosmic shadows: Thanos.',
rating: 8.7,
directors: 'Directors: Anthony Russo, Joe Russo',
releaseDate: '27 April 2018',
releaseDateDesc: 'USA (2018), 2h 29min',
runtime: '2h 29min',
bannerUrl: 'assets/images/movie_banner_1.png',
imageUrl: 'assets/images/ic_preview_1.png',
trailerImg1: 'assets/images/ic_thumb_11.png',
trailerImg2: 'assets/images/ic_thumb_12.png',
trailerImg3: 'assets/images/ic_thumb_13.png',
),
Item(
id: 1,
name: 'Transformers: The Last Knight',
category: 'Action, Adventure, Sci-Fi',
desc: 'Autobots and Decepticons are at war, with humans '
'on the sidelines. Optimus Prime is gone. The key to '
'saving our future lies buried in the secrets of the past, '
'in the hidden history of Transformers on Earth.',
rating: 5.2,
directors: 'Directors: Michael Bay',
releaseDate: '21 June 2017',
releaseDateDesc: 'USA (2017), 2h 34min',
runtime: '2h 34min',
bannerUrl: 'assets/images/movie_banner_2.png',
imageUrl: 'assets/images/ic_preview_2.png',
trailerImg1: 'assets/images/ic_thumb_21.png',
trailerImg2: 'assets/images/ic_thumb_21.png',
trailerImg3: 'assets/images/ic_thumb_21.png',
),
Item(
id: 2,
name: 'Pacific Rim: Uprising',
category: 'Action, Adventure, Sci-Fi',
desc: 'Jake Pentecost, son of Stacker Pentecost, reunites with '
'Mako Mori to lead a new generation of Jaeger pilots, including '
'rival Lambert and 15-year-old hacker Amara, against a new Kaiju threat.',
rating: 5.7,
directors: 'Directors: Steven S. DeKnight',
releaseDate: '23 March 2018',
releaseDateDesc: 'USA (2018), 1h 51min',
runtime: '1h 51min',
bannerUrl: 'assets/images/movie_banner_3.png',
imageUrl: 'assets/images/ic_preview_3.png',
trailerImg1: 'assets/images/ic_thumb_31.png',
trailerImg2: 'assets/images/ic_thumb_31.png',
trailerImg3: 'assets/images/ic_thumb_31.png',
),
Item(
id: 3,
name: 'Thor: Ragnarok',
category: 'Action, Adventure, Comedy',
desc: 'Thor is imprisoned on the planet Sakaar, and must '
'race against time to return to Asgard and stop Ragnarök, '
'the destruction of his world, at the hands of the powerful '
'and ruthless villain Hela.',
rating: 7.9,
directors: 'Directors: Taika Waititi',
releaseDate: '3 November 2017',
releaseDateDesc: 'USA (2017), 2h 10min',
runtime: '2h 10min',
bannerUrl: 'assets/images/movie_banner_4.png',
imageUrl: 'assets/images/ic_preview_4.png',
trailerImg1: 'assets/images/ic_thumb_41.png',
trailerImg2: 'assets/images/ic_thumb_41.png',
trailerImg3: 'assets/images/ic_thumb_41.png',
),
];
}
}
Step 6: Create main class
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_listview_app/constant/Constant.dart';
import 'package:flutter_listview_app/screen/HomeScreen.dart';
import 'package:flutter_listview_app/screen/SplashScreen.dart';
void main() => runApp(
MaterialApp(
title: 'ListView Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.red,
accentColor: Color(0xFF761322),
),
home: SplashScreen(),
routes: <String, WidgetBuilder>{
SPLASH_SCREEN: (BuildContext context) => SplashScreen(),
HOME_SCREEN:(BuildContext context)=>HomeScreen(),
},
),
);
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |