Flutter/Dart - Dynamic font size to fit Card - flutter

I am trying to create some ocassion cards inside a pageview and was wondering if there was a way to make my fontsize dynamic to avoid pixel overflow. Here is a screenshot of a card that works fine:
But when I add an occasion that has more characters...
Here is my code:
class Category {
String name;
IconData icon;
Widget route;
Category(this.name, this.icon, this.route);
}
class CalendarEvents {
String title;
String date;
CalendarEvents(this.title, this.date);
}
class AccountPage extends StatefulWidget {
#override
_AccountPageState createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
List<Category> _categories = [
Category('My History', Icons.history, MyHistory()),
Category('Dates to Remember', Icons.event_note, DatesToRemember()),
Category('Terms and Conditions', Icons.assignment, TermsandConditions()),
Category('Privacy Notice', Icons.security, PrivacyNotice()),
Category('Rate us' , Icons.stars, RateUs()),
Category('Send us Feedback' , Icons.feedback, GiveUsFeedback())
];
DateFormat formatter = DateFormat('dd/MM/yyyy');
List<CalendarEvents> _events = [
CalendarEvents('Christmas Day', "25/12/2020"),
CalendarEvents('New Years Eve', "31/12/2020"),
CalendarEvents('New Years Day',"01/01/2021"),
CalendarEvents('Valentines Day', "14/02/2021"),
CalendarEvents('A very long occasion that needs to be resized','01/01/2021')
];
int _index = 0;
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
Container(
child: SizedBox(
height: 75, // card height
child: PageView.builder(
itemCount: _events.length,
controller: PageController(viewportFraction: 0.5),
onPageChanged: (int index) => setState(() => _index = index),
itemBuilder: (_, i) {
return Transform.scale(
scale: i == _index ? 1 : 0.5,
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(formatter.parse(_events[i].date).day.toString()),
Text(DateFormat.MMMM().format(formatter.parse(_events[i].date)).toString()),
Text(
_events[i].title,
style: AppBarTextStyle,
),
],
),
),
);
},
),
),
),
// SizedBox(height: 100.0,),
Container(
height: MediaQuery.of(context).size.height * 0.7,
child: ListView.builder(
itemCount: _categories.length,
itemBuilder: (context, index) {
return Column(
children: <Widget>[
ListTile(
leading: Icon(_categories[index].icon, color: Colors.black,),
title: Text(_categories[index].name),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => _categories[index].route)),
),
Divider(), //
],
);
}),
),
],
),
);
}
}
It would be useful to resize the font depending on the length of occasion. If this is possible, what would be the best way of achieving these results? Thanks

If you want control over the fontsize even after resized, you should use https://pub.dev/packages/auto_size_text package like #Abhishek mentioned
If you just want the text to resize freely, you can use FittedBox and wrap it around Text widget, like this:
FittedBox(
fit: BoxFit.contain,
child: Text()
)

you can use https://pub.dev/packages/auto_size_text package like
title: Text(_categories[index].name),
instead of this
title: AutoSizeText(
_categories[index].name,
minFontSize: 10,
stepGranularity: 10,
maxLines: 4,
overflow: TextOverflow.ellipsis,
)

Related

Build a table row in Flutter

