ListView taking all the vertical space - flutter

I am working on a website using Flutter, but I have encountered a problem with ListView.builder. It's working fine until I try to scroll it down, then it takes all the possible space on screen. I also tried to create a ListView.builder in column and I had the same issue that the ListView was showing on the whole possible height.
main.dart:
import 'package:flutter/material.dart';
import 'package:kakadu_web/pages/shop.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Welcome to Flutter',
home: Builder(
builder: (BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Scaffold(body: ShopPage(width, height));
},
));
}
}
shop.dart:
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:google_fonts/google_fonts.dart';
import '../resources/values/colors.dart';
import '../widgets/buttons.dart';
Widget ShopPage(var width, var height) {
return Container(
alignment: Alignment.center,
child: Row(children: [
SizedBox(
width: 80,
height: height,
child: Container(
decoration: BoxDecoration(
color: AppColors.primaryBackgroundColor,
),
child: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
Padding(
padding: const EdgeInsets.only(top: 15),
child: RichText(
text: TextSpan(
style: GoogleFonts.josefinSans(
fontWeight: FontWeight.w700,
fontSize: 60,
color: AppColors.primaryIconColor),
children: [TextSpan(text: "M")])),
),
Container(
height: 200,
),
PageSelectionButton(
text: "Sklepik ",
icon: Icon(Icons.shopping_cart_outlined),
),
PageSelectionButton(
text: "Sklepik ",
icon: Icon(Icons.shopping_cart_outlined),
),
PageSelectionButton(
text: "Sklepik ",
icon: Icon(Icons.shopping_cart_outlined),
),
]),
),
),
Expanded(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(children: [
Expanded(child: Container()),
Flexible(
child: ListTile(
minVerticalPadding: 8,
selectedTileColor: Colors.indigoAccent,
selected: true,
title: Center(
child: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 16, fontWeight: FontWeight.w500),
text: "Wszystko"),
)),
tileColor: Colors.indigo,
),
),
Flexible(
child: ListTile(
minVerticalPadding: 8,
contentPadding: EdgeInsets.only(left: 16),
selectedTileColor: Colors.indigoAccent,
selected: true,
title: Center(
child: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 16, fontWeight: FontWeight.w500),
text: "Wszystko"),
)),
tileColor: Colors.indigo,
),
),
Expanded(child: Container()),
]),
),
Divider(),
Expanded(
child: Container(
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.only(
top: 8,
),
itemBuilder: (context, index) => Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: ListTile(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(60))),
leading: SvgPicture.asset(
"assets/drink.svg",
),
title: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 20, fontWeight: FontWeight.w400),
text: "Tymbark Jabłkowy 2L",
)),
subtitle: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 16, fontWeight: FontWeight.w300),
text: "20.00 zł",
)),
onTap: () => debugPrint("Przycisnął"),
tileColor: AppColors.primaryBackgroundColor,
trailing: Container(
width: 100,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SvgPicture.asset(
"assets/sweets.svg",
semanticsLabel: "Vege",
width: 50,
),
SvgPicture.asset("assets/cold.svg",
semanticsLabel: "Vege", width: 50)
]),
),
),
),
),
],
),
),
),
),
],
),
),
]),
);
}
These are screens how it looks like:
ListView after adding SizedBox
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:google_fonts/google_fonts.dart';
import '../resources/values/colors.dart';
import '../widgets/buttons.dart';
Widget ShopPage() {
return Expanded(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(children: [
Expanded(child: Container()),
Flexible(
child: ListTile(
minVerticalPadding: 8,
selectedTileColor: Colors.indigoAccent,
selected: true,
title: Center(
child: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 16, fontWeight: FontWeight.w500),
text: "Wszystko"),
)),
tileColor: Colors.indigo,
),
),
Flexible(
child: ListTile(
minVerticalPadding: 8,
contentPadding: EdgeInsets.only(left: 16),
selectedTileColor: Colors.indigoAccent,
selected: true,
title: Center(
child: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 16, fontWeight: FontWeight.w500),
text: "Wszystko"),
)),
tileColor: Colors.indigo,
),
),
Expanded(child: Container()),
]),
),
Divider(),
Expanded(
child: SizedBox( // Here is created SizedBox
height: 500,
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.only(
top: 8,
),
itemBuilder: (context, index) => Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: ListTile(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(60))),
leading: SvgPicture.asset(
"assets/drink.svg",
),
title: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 20, fontWeight: FontWeight.w400),
text: "Tymbark Jabłkowy 2L",
)),
subtitle: RichText(
text: TextSpan(
style: GoogleFonts.rubik(
fontSize: 16, fontWeight: FontWeight.w300),
text: "20.00 zł",
)),
onTap: () => debugPrint("Przycisnął"),
tileColor: AppColors.primaryBackgroundColor,
trailing: Container(
width: 100,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SvgPicture.asset(
"assets/sweets.svg",
semanticsLabel: "Vege",
width: 50,
),
SvgPicture.asset("assets/cold.svg",
semanticsLabel: "Vege", width: 50)
]),
),
),
),
),
],
),
),
),
),
],
),
);
}

