Kel
Kel

Reputation: 463

Flutter: How to AUTO RESIZE height, width, fontSize in ListView based on Expanded and itemCount

I have a page like this:

  1. 1st Listview: Axis.vertical as default
  2. 2nd Listview inside 1st Listview: Axis.horizontal, itemCount = 3. To avoid the error, I set the height:50 width:100 for Container in this

However, I want to find some way for it to display well on all screens, specifically in the 2nd ListView:

enter image description here

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

Answers (1)

EdwynZN
EdwynZN

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

With FittedBox

Result without FittedBox and maxLines: 2, softwrap: true

enter image description here

Upvotes: 2

Related Questions