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

吃可爱长大的小学妹 提交于 2020-06-17 09:51:05

问题


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:

  • 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,
                                            )),
                                      ]))
                                ]);
                              })))
                ])
              ]);
            }));
  }
}

回答1:


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



来源:https://stackoverflow.com/questions/62207592/flutter-how-to-auto-resize-height-width-fontsize-in-listview-based-on-expande

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!