Reputation: 463
I have a page like this:
Axis.vertical
as defaultAxis.horizontal
, itemCount = 3
. To avoid the error, I set the height:50
width:100
for Container
in thisHowever, I want to find some way for it to display well on all screens, specifically in the 2nd ListView:
Expanded
(1: 1: 1) and it fits the right of the screen, can't scroll.Width
of Container
will auto-resize based on Expanded flex
.fontSize
of Text
and Height
of Container
will auto-resize based on maxLine
So pls help me, this is JSON file
[
{
"id": 1,
"continent": "North America",
"countries": [
{
"name": "United States",
"capital": "Washington, D.C.",
"language": ["English"]
},
{
"country": "Canada",
"capital": "Ottawa",
"language": ["English", "French"]
},
{
"country": "Mexico",
"capital": "Mexico City",
"language": ["Spanish"]
}
]
},
{
"id": 2,
"continent": "Europe",
"countries": [
{
"country": "Germany",
"capital": "Berlin",
"language": ["German"]
},
{
"country": "United Kingdom",
"capital": "London",
"language": ["English"]
}
]
},
{
"id": 3,
"continent": "Asia",
"country": [
{
"country": "Singapore",
"capital": "Singapore",
"language": ["English","Malay","Mandarin","Tamil"]
}
]
}
]
And this is the main file
import 'package:flutter/material.dart';
import 'model/continent_model.dart';
import 'services/continent_services.dart';
class ContinentPage2 extends StatefulWidget {
ContinentPage2() : super();
@override
_ContinentPageState createState() => _ContinentPageState();
}
class _ContinentPageState extends State<ContinentPage2> {
List<Continent> _continent;
@override
void initState() {
super.initState();
ContinentServices.getContinent().then((continents) {
setState(() {
_continent = continents;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')),
body: ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return SizedBox(height: 10);
},
shrinkWrap: true,
itemCount: null == _continent ? 0 : _continent.length,
itemBuilder: (context, index) {
return Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[
Row(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[
Expanded(flex: 1, child: Text(_continent[index].continent)),
Expanded(
flex: 2,
child: Container(
height: 50,
child: ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return SizedBox(width: 10);
},
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: null == _continent ? 0 : _continent[index].country.length,
itemBuilder: (context, countryIndex) {
print(_continent[index].country[countryIndex].name);
return Row(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[
Container(
width: 100,
child: Column(children: <Widget>[
Text(
_continent[index].country[countryIndex].name,
),
Text(_continent[index].country[countryIndex].capital,
style: TextStyle(
color: Colors.blue,
)),
]))
]);
})))
])
]);
}));
}
}
Upvotes: 1
Views: 8958
Reputation: 5601
import 'dart:math'; //useful to check the longest list of countries
class ContinentPage2 extends StatefulWidget {
ContinentPage2() : super();
@override
_ContinentPageState createState() => _ContinentPageState();
}
class _ContinentPageState extends State<ContinentPage2> {
List<Continent> _continent = []; //initialize empty to avoid problem with the table
int maxLength; //we need to check for the longest list of countries in all the continents
@override
void initState() {
super.initState();
ContinentServices.getContinent().then((continents) {
setState(() {
_continent = continents;
maxLength = _continent.map((continent) => continent.country.length).reduce(max);
/*max uses dart:math, I made a list of the length of all the List<countries>
and then check for the list with the maximum length*/
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')),
body: SingleChildScrollView(
child: Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
columnWidths: {0: FlexColumnWidth(0.5)}, //accept a map and gives to the index the width I want, in this case the first widget a flex of 0.5, and the others will have the default 1, so it's the same as using expanded with flex: 1 and then flex: 2
children: [
for (Continent continent in _continent)
TableRow(children: [
TableCell(
verticalAlignment: TableCellVerticalAlignment.middle,
child: Text(continent.continent),
),
for (Country country in continent.country)
Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(country.name),
),
),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
country.capital,
style: TextStyle(
color: Colors.blue,
),
textAlign: TextAlign.center,
)),
),
//OPTION 1 create a String with all the languages as a single text
if(country.languages.isNotempty)
Flexible(
child: MyLanguages(
lang: country.languages.reduce((prev, curr) => '$prev, $curr')
)
),
// Option 2 using a for to create multiple text widgets inside the column widget with all the languages per country
if(country.languages.isNotempty)
for(String language in country.language)
Flexible(
child: Text(language)
)
]),
),
if (continent.country.length < maxLength)
for (int i = 0; i < maxLength - continent.country.length; i++)
const SizedBox()
])
],
)));
}
}
// Used in Option 1
class MyLanguages extends StatelessWidget{
final String languages;
MyLanguages({String lang, Key key}) : languages = lang.replaceFirst(',', ' and', lang.lastIndexOf(',').clamp(0, double.infinity)), super(key: key);
@override
Widget build(BuildContext context){
return Text(languages);
}
}
With all the nested Rows and Columns I thought the best idea was tu use a Table widget, it allows to make a table of x rows with y columns, but there is only one problem, all the rows need to have the same amount of columns (rectangular or square table). So to work around that I made an int maxLength, then:
maxLength = _continent.map((continent) => continent.country.length).reduce(max);
Makes a List with all the lengths of the countries and with reduce max I retrieve the value of the the list with the maximum length, useful for the next part
if (continent.country.length < maxLength)
for (int i = 0; i < maxLength - continent.country.length; i++)
const SizedBox()
Here I check if I made a full row or if that row has less widgets that the maximum I will have in any row, and then just add dummys const SizedBox() to trick the Table that it has the same amount of widgets in every Row (if you comment this part it will give you an error because it needs to have the same amount of columns).
columnWidths: {0: FlexColumnWidth(0.5)},
This line works as flex with Expanded, it accepts a map Map where the key int is the index of the column in each row, in this case 0 and I give it a flex 0.5, and the others keep the default 1.0. There are other TableColumnWidth classes if you want to give it a try
Finally I wrapped the Countries in a Padding with vertical 5 to make it look like your separatorbuilder SizedBox(height: 10), inside the Columns I wrapped the text with a Flexible and FittedBox with fit.scaleDown so it reduce the size of the text in case it fills all the width and added the textAlign.center to center the text. In case you have a big large text it will reduce it's size to fill. If you don't want that you can keep only the Text widget and add maxLines and softWrap
Text(
country.capital,
style: TextStyle(color: Colors.blue),
textAlign: TextAlign.center,
maxLines: 2, softWrap: true,
)
Or you could check the Auto_Size_Text Package if you really want more customization with the size of the text
Result with fittedBox
Result without FittedBox and maxLines: 2, softwrap: true
Upvotes: 2