Listview is expected to take all the possible height given by its parent. So if you expect to have a Listview with finite height you should create a SizedBox() with specific height and make Listview its child.
Here is a youtube video from Flutter team explaining this: https://www.youtube.com/watch?v=jckqXR5CrPI

Related

Flutter ExpansionPanel closes immediately after clicking

I'fetching data from my API and want to display the data in a ExpansionPanelList. Now I have the problem, that every time when i want to fold out a Panel it closes immediately. Thanks for helping, here my Code:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:http/http.dart' as http;
class Job {
final String vorname;
final String nachname;
bool isExpanded;
Job({
required this.vorname,
required this.nachname,
required this.isExpanded,
});
factory Job.fromJson(Map<String, dynamic> json) {
return Job(
vorname: json['vorname'],
nachname: json['nachname'],
isExpanded: false,
);
}
}
Future<List<Job>> fetchJobs() async {
final response = await http.get(Uri.parse('http://10.0.2.2:8000/api/jobs'));
if(response.statusCode == 200){
List jsonResponse = json.decode(response.body);
return jsonResponse.map((data) => Job.fromJson(data)).toList();
} else {
throw Exception('failed to load job');
}
}
class AuftraegePage extends StatefulWidget {
#override
_AuftraegePage createState() => _AuftraegePage();
}
class _AuftraegePage extends State<AuftraegePage>{
String selectedDay = DateFormat('dd.MM.yyyy').format(DateTime.now());
final List<Map<String, dynamic>> _items = List.generate(
10,
(index) => {
'id': index,
'title': 'Max Mustermann',
'description':
'This is the description of the item $index. Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
'isExpanded': false,
});
void _showDatePicker(){
showDatePicker(
locale: const Locale('de', 'DE'),
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1970),
lastDate: DateTime(2050),
builder: (context, child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: Color(0xff1f6526),
onPrimary: Colors.white,
onSurface: Colors.black,
),
dialogBackgroundColor:Colors.white,
),
child: child!,
);
},
).then((value) {
setState(() {
String _dateTime = DateFormat('dd.MM.yyy', 'de_DE').format(value!);
selectedDay = _dateTime;
});
} );
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.grey[300],
child: SingleChildScrollView(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 4.0),
physics: AlwaysScrollableScrollPhysics(),
child: Column(
children: <Widget> [
const SizedBox(height: 20),
const Text('Aufträge',
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
buildDatePicker(),
const SizedBox(height: 20),
buildCreateButton(),
const SizedBox(height: 20),
buildListOfJobs(),
],
),
),
);
}
Widget buildListOfJobs(){
return FutureBuilder<List<Job>>(
future: fetchJobs(),
builder: (context, snapshot) {
if(snapshot.hasData){
return ExpansionPanelList(
dividerColor: Colors.grey[700],
elevation: 3,
// Controlling the expansion behavior
expansionCallback: (index, isExpanded) {
setState(() {
snapshot.data![index].isExpanded = !isExpanded;
print(snapshot.data![index].isExpanded);
});
},
animationDuration: Duration(milliseconds: 600),
children: snapshot.data!.map(
(item) => ExpansionPanel(
canTapOnHeader: true,
backgroundColor:
item.isExpanded == true ? Colors.grey[300] : Colors.grey[300],
headerBuilder: (context, isExpanded){
return Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.vorname,
style: TextStyle(
fontSize: 18.0,
),
),
SizedBox(height: 5),
Wrap(
children: <Widget>[
Icon(
Icons.access_time_filled,
size: 22.0,
),
SizedBox(
width:5,
),
Text('07:00 - 07:13',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15
),)
],
),
],
)
);
},
body: Container(
color: Colors.grey[300],
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 30),
child: Column(
children: [
Row(
children: [
Expanded(
flex: 5,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.user,
size: 19,),
SizedBox(
width:5,
),
Text('Mitglied: OGV Imst ',
style: TextStyle(
fontSize: 15,
),),
],
),
),
Expanded(
flex: 4,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.locationDot,
size: 19),
SizedBox(
width:5,
),
Text('Ort: Imst',
style: TextStyle(
fontSize: 15,
)),
],
),
),
],
),
SizedBox(height: 10),
Row(
children: [
Expanded(
flex: 5,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.weightHanging,
size: 19,),
SizedBox(
width:5,
),
Text('Menge: 300 ',
style: TextStyle(
fontSize: 15,
),),
],
),
),
Expanded(
flex: 4,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.wineBottle,
size: 19,),
SizedBox(
width:5,
),
Text('Most: 0',
style: TextStyle(
fontSize: 15,
)),
],
),
),
],
),
SizedBox(height: 10),
Row(
children: [
Expanded(
flex: 5,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.bottleDroplet,
size: 19,),
SizedBox(
width:5,
),
Text('Flaschen: 0',
style: TextStyle(
fontSize: 15,
),),
],
),
),
Expanded(
flex: 4,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.bagShopping,
size: 19,),
SizedBox(
width:5,
),
Text('Bag: 0',
style: TextStyle(
fontSize: 15,
)),
],
),
),
],
),
SizedBox(height: 10),
Row(
children: [
Expanded(
flex: 5,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.box,
size: 19,),
SizedBox(
width:5,
),
Text('Karton: 0',
style: TextStyle(
fontSize: 15,
),),
],
),
),
],
),
SizedBox(height: 10),
Divider(color: Colors.black,),
SizedBox(height: 10),
Row(
children: [
Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.message,
size: 19,),
SizedBox(
width:5,
),
Text('Anmerkungen: ',
style: TextStyle(
fontSize: 15,
),),
],
),
],
),
SizedBox(height: 10),
Divider(color: Colors.black,),
SizedBox(height: 10),
Row(
children: [
Expanded(
flex: 5,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.tasks,
size: 19,),
SizedBox(
width:5,
),
Text('Bestätigt: Nein',
style: TextStyle(
fontSize: 15,
),),
],
),
),
Expanded(
flex: 4,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.xmark,
size: 19,),
SizedBox(
width:5,
),
Text('Stoniert: Nein',
style: TextStyle(
fontSize: 15,
)),
],
),
),
],
),
SizedBox(height: 10),
Row(
children: [
Expanded(
flex: 5,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.eye,
size: 19,),
SizedBox(
width:5,
),
Text('Noshow: Nein',
style: TextStyle(
fontSize: 15,
),),
],
),
),
Expanded(
flex: 4,
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.check,
size: 19,),
SizedBox(
width:5,
),
Text('Erledigt: Nein',
style: TextStyle(
fontSize: 15,
)),
],
),
),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(
flex: 5,
child: ElevatedButton(
onPressed:_showDatePicker,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
elevation: 5,
padding: EdgeInsets.all(13),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
),
child: Wrap(
children: const <Widget>[
Icon(FontAwesomeIcons.edit,
color: Colors.white,
size: 20.0,
),
SizedBox(
width:5,
),
Text(
'Bearbeiten',
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
],
),
),
),
SizedBox(width: 10),
Expanded(
flex: 5,
child: ElevatedButton(
onPressed:_showDatePicker,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.yellow[600],
elevation: 5,
padding: EdgeInsets.all(13),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
),
child: Wrap(
children: <Widget>[
Icon(FontAwesomeIcons.file,
color: Colors.black,
size: 20.0,
),
SizedBox(
width:5,
),
Text(
'Rechnung',
style: TextStyle(
color: Colors.black,
fontSize: 16.0,
),
),
],
),
),
),
],
),
],
),
),
isExpanded: item.isExpanded,
),
).toList(),
);
} else if(snapshot.hasError) {
return Text('${snapshot.error}');
//return Text('keine Daten gefunden');
}
return const CircularProgressIndicator();
},
);
}
Widget buildDatePicker(){
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Datum wählen: ',
style: TextStyle(
fontSize: 20,
),
),
ElevatedButton(
onPressed:_showDatePicker,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[700],
elevation: 5,
padding: EdgeInsets.all(13),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
),
child: Wrap(
children: <Widget>[
Icon(
Icons.date_range,
color: Colors.white,
size: 20.0,
),
SizedBox(
width:5,
),
Text(
selectedDay,
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
],
),
),
],
);
}
Widget buildCreateButton(){
return ElevatedButton(
onPressed:_showDatePicker,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[700],
minimumSize: const Size.fromHeight(50),
elevation: 5,
padding: EdgeInsets.only(left: 35, right: 20, top: 13, bottom: 13),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
),
child: Wrap(
children: <Widget>[
Text(
'Auftrag erstellen',
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
SizedBox(
width:5,
),
Icon(
Icons.add_card ,
color: Colors.white,
size: 20.0,
),
],
),
);
}
}
I think there could be an issue during fetching the data, because I think with every click my data get reloaded and "isExpanded" is set to *false * again.
You can replace future: fetchJobs() with by creating a state future variable. It will prevent unwanted api call.
late final jobListFuture = fetchJobs();
#override
Widget build(BuildContext context) {
Widget buildListOfJobs(){
return FutureBuilder<List<Job>>(
future: jobListFuture,

Add buttons below the body

The code below (I shortened the code for simplicity) displays the characteristics of the device. I placed all the characteristics in the body and displayed them in a certain way. Tell me how I can make two buttons (marked in red in the photo) under the body. Or put them in the body? tell me the best way to do it.
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
),
body: Align(
alignment: Alignment.topCenter,
child: Container(
constraints: const BoxConstraints(maxWidth: 800, maxHeight: 400),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(5.0),),
child: SingleChildScrollView(
child: Card(
child: Column(
children: [
ListTile(
title: const Text('Brand:', style: TextStyle(fontWeight: FontWeight.w400, fontSize: 25)),
trailing: Text('${device.brand} ', style: const TextStyle(fontWeight: FontWeight.w400, fontSize: 20 ))),
const Divider(color: Colors.black, endIndent: 10, indent: 10),
ListTile(
title: const Text('Operation system:', style: TextStyle(fontWeight: FontWeight.w400, fontSize: 25)),
trailing: Text('${device.operation_system} ', style: const TextStyle(fontWeight: FontWeight.w400, fontSize: 20 ))),
],
),
),
)
),
));
You can use bottomNavigationBar on Scaffold
Scaffold(
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(8.0), //the one you prefer
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {},
child: Text("NNNNNN"),
),
),
SizedBox(
//space between button
width: 16,
),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: Text("NNNNNN"),
),
),
],
),
),
Also, you can wrap Container with Column widget
body: Column(
children: [
Container(...)//your widget
Padding(
padding: const EdgeInsets.all(8.0), //the one you prefer
child: Row(
children: [
Expanded(child: OutlinedButton(...)),
SizedBox(width:x),
Expanded(child: OutlinedButton(...)),
Full snippet on second approach
return Scaffold(
body: Column(
children: [
Container(
constraints: const BoxConstraints(maxWidth: 800, maxHeight: 400),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(5.0),
),
child: SingleChildScrollView(
child: Card(
child: Column(
children: [
ListTile(
title: const Text('Brand:',
style: TextStyle(
fontWeight: FontWeight.w400, fontSize: 25)),
trailing: Text('${device.brand} ',
style: const TextStyle(
fontWeight: FontWeight.w400, fontSize: 20))),
const Divider(
color: Colors.black, endIndent: 10, indent: 10),
ListTile(
title: const Text('Operation system:',
style: TextStyle(
fontWeight: FontWeight.w400, fontSize: 25)),
trailing: Text('${device.operation_system} ',
style: const TextStyle(
fontWeight: FontWeight.w400, fontSize: 20))),
],
),
),
)),
// Spacer(), /// you you want at the bottom
Padding(
padding: const EdgeInsets.all(8.0), //the one you prefer
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {},
child: Text("NNNNNN"),
),
),
SizedBox(
//space between button
width: 16,
),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: Text("NNNNNN"),
),
),
],
),
)
],
),
);
You can check more about widgets, Column and layout.