I have data that comes from API and I want to arrange it in the form of a table as in the image attached below, but when I do that the data is repeated and I could not solve the problem, I need help on modifying the code below so that I do not want to repeat the code until the modification begins.
Clarification
The code
class StarringComponent extends StatelessWidget {
StarringComponent({Key? key}) : super(key: key);
final List<_StarringModel> _listOfStarring = [
_StarringModel('Michael J. Fox', 'https://www.biography.com/.image/ar_8:10%2Cc_fill%2Ccs_srgb%2Cfl_progressive%2Cg_faces:center%2Cq_auto:good%2Cw_620/MTkwNTAwODA4ODM0NDI2Nzc4/gettyimages-1144626740.jpg'),
_StarringModel('Christopher Lloyd', 'https://s3.r29static.com/bin/entry/9e6/0,0,2000,2000/x,80/2172764/image.jpg'),
_StarringModel('Robert Zimek', 'https://media1.popsugar-assets.com/files/thumbor/XS_IeHJo2d3MzS-0CXmqOZ0Zl34/fit-in/2048xorig/filters:format_auto-!!-:strip_icc-!!-/2019/01/08/515/n/1922398/7f748b5fb07a03fd_GettyImages-1019711520/i/Charlie-Hunnam.jpg'),
_StarringModel('Lea Thompson', 'https://www.independent.ie/migration_catalog/8bb89/25195674.ece/AUTOCROP/w620/N0217211295348219998A_1'),
];
#override
Widget build(BuildContext context) {
return Table(children: List.generate(_listOfStarring.length, (index) => _buildTableRow(context, _listOfStarring[index].name, _listOfStarring[index].image)));
}
TableRow _buildTableRow(BuildContext context, String name, String image) {
return TableRow(
children: List.generate(2, (index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
CircleAvatar(
radius: 14,
backgroundColor: Colors.red.withOpacity(0.4),
backgroundImage: NetworkImage(image),
),
SizedBox(width: 10),
Flexible(
child: Text(name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
))
);
}
}
Starring model
class _StarringModel {
String name, image;
_StarringModel(this.name, this.image);
}
Use GridView for simpler implementation.
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
...
),
itemCount: _listOfStarring.length,
itemBuilder: (BuildContext ctx, index) {
return _buildItem(
_listOfStarring[index].name,
_listOfStarring[index].image,
);
},
);
Widget _buildItem(String name, String image) {
...
}

The swap widget so that the under widget has been fixed

I have a create so simple slidable view pager with CarouselSlider:
return Scaffold(
body: CarouselSlider(
options: CarouselOptions(
viewportFraction: 1,
// aspectRatio: 1,
height: double.maxFinite,
// enlargeCenterPage: true,
),
items: List.generate(
10,
(i) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
color: (i % 2 == 0) ? Colors.red : Colors.green,
),
),
Text('text $i', style: TextStyle(fontSize: 16.0)),
],
)),
));
This is its result:
But as you can see next container connects to the first widget, I want when the first widget to be swapped to the left, the next widget appears under the first widget Not next to it. It looks like the following widget is fixed and we remove the top widget.
You can use a package called stacked_page_view, it is very simple, lightweight, and similar to the same original PageView in usage.
Example Snippet:
PageView.builder(
itemCount: 10,
scrollDirection: Axis.vertical,
controller: pageController,
itemBuilder: (context, index) {
return StackPageView(
controller: pageController,
index: index,
child: Container(
color: (colors..shuffle()).first,
child: Center(
child: Text(
'$index',
style: TextStyle(
color: Colors.white,
fontSize: 25,
),
),
),
),
);
},
)
Note: You can control the scroll axis with the property scrollDirection inside PageView.builder() with values of Axis.vertical or Axis.horizontal.
I finally find a way to create stack page view, This is a full codes:
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/material.dart';
import 'dummy_data.dart';
import 'page_view_item.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
/// The current page of the page view
double _page = 0;
/// The index of the leftmost element of the list to be displayed
int get _firstItemIndex => _page.toInt();
/// Controller to get the current position of the page view
final _controller = PageController(
viewportFraction: 0.5,
);
/// The width of a single item
late final _itemWidth =
MediaQuery.of(context).size.width * _controller.viewportFraction;
#override
void initState() {
super.initState();
_controller.addListener(() => setState(() {
_page = _controller.page!;
}));
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("LV Scroll"),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Positioned.fill(
child: Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: _itemWidth,
child: FractionallySizedBox(
child: PageViewItem(
index: _firstItemIndex,
width: _itemWidth,
url: model[_firstItemIndex],
),
),
),
),
),
SizedBox(
height: 250,
child: PageView.builder(
padEnds: false,
controller: _controller,
itemBuilder: (context, index) {
return Opacity(
opacity: index <= _firstItemIndex ? 0 : 1,
child: PageViewItem(
index: index,
width: _itemWidth,
url: model[index],
),
);
},
itemCount: model.length,
),
),
],
),
],
),
);
}
}
it's result :
and its reference;
You can use a package called expandable_page_view, it is a PageView widget adjusting its height to currently displayed page. It accepts the same parameters as classic PageView.
ExpandablePageView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return Container(color: Colors.blue);
},
),