TabBarView inside Sliver with StickyHeader

I have made this layout so far using CustomScrollView and Sticky Header
https://imgur.com/a/Xo4AfAM
What I want to achieve is that the content below the tabs should not scroll unless there is extra content available. Also, after scrolling, the content should not go behind the sticky header.
My Code so far.
Scaffold(
backgroundColor: Colors.white,
extendBodyBehindAppBar: true,
appBar: AppBar(
toolbarHeight: getHeight() * (1 / 11),
),
body: Padding(
padding: getPaddings(),
child: DefaultTabController(
length: 3,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
height: getHeight() * (3 / 11),
color: Colors.blue,
),
),
SliverStickyHeader(
header: Column(
children: [
Container(
height: getHeight() * (1 / 11),
width: double.infinity,
color: kPrimaryColor,
child: Center(
child: Text(
"TEXT",
style: TextStyle(
fontSize: 32,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
Container(
height: getHeight() * (1 / 11),
color: kPrimaryColor,
child: TabBar(
tabs: [
Tab(
child: Text(
'TITLE1',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'TITLE2',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'TITLE3',
style: TextStyle(color: Colors.black),
),
),
],
),
),
],
),
sliver: SliverFillRemaining(
child: TabBarView(
children: [
Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(50),
vertical: getProportionateScreenHeight(20)),
child: Column(
children: [
RoundedPicture(),
FittedBox(
child: Text("Hello World",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 40)),
),
SizedBox(
height: getProportionateScreenHeight(20),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
text: 'Info1: ',
children: [
TextSpan(
text: "123",
style: TextStyle(
color: kSecondaryColor,
),
),
]),
),
SizedBox(
height: getProportionateScreenHeight(20),
),
RichText(
text: TextSpan(
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
text: 'Info2: ',
children: [
TextSpan(
text: "abcd",
style: TextStyle(
color: kSecondaryColor,
),
),
]),
),
SizedBox(
height: getProportionateScreenHeight(20),
),
RichText(
text: TextSpan(
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
text: 'Info3: ',
children: [
TextSpan(
text: "xyz",
style: TextStyle(
color: kSecondaryColor,
),
),
]),
),
],
),
],
),
),
Center(
child: Text("TITLE2"),
),
Center(
child: Text("TITLE3"),
),
],
),
),
),
],
),
),
),
To achieve the required layout, I tried using TabBarView inside different slivers i.e SliverList and SliverToBoxAdapter but they all resulted in an error because TabBarView doesn't have a predefined height.
Here is my implementation.
Because there are no method related to size, I just set a value myself.
Stack
DefaultTabController
NestedScrollView
SliverAppBar
SliverPersistentHeader
SliverPersistentHeader
TabBarView
Container // For back button
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(initialIndex: 0, length: 3, vsync: this);
}
double getHeight() {
return 800;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
extendBodyBehindAppBar: true,
body: SafeArea(
child: Stack(
children: [
DefaultTabController(
length: 3,
child: NestedScrollView(
headerSliverBuilder: (context, value) {
return [
SliverAppBar(
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(),
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
minHeight: 90,
maxHeight: 90,
child: Container(
height: getHeight() * (1 / 11),
width: double.infinity,
color: Colors.green[200],
child: Center(
child: Text(
"TEXT",
style: TextStyle(
fontSize: 32,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
minHeight: 90,
maxHeight: 90,
child: Container(
color: Colors.green[200],
child: TabBar(
controller: _tabController,
tabs: [
Tab(
child: Text(
'TITLE1',
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
'TITLE2',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'TITLE3',
style: TextStyle(color: Colors.black),
),
),
],
),
),
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(bottom: 600),
child: Column(
children: [
// RoundedPicture(),
Icon(
Icons.favorite,
color: Colors.pink,
size: 150.0,
semanticLabel:
'Text to announce in accessibility modes',
),
FittedBox(
child: Text("Hello World",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 40)),
),
SizedBox(
height: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
text: 'Info1: ',
children: [
TextSpan(
text: "123",
style: TextStyle(),
),
]),
),
SizedBox(
height: 20,
),
RichText(
text: TextSpan(
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
text: 'Info2: ',
children: [
TextSpan(
text: "abcd",
style: TextStyle(),
),
]),
),
SizedBox(
height: 20,
),
RichText(
text: TextSpan(
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20),
text: 'Info3: ',
children: [
TextSpan(
text: "xyz",
style: TextStyle(),
),
]),
),
],
),
],
),
),
),
SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(bottom: 600),
child: Column(
children: [
Container(
padding: EdgeInsets.only(bottom: 600),
child: Center(
child: Text("TITLE2"),
),
),
],
),
),
),
SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(bottom: 600),
child: Column(
children: [
Container(
padding: EdgeInsets.only(bottom: 600),
child: Center(
child: Text("TITLE3"),
),
),
],
),
),
),
],
),
),
),
Container(
height: 90,
padding: EdgeInsets.symmetric(horizontal: 15),
child: InkWell(
onTap: () {},
child: Icon(Icons.arrow_back),
),
),
],
),
),
);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
#required this.minHeight,
#required this.maxHeight,
#required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
#override
double get minExtent => minHeight;
#override
double get maxExtent => math.max(maxHeight, minHeight);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
After finding no possible solution, I decided to implement my own tab bar view instead of using the default one. The solution isn't as fancy since the transition of tabs is done by changing the current page number and rebuilding the widget but solves the issue. I used a gesture detector for allowing users to swipe the page.
GestureDetector(
onHorizontalDragEnd: (dragDetails) {
if (dragDetails.primaryVelocity != 0) {
final int val = dragDetails.primaryVelocity.sign.toInt();
if (currentPage - val >= 0 &&
currentPage - val < tabController.length)
tabController.animateTo(currentPage - val);
}
},
child: CustomScrollView(
shrinkWrap: true,
controller: scrollController,
slivers: [
SliverToBoxAdapter(
child: LogoContainer(),
),
SliverStickyHeader(
header: Column(
children: [
NameContainer(text: 'TEXT'),
Container(
height: 60,
color: kSecondaryColor,
child: TabBar(
controller: tabController,
tabs: [
Tab(
child: Text(
'TITLE1',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'TITLE2',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'TITLE3',
style: TextStyle(color: Colors.black),
),
),
],
),
),
],
),
sliver: SliverToBoxAdapter(
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: getHeight() * (6 / 11)),
child: [
Column(
children: [
RoundedPicture(),
FittedBox(
child: Text(
'HELLO WORLD',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 40),
),
),
SizedBox(
height: getProportionateScreenHeight(20),
),
Column(
children: [
ProfileInfoText(
title: 'INFO1',
text: 'abcd',
),
SizedBox(
height: getProportionateScreenHeight(20),
),
ProfileInfoText(
title: 'INFO2',
text: '123',
),
SizedBox(
height: getProportionateScreenHeight(20),
),
ProfileInfoText(
title: 'INFO3',
text: 'xyz',
),
],
),
],
),
Center(
child: Text("TITLE2"),
),
Center(
child: Text("TITLE3"),
),
].elementAt(currentPage),
),
),
),
],
),
),
I also used scroll controller to animate the list back to top when changing the tabs to give it a smooth look.
#override
void initState() {
super.initState();
tabController = TabController(length: 3, vsync: this)
..addListener(() async {
await scrollController.animateTo(
0,
duration: Duration(seconds: 1),
curve: Curves.ease,
);
setState(() {
currentPage = tabController.index;
});
});
}

Setting Height on Grid Tile Bar

I have a general question regarding the height of a GridTile Bar.
I currently have the GridTile display like this:
My Objective is to have it like this:
When I add the SizedBox to leave a space between price and Address, the address gets cut off the second line.
Any Ideas on how to move it up.
Here is my code of the Grid Tile:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../providers/listing.dart';
class ListingItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
final listing = Provider.of<Listing>(context, listen: false);
final formatDolar = new NumberFormat("#,##0.00", "en_US");
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: GridTile(
child: GestureDetector(
onTap: () {},
child: Image.network(
listing.coverPhoto,
fit: BoxFit.cover,
),
),
header: GridTileBar(
title: Text(''),
trailing: IconButton(
icon: Icon(Icons.favorite_border),
color: Theme.of(context).accentColor,
onPressed: () {},
),
),
footer: GridTileBar(
backgroundColor: Colors.black54,
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'\$ ${formatDolar.format(listing.price)}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(
width: 20,
height: 5,
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Flexible(
child: Text(
'${listing.street}, ${listing.street2}, ${listing.city}, ${listing.state}, ${listing.zipCode}',
maxLines: 3,
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
SizedBox(
height: 5,
),
Text(
'|',
style: TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold),
),
SizedBox(
width: 5,
),
Text(
'${listing.bedRooms} bds',
style: TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold),
),
SizedBox(
width: 5,
),
Text(
'|',
style: TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold),
),
SizedBox(
width: 5,
),
Text(
'${listing.bathRooms} bth',
style: TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold),
),
SizedBox(
width: 5,
),
],
),
),
SizedBox(
height: 1,
),
],
),
),
),
);
}
}
and here it is the code for the Grid:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/listings.dart';
import './listing_item.dart';
class ListingGrid extends StatelessWidget {
#override
Widget build(BuildContext context) {
final listingData = Provider.of<Listings>(context);
final listings = listingData.items;
return GridView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: 10,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: listings[i],
child: ListingItem(),
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
childAspectRatio: 3.5 / 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10),
);
}
}
I have tried changing the childAspectRatio in the grid but I only get the cover photo to get bigger not the Tile Bar which is what I want to move up.
Any Ideas?
Kind Regards.
Put GridTileBar widget in a Container widget and give it the height that you want. Here's an example code:
GridTile(
footer: Container(
padding: const EdgeInsets.all(8),
color: Colors.black54,
height: 60,
child: GridTileBar(
title: Text(
"Example",
style: TextStyle(color: Colors.black),
),
),
),

'RenderBox was not laid out' error even after using Expanded

I have added two card widgets in a row enclosed in a columnCode:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Padding(
padding: EdgeInsets.fromLTRB(0.0, 10, 0.0, 0.0),
child: Row(
children: [
Expanded(
child: SizedBox(
height: 70,
child: Card(
color: Colors.orange[500],
child: ListTile(
leading: CircleAvatar(
backgroundImage:
AssetImage('assets/card_photo.png'),
),
title: Text(
'Teacher of the month',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins-Bold'),
),
subtitle: Text(
'MAY 2020',
style: TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: 'Poppins-Bold'),
),
onTap: () {},
),
),
),
),
Expanded(
child: SizedBox(
height: 70,
child: Card(
color: Colors.orange[500],
child: ListTile(
leading: CircleAvatar(
backgroundImage:
AssetImage('assets/card_photo.png'),
),
title: Text(
'Teacher of the month',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins-Bold'),
),
subtitle: Text(
'CLASS NAME',
style: TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: 'Poppins-Bold'),
),
onTap: () {},
),
),
),
),
],
),
),
],
),
),
);
}
}
This is the output:Output Image
However I want this row to be scrollabe widgets of cards. But even after using expanded, I am getting 'RenderBox was not laid out' error.
Here is the code for it:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Padding(
padding: EdgeInsets.fromLTRB(0.0, 10, 0.0, 0.0),
child: SingleChildScrollView(//added scrollview widget
scrollDirection: Axis.horizontal,
child: Row(
children: [
Expanded(
child: SizedBox(
height: 70,
child: Card(
color: Colors.orange[500],
child: ListTile(
leading: CircleAvatar(
backgroundImage:
AssetImage('assets/card_photo.png'),
),
title: Text(
'Teacher of the month',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins-Bold'),
),
subtitle: Text(
'MAY 2020',
style: TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: 'Poppins-Bold'),
),
onTap: () {},
),
),
),
),
Expanded(
child: SizedBox(
height: 70,
child: Card(
color: Colors.orange[500],
child: ListTile(
leading: CircleAvatar(
backgroundImage:
AssetImage('assets/card_photo.png'),
),
title: Text(
'Teacher of the month',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
fontFamily: 'Poppins-Bold'),
),
subtitle: Text(
'CLASS NAME',
style: TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: 'Poppins-Bold'),
),
onTap: () {},
),
),
),
),
],
),
),
),
],
),
),
);
}
}
Edit: I also want text below the icon. If someone could help me with that also. Sample image name icon
Check if this works
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class something extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appBar = AppBar();
return Container(
height: MediaQuery.of(context).size.height / 3,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 6,
itemExtent: MediaQuery.of(context).size.height / 3,
itemBuilder: (context, index) {
return _cards(context, appBar);
},
),
);
}
}
Widget _cards(BuildContext context, AppBar appBar) {
return Align(
child: Container(
height: 100,
child: Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
elevation: 5,
margin: EdgeInsets.all(10),
color: Colors.orange[500],
child: ListTile(
leading: Column(
children: [
CircleAvatar(
backgroundImage: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
),
Text("Name"),
],
),
title: Text('Teacher of the month', style: _textStyle(10)),
subtitle: Text('MAY 2020', style: _textStyle(8)),
onTap: () {},
),
),
),
);
}
_textStyle(double size) {
return TextStyle(
fontSize: size,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: 'Poppins-Bold');
}
It gives me output like this
You should use listView widget.
ListView(
children: <Widget>[
ItemOne(),
ItemTwo(),
ItemThree(),
],
),
and to change the scroll physics use:
scrollDirection: Axis.horizontal,