ScrollablePositionedList with SliverAppBar not working properly

This is a repository to create a minimal reproducible example.
I want SliverAppBar hidden when ScrollablePositionedList.builder is Scrolled. This is the relevant piece of code I am including here.
NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverAppBar(
backgroundColor: Colors.blue,
expandedHeight: 112,
snap: true,
pinned: false,
floating: true,
forceElevated: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.event),
)
],
flexibleSpace: SafeArea(
child: Column(
children: <Widget>[
Container(
height: kToolbarHeight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Title',
style: Theme.of(context)
.textTheme
.title
.copyWith(
fontSize: 16, color: Colors.white),
),
SizedBox(
height: 2,
),
Text(
'Date',
style: Theme.of(context)
.textTheme
.caption
.copyWith(
fontSize: 10, color: Colors.white),
),
SizedBox(
height: 2,
),
Text(
'Another Text',
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(
fontSize: 14, color: Colors.white),
),
],
),
),
Expanded(
child: Container(
height: kToolbarHeight,
width: MediaQuery.of(context).size.width,
color: Colors.white,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
'Prev',
),
Text(
'Next',
)
],
),
),
)
],
),
),
)
],
body: ScrollablePositionedList.builder(
physics: ScrollPhysics(),
itemPositionsListener: itemPositionListener,
itemScrollController: _itemScrollController,
initialScrollIndex: 0,
itemCount: 500,
itemBuilder: (BuildContext ctxt, int index) {
return Container(
margin: EdgeInsets.all(16)
,
child: Text('$index'));
})),
I tried two approaches so far none of them working properly,
Approach 1
I added physics: ScrollPhysics(), to ScrollablePositionedList.builder
Output:
Appraoch 2
I added physics: NeverScrollableScrollPhysics(), to ScrollablePositionedList.builder
SliverAppBar hides this time but now I can not scroll to the very end of ScrollablePositionedList.builder I have 500 items on my list but it scrolls up to only 14th item, see the output. Also, it lags too much on scroll
Output:
Thanks in advance.
Answering question myself
This problem has no solution for it. I have created an issue here
It looks like ScrollablePositionedList with SliverAppBar cannot work until Flutter Team does not add shrinkwrap property to ScrollablePositionedList.
Feature request to add shrinkwrap is created here
It works for me
//create list of global keys
List<GlobalKey> _formKeys = [];
//assign keys from your list
for(int i=0 ;i< syourlist.length;i++){
final key = GlobalKey();
_formKeys.add(key);
}
//in list view give key as below
key:_formKeys[index]
//on button click
Scrollable.ensureVisible(_formKeys[index].currentContext);
Here is a basic workaround:
Use the ItemsPositionsListener to listen for the current item the list has scrolled to.
Then create boolean values to check the scroll-direction and amount.
These conditions control an AnimatedContainer controlling the height of a custom header.
This is placed as a child in a Column with the header in a Flexible widget so the scrollablelist correctly takes up the space before and after animation.
Although this is very basic and does not use the NestedScrollView, it keeps use of the ScrollablePositionedList, and achieves a similar effect with a header that slides in and out, based on the set scroll conditions.
Providing in case helps anyone else, until the underlying issue is fixed...:)
import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class ScrollAllWords extends StatefulWidget {
const ScrollAllWords({
Key? key,
required this.list,
}) : super(key: key);
final List<String> list;
#override
State<ScrollAllWords> createState() => _ScrollAllWordsState();
}
class _ScrollAllWordsState extends State<ScrollAllWords> {
/// use this listener to control the header position.
final _itemPositionsListener = ItemPositionsListener.create();
///Can also use the ItemScrollController to animate through the list (code omitted)
final _itemScrollController = ItemScrollController();
/// Gets the current index the list has scrolled to.
int _currentIndex = 0;
/// Compares against current index to determine the scroll direction.
int _shadowIndex = 0;
bool _reverseScrolling = false;
bool _showHeader = true;
#override
void initState() {
/// Set up the listener.
_itemPositionsListener.itemPositions.addListener(() {
checkScroll();
});
super.initState();
}
void checkScroll() {
/// Gets the current index of the scroll.
_currentIndex =
_itemPositionsListener.itemPositions.value
.elementAt(0)
.index;
/// Checks the scroll direction.
if (_currentIndex > _shadowIndex) {
_reverseScrolling = false;
_shadowIndex = _currentIndex;
}
if (_currentIndex < _shadowIndex) {
_reverseScrolling = true;
_shadowIndex = _currentIndex;
}
/// Checks whether to show or hide the scroller (e.g. show when scrolled passed 15 items and not reversing).
if (!_reverseScrolling && _currentIndex > 15) {
_showHeader = false;
} else {
_showHeader = true;
}
setState(() {});
}
#override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 120),
height: _showHeader ? 200 : 0,
curve: Curves.easeOutCubic,
child: Container(
color: Colors.red,
height: size.height * 0.20,
),
),
Flexible(
child: ScrollablePositionedList.builder(
itemScrollController: _itemScrollController,
itemPositionsListener: _itemPositionsListener,
itemCount: widget.list.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.list[index]),
);
},
),
),
],
);
}
}

How to make a multi column Flutter DataTable widget span the full width?

I have a 2 column flutter DataTable and the lines don't span the screen width leaving lots of white space. I found this issue
https://github.com/flutter/flutter/issues/12775
That recommended wrapping the DataTable in a SizedBox.expand widget but that does not work produces RenderBox was not laid out:
SizedBox.expand(
child: DataTable(columns:_columns, rows:_rows),
),
Full widget
#override
Widget build(BuildContext context) {
return new Scaffold(
body:
SingleChildScrollView(
child: Column(
children: [Container(Text('My Text')),
Container(
alignment: Alignment.topLeft,
child: SingleChildScrollView(scrollDirection: Axis.horizontal,
child: SizedBox.expand(
child: DataTable(columns:_columns, rows:_rows),
),
),
),
]))
);
}
You can add the crossAxisAlignment for your Column to strech
crossAxisAlignment: CrossAxisAlignment.stretch
SizedBox.expand results in the DataTable taking an infinite height which the SingleChildScrollView won't like. Since you only want to span the width of the parent, you can use a LayoutBuilder to get the size of the parent you care about and then wrap the DataTable in a ConstrainedBox.
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
child: Column(
children: [
const Text('My Text'),
Container(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: constraints.minWidth),
child: DataTable(columns: [], rows: []),
),
),
),
],
),
),
),
);
}
This is an issue, incompleteness, in an otherwise beautiful Widget which is the DataTable,
I faced this issue in a production code, this solution worked on more than half of the lab devices:
ConstrainedBox(
constraints: BoxConstraints.expand(
width: MediaQuery.of(context).size.width
),
child: DataTable( // columns and rows.),)
But you know what suprisingly worked on %100 of the devices ? this:
Row( // a dirty trick to make the DataTable fit width
children: <Widget>[
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(...) ...]//row children
Note: The Row has only one child Expanded which in turn enclose a SingleChildScrollView which in turn enclose the DataTable.
Note that this way you cant use SingleChileScrollView with scrollDirection: Axis.horizontal, in case you need it, but you dont otherwise this question would be irrelevant to your use case.
In case someone of the Flutter team reads this, please enrich the DataTable Widget, it will make flutter competitive and powerful, flutter may eclipse androids own native API if done right.
Set your datatable in Container and make container's width as double.infinity
Container(
width: double.infinity,
child: DataTable(
columns: _columns,
rows: _rows,
));
For DataTable widget this code has worked for me regarding dataTable width as match parent to device-width,
Code snippet:
ConstrainedBox(
constraints:
BoxConstraints.expand(
width: MediaQuery.of(context).size.width
),
child:
DataTable(
// inside dataTable widget you must have columns and rows.),)
and you can remove space between columns by using attribute like
columnSpacing: 0,
Note:
using ConstrainedBox widget solves your issue,
constraints: BoxConstraints.expand(width: MediaQuery.of(context).size.width),
Complete Code :
Note:
In this sample code, I covered sorting and editing DataTable widget concepts.
In Lib Folder you must have this class
main.dart
DataTableDemo.dart
customer.dart
main.dart class code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'DataTableDemo.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DataTableDemo(),
);
}
}
DataTableDemo.dart class code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'customer.dart';
class DataTableDemo extends StatefulWidget {
DataTableDemo() : super();
final String title = "Data Table";
#override
DataTableDemoState createState() => DataTableDemoState();
}
class DataTableDemoState extends State<DataTableDemo> {
List<customer> users;
List<customer> selectedUsers;
bool sort;
TextEditingController _controller;
int iSortColumnIndex = 0;
int iContact;
#override
void initState() {
sort = false;
selectedUsers = [];
users = customer.getUsers();
_controller = new TextEditingController();
super.initState();
}
onSortColum(int columnIndex, bool ascending) {
if (columnIndex == 0) {
if (ascending) {
users.sort((a, b) => a.firstName.compareTo(b.firstName));
} else {
users.sort((a, b) => b.firstName.compareTo(a.firstName));
}
}
}
onSelectedRow(bool selected, customer user) async {
setState(() {
if (selected) {
selectedUsers.add(user);
} else {
selectedUsers.remove(user);
}
});
}
deleteSelected() async {
setState(() {
if (selectedUsers.isNotEmpty) {
List<customer> temp = [];
temp.addAll(selectedUsers);
for (customer user in temp) {
users.remove(user);
selectedUsers.remove(user);
}
}
});
}
SingleChildScrollView dataBody() {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints.expand(width: MediaQuery.of(context).size.width),
child: DataTable(
sortAscending: sort,
sortColumnIndex: iSortColumnIndex,
columns: [
DataColumn(
label: Text("FIRST NAME"),
numeric: false,
tooltip: "This is First Name",
onSort: (columnIndex, ascending) {
setState(() {
sort = !sort;
});
onSortColum(columnIndex, ascending);
}),
DataColumn(
label: Text("LAST NAME"),
numeric: false,
tooltip: "This is Last Name",
),
DataColumn(label: Text("CONTACT NO"), numeric: false, tooltip: "This is Contact No")
],
columnSpacing: 2,
rows: users
.map(
(user) => DataRow(
selected: selectedUsers.contains(user),
onSelectChanged: (b) {
print("Onselect");
onSelectedRow(b, user);
},
cells: [
DataCell(
Text(user.firstName),
onTap: () {
print('Selected ${user.firstName}');
},
),
DataCell(
Text(user.lastName),
),
DataCell(Text("${user.iContactNo}"),
showEditIcon: true, onTap: () => showEditDialog(user))
]),
)
.toList(),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
// verticalDirection: VerticalDirection.down,
children: <Widget>[
Expanded(
child: Container(
child: dataBody(),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: OutlineButton(
child: Text('SELECTED ${selectedUsers.length}'),
onPressed: () {},
),
),
Padding(
padding: EdgeInsets.all(20.0),
child: OutlineButton(
child: Text('DELETE SELECTED'),
onPressed: selectedUsers.isEmpty ? null : () => deleteSelected(),
),
),
],
),
],
),
),
);
}
void showEditDialog(customer user) {
String sPreviousText = user.iContactNo.toString();
String sCurrentText;
_controller.text = sPreviousText;
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text("Edit Contact No"),
content: new TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Enter an Contact No'),
onChanged: (input) {
if (input.length > 0) {
sCurrentText = input;
iContact = int.parse(input);
}
},
),
actions: <Widget>[
new FlatButton(
child: new Text("Save"),
onPressed: () {
setState(() {
if (sCurrentText != null && sCurrentText.length > 0) user.iContactNo = iContact;
});
Navigator.of(context).pop();
},
),
new FlatButton(
child: new Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
customer.dart class code
class customer {
String firstName;
String lastName;
int iContactNo;
customer({this.firstName, this.lastName,this.iContactNo});
static List<customer> getUsers() {
return <customer>[
customer(firstName: "Aaryan", lastName: "Shah",iContactNo: 123456897),
customer(firstName: "Ben", lastName: "John",iContactNo: 78879546),
customer(firstName: "Carrie", lastName: "Brown",iContactNo: 7895687),
customer(firstName: "Deep", lastName: "Sen",iContactNo: 123564),
customer(firstName: "Emily", lastName: "Jane", iContactNo: 5454698756),
];
}
}
Simple Answer:
Wrap your datatable with a Container() with width: double.infinity().
Container(
width: double.infinity,
child: DataTable(
..
.
My Prefered Way
You can use DataTable 2 Package at pub.dev https://pub.dev/packages/data_table_2
This package will give you the DataTable2() widget which will expand to the available space by default. Also you get more options like ColumnSize etc.
just wrap your DataTable with Sizedbox and give width to double.infinity.
SizedBox(
width: double.infinity,
child: DataTable()
)
Just wrap the data table with a container having fixed width defined and everything should work.
Even when you need multiple tables in one screen this worked well for me as of flutter 2.2.3.
final screenWidth = MediaQuery.of(context).size.width;
Scaffold(
body: SingleChildScrollView(child:Container(
child: Column(
children: [
Container(
width: screenWidth, // <- important for full screen width
padding: EdgeInsets.fromLTRB(0, 2, 0, 2),
child: buildFirstTable() // returns a datatable
),
Container(
width: screenWidth, // <- this is important
padding: EdgeInsets.fromLTRB(0, 2, 0, 2),
child: buildSecondTable() // returns a datatable
)
])
))
)
This also works for single table just wrap with container with desired width.
SingleChildScrollView(
child: Card(
child: SizedBox(
width: double.infinity,
child: DataTable(columns:_columns, rows:_rows),
),
),
),

Flutter : How to add a Header Row to a ListView

Very new to Flutter. I've been able to utilize HTTP requests for data, build a ListView, edit a Row in that List and other basics. Excellent environment.
I've managed to cobble together a badly constructed Header for a ListView but I know this isn't right. I can't get the Header text to line up properly.
I see that the Drawer Class has a DrawerHeader Class, but can't see that ListView has a ListViewHeader.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Contacts'),
actions: <Widget>[
IconButton(icon: Icon(Icons.add_circle),
onPressed: getCustData
),
],
),
//body:
body: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(child: Text('', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
Expanded(child: Text('First Name', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
Expanded(child: Text('Last Name', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
Expanded(child: Text('City', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
Expanded(child: Text('Customer Id', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
Expanded(child: Text('', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
]
),
Expanded(child:Container(
child: ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => APIDetailView(data[index])),
);
},
child: ListTile( //return new ListTile(
onTap: null,
leading: CircleAvatar(
backgroundColor: Colors.blue,
child: Text(data[index]["FirstName"][0]),
),
title: Row(
children: <Widget>[
Expanded(child: Text(data[index]["FirstName"])),
Expanded(child: Text(data[index]["LastName"])),
Expanded(child: Text(data[index]["Bill_City"])),
Expanded(child: Text(data[index]["Customer_Id"])),
]
)
),
);
}, //itemBuilder
),
),
),
]
)
);
}
}
Thanks.
Return the header as first row by itemBuilder:
ListView.builder(
itemCount: data == null ? 1 : data.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
// return the header
return new Column(...);
}
index -= 1;
// return row
var row = data[index];
return new InkWell(... with row ...);
},
);
Here's how I solved this. Thanks #najeira for getting me thinking about other solutions.
In the first body Column I used the same layout for my Header that I used for the ListTile.
Because my data ListTile, in this case, includes a CircleAvatar, all the horizontal spacing is off a bit... 5 columns where the CircleAvatar is rendered... then 4 evenly spaced columns.
So... I added a ListTile to the first body Column, a CircleAvatar with a backgroundColor of transparent, and then a Row of my 4 Headings.
ListTile(
onTap: null,
leading: CircleAvatar(
backgroundColor: Colors.transparent,
),
title: Row(
children: <Widget>[
Expanded(child: Text("First Name")),
Expanded(child: Text("Last Name")),
Expanded(child: Text("City")),
Expanded(child: Text("Id")),
]
),
),
You can add Container and ListView in Column.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("Demo App1"),
),
body: Column(
children: <Widget>[
Container(
height: 40.0,
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(4.0),
width: 100.0,
child: Text(
"Name",
style: TextStyle(fontSize: 18),
)),
Container(
padding: EdgeInsets.all(4.0),
width: 100.0,
child: Text(
"Age",
style: TextStyle(fontSize: 18),
)),
],
),
),
Expanded(
child: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(4.0),
width: 100.0,
child: Text(
"Name $index",
style: TextStyle(fontSize: 18),
)),
Container(
padding: EdgeInsets.all(4.0),
width: 100.0,
child: Text(
"Age $index",
style: TextStyle(fontSize: 18),
),
)
],
);
},
),
),
],
),
),
);
}
}
You can add a column to the first item in the item list like this
new ListView.builder(
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int index) {
if (index == 0) {
return Column(
children: <Widget>[
Header(),
rowContent(index),
],
);
} else {
return rowContent(index);
}
},
)
najeira's solution is easy and simple, but you can get the same and more flexible result without touching index.
Instead of using listView, you could use CustomScrollView & SliverList which is functionally the same as listView.
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
// you could add any widget
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.transparent,
),
title: Row(
children: <Widget>[
Expanded(child: Text("First Name")),
Expanded(child: Text("Last Name")),
Expanded(child: Text("City")),
Expanded(child: Text("Id")),
],
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => APIDetailView(data[index])),
);
},
child: ListTile(
//return ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue,
child: Text(data[index]["FirstName"][0]),
),
title: Row(
children: <Widget>[
Expanded(child: Text(data[index]["FirstName"])),
Expanded(child: Text(data[index]["LastName"])),
Expanded(child: Text(data[index]["Bill_City"])),
Expanded(child: Text(data[index]["Customer_Id"])),
],
),
),
);
},
childCount: data == null ? 0 : data.length,
),
),
],
),
);
Use DataTable widget !
That widget allows you to build a table. Code : DataTable(columns: [], rows: [],)
Example :
DataTable(
columns: [
DataColumn(label: Text('Lang.')),
DataColumn(label: Text('Year')),
],
rows: [
DataRow(cells: [DataCell(Text('Dart')), DataCell(Text('2010'))]),
DataRow(cells: [DataCell(Text('Go')), DataCell(Text('2009'))]),
DataRow(cells: [DataCell(Text('PHP')), DataCell(Text('1994'))]),
DataRow(cells: [DataCell(Text('Java')), DataCell(Text('1995'))]),
],
)
Output:
You can learn more about DataTable by watching this official video or by visiting flutter.dev
It seems what you are really looking for is the DataTable widget instead of a ListView.
It has a customizable Header including sorting options.
Read the documentation including some great examples on api.flutter.dev: Data Table CLass
I have created listview_utils package to reduce boilerplate code needed to build header and footer list items. Here's an example code using the package:
import 'package:listview_utils/listview_utils.dart';
CustomListView(
header: Container(
child: Text('Header'),
),
itemCount: items.length,
itemBuilder: (BuildContext context, int index, _) {
return ListTile(
title: Text(item['title']),
);
},
);
Disclaimer: I am maintainer of the package.
Looking for dynamic section headers according to your api data. Add this class to your project.
class _FlutterSectionListViewState extends State<FlutterSectionListView> {
/// List of total number of rows and section in each group
var itemList = [];
int itemCount = 0;
int sectionCount = 0;
#override
void initState() {
/// ----#4
sectionCount = widget.numberOfSection();
/// ----#5
itemCount = listItemCount();
super.initState();
}
/// ----#6
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
return buildItemWidget(index);
},
key: widget.key,
);
}
/// Get the total count of items in list(including both row and sections)
int listItemCount() {
itemList = [];
int rowCount = 0;
for (int i = 0; i < sectionCount; i++) {
/// Get the number of rows in each section using callback
int rows = widget.numberOfRowsInSection(i);
/// Here 1 is added for each section in one group
rowCount += rows + 1;
itemList.insert(i, rowCount);
}
return rowCount;
}
/// ----#7
/// Get the widget for each item in list
Widget buildItemWidget(int index) {
/// ----#8
IndexPath indexPath = sectionModel(index);
/// ----#9
/// If the row number is -1 of any indexPath it will represent a section else row
if (indexPath.row < 0) {
/// ----#10
return widget.sectionWidget != null
? widget.sectionWidget!(indexPath.section)
: SizedBox(
height: 0,
);
} else {
return widget.rowWidget!(indexPath.section, indexPath.row);
}
}
/// Calculate/Map the indexPath for an item Index
IndexPath sectionModel(int index) {
int? row = 0;
int section = 0;
for (int i = 0; i < sectionCount; i++) {
int item = itemList[i];
if (index < item) {
row = (index - (i > 0 ? itemList[i - 1] : 0) - 1) as int?;
section = i;
break;
}
}
return IndexPath(section: section, row: row!);
}
}
/// Helper class for indexPath of each item in list
class IndexPath {
IndexPath({required this.section, required this.row});
int section = 0;
int row = 0;
}
create your list according to your api data
List<List<Operator>> ops = [];
List<String> sections = [];
if(c.operatorStatuses.value!.availableOperators.length>0){
ops.add(c.operatorStatuses.value!.availableOperators);
sections.add("Müsait Operatörler");
}
if(c.operatorStatuses.value!.busyOperators.length>0){
ops.add(c.operatorStatuses.value!.busyOperators);
sections.add("Meşgul Operatörler");
}
if(c.operatorStatuses.value!.breakOperators.length>0){
ops.add(c.operatorStatuses.value!.breakOperators);
sections.add("Moladaki Operatörler");
}
if(c.operatorStatuses.value!.closedOperators.length>0){
ops.add(c.operatorStatuses.value!.closedOperators);
sections.add("Kapalı Operatörler");
}
show it in ui;
FlutterSectionListView(
numberOfSection: () => ops.length,
numberOfRowsInSection: (section) {
return ops[section].length;
},
sectionWidget: (section) {
if(section<ops.length){
return Container(
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(sections[section]),
),
color: Colors.grey,
);
}else{
return SizedBox();
}
},
rowWidget: (section, row) {
if(row < ops[section].length){
Operator? op = ops[section][row];
return card(op);
}else{
return SizedBox();
}
},
)
thanks to [this article][1].
NOTE : code block produces error some time according to updated data..
[1]: https://medium.com/#dharmendra_yadav/ios-like-sectioned-listview-widget-in-flutter-7cf9dab2dd1a
I use this:
body: Column(
children: [
Container(
// The header will be here
),
Expanded(
// The ListView
child: ListView.builder(
itemCount: // The length,
itemBuilder: (_, index) {
return //List Item Widget Here
}),
),
],
)
Here I've created flat_list widget which has similar specifications as in React Native's FlatList.
FlatList(
+ listHeaderWidget: const Header(),
data: items.value,
buildItem: (item, index) {
var person = items.value[index];
return ListItemView(person: person);
},
),