如何在Flutter中的GridView中为小部件设置自定义高度?

223
即使在 Container GridView 中指定了高度,我的代码仍然会产生正方形小部件。
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> widgetList = ['A', 'B', 'C'];

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Container(
        child: new GridView.count(
          crossAxisCount: 2,
          controller: new ScrollController(keepScrollOffset: false),
          shrinkWrap: true,
          scrollDirection: Axis.vertical,
          children: widgetList.map((String value) {
            return new Container(
              height: 250.0,
              color: Colors.green,
              margin: new EdgeInsets.all(1.0),
              child: new Center(
                child: new Text(
                  value,
                  style: new TextStyle(fontSize: 50.0,color: Colors.white),
                ),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

上面代码的输出如左图所示。如何获得一个自定义高度小部件的GridView,如右图所示?

output Required Output


@xskxzr 我需要展示一个宽高比为1:1的网格,但是高度需要额外增加100像素。例如,我该如何实现?请给予建议。谢谢。 - Kamlesh
17个回答

350

关键在于childAspectRatio。这个值用于确定GridView的布局。为了获得所需的宽高比,您必须将其设置为(itemWidth/ itemHeight)。解决方案如下:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> widgetList = ['A', 'B', 'C'];

  @override
  Widget build(BuildContext context) {
    var size = MediaQuery.of(context).size;

    /*24 is for notification bar on Android*/
    final double itemHeight = (size.height - kToolbarHeight - 24) / 2;
    final double itemWidth = size.width / 2;

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Container(
        child: new GridView.count(
          crossAxisCount: 2,
          childAspectRatio: (itemWidth / itemHeight),
          controller: new ScrollController(keepScrollOffset: false),
          shrinkWrap: true,
          scrollDirection: Axis.vertical,
          children: widgetList.map((String value) {
            return new Container(
              color: Colors.green,
              margin: new EdgeInsets.all(1.0),
              child: new Center(
                child: new Text(
                  value,
                  style: new TextStyle(
                    fontSize: 50.0,
                    color: Colors.white,
                  ),
                ),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

GridView没有childAspectRatio属性,除非你用计算的方式来实现。:-/ - Oliver Dixon
20
如果您正在使用GridView.builderSliverGridDelegateWithFixedCrossAxisCount,那么childAspectRatio应该在gridDelegate中设置。 - Fifer Sheep
28
如何使这个高度动态化? - Dani
4
动态高度的项 - 交错网格 普通静态高度的项 - 网格视图 - Balaji

47

最佳答案

只需使用

 childAspectRatio: (1 / .4)

其中1是宽度,0.4是高度,请根据自己的需求进行个性化定制。 完整代码请见下文。

 return GridView.count(
  crossAxisCount: 2,
  childAspectRatio: (1 / .4),
  shrinkWrap: true,
  children: List.generate(6, (index) {
    return Padding(
      padding: const EdgeInsets.all(10.0),
      child: Container(
        color: Colors.grey[600],
        child: Row(
          children: [

          ],
        ),
      ),
    );
  }),
);

enter image description here


30

几天前,我来到这里寻找一种动态更改高度的方法,当从互联网加载图片时,使用childAspectRatio无法做到这一点,因为它适用于 GridView 中的所有小部件(每个小部件的高度相同)。

这个答案可能对那些想根据每个小部件的内容获得不同高度的人有所帮助:

我发现了一个叫做 Flutter Staggered GridView by Romain Rastel 的包。使用这个包,我们可以实现很多功能,在这里查看示例。

要实现我们想要的效果,我们可以使用StaggeredGridView.count()和它的属性staggeredTiles:,将值映射到所有小部件,并应用StaggeredTile.fit(2)

示例代码:

StaggeredGridView.count(
    crossAxisCount: 4, // I only need two card horizontally
    padding: const EdgeInsets.all(2.0),
    children: yourList.map<Widget>((item) {
      //Do you need to go somewhere when you tap on this card, wrap using InkWell and add your route
      return new Card(
        child: Column(
          children: <Widget>[
             Image.network(item.yourImage),
             Text(yourList.yourText),//may be the structure of your data is different
          ],
        ),
      );
    }).toList(),

    //Here is the place that we are getting flexible/ dynamic card for various images
    staggeredTiles: yourList.map<StaggeredTile>((_) => StaggeredTile.fit(2))
        .toList(),
    mainAxisSpacing: 3.0,
    crossAxisSpacing: 4.0, // add some space
  ),
);

您可以在这里找到完整的示例(复制、粘贴和运行)。


staggeredTiles未定义,先生。 - Nicholas Jela
1
@NicholasJela,最新的软件包语法可能已经更改。请查看以下示例以获取最新信息:https://github.com/letsar/flutter_staggered_grid_view/tree/master/example/lib - Blasanka

24

所有荣誉归功于Github上的@ayRatul,感谢他提供了这个答案,这是我见过的最好和最简单的方法来实现在GridView中固定项目高度:

创建自己的自定义代理:

class SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight
    extends SliverGridDelegate {
  /// Creates a delegate that makes grid layouts with a fixed number of tiles in
  /// the cross axis.
  ///
  /// All of the arguments must not be null. The `mainAxisSpacing` and
  /// `crossAxisSpacing` arguments must not be negative. The `crossAxisCount`
  /// and `childAspectRatio` arguments must be greater than zero.
  const SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight({
    @required this.crossAxisCount,
    this.mainAxisSpacing = 0.0,
    this.crossAxisSpacing = 0.0,
    this.height = 56.0,
  })  : assert(crossAxisCount != null && crossAxisCount > 0),
        assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
        assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
        assert(height != null && height > 0);

  /// The number of children in the cross axis.
  final int crossAxisCount;

  /// The number of logical pixels between each child along the main axis.
  final double mainAxisSpacing;

  /// The number of logical pixels between each child along the cross axis.
  final double crossAxisSpacing;

  /// The height of the crossAxis.
  final double height;

  bool _debugAssertIsValid() {
    assert(crossAxisCount > 0);
    assert(mainAxisSpacing >= 0.0);
    assert(crossAxisSpacing >= 0.0);
    assert(height > 0.0);
    return true;
  }

  @override
  SliverGridLayout getLayout(SliverConstraints constraints) {
    assert(_debugAssertIsValid());
    final double usableCrossAxisExtent =
        constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
    final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
    final double childMainAxisExtent = height;
    return SliverGridRegularTileLayout(
      crossAxisCount: crossAxisCount,
      mainAxisStride: childMainAxisExtent + mainAxisSpacing,
      crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
      childMainAxisExtent: childMainAxisExtent,
      childCrossAxisExtent: childCrossAxisExtent,
      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
    );
  }

  @override
  bool shouldRelayout(
      SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight oldDelegate) {
    return oldDelegate.crossAxisCount != crossAxisCount ||
        oldDelegate.mainAxisSpacing != mainAxisSpacing ||
        oldDelegate.crossAxisSpacing != crossAxisSpacing ||
        oldDelegate.height != height;
  }
}
你可以按照以下方式使用你的自定义代理:
...
GridView.builder(
            shrinkWrap: true,
            itemCount: list.length,
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight(
                crossAxisCount: 5,
                crossAxisSpacing: 5,
                mainAxisSpacing: 5,
                height: 48.0, //48 dp of height 
...

我需要以1:1(宽度:高度)的比例显示网格,但是高度应该额外增加100像素。例如,我该怎么做?请提供建议。谢谢。 - Kamlesh
一旦你使用GridView一段时间后,你会意识到这应该被Flutter团队采纳,并成为每周的小部件。 - jbryanh

21

crossAxisCountcrossAxisSpacing和屏幕宽度决定了width,而childAspectRatio则决定了height

我进行了一些计算,以确定它们之间的关系。

var width = (screenWidth - ((_crossAxisCount - 1) * _crossAxisSpacing)) / _crossAxisCount;
var height = width / _aspectRatio;

完整的例子:

double _crossAxisSpacing = 8, _mainAxisSpacing = 12, _aspectRatio = 2;
int _crossAxisCount = 2;

@override
Widget build(BuildContext context) {
  double screenWidth = MediaQuery.of(context).size.width;

  var width = (screenWidth - ((_crossAxisCount - 1) * _crossAxisSpacing)) / _crossAxisCount;
  var height = width / _aspectRatio;

  return Scaffold(
    body: GridView.builder(
      itemCount: 10,
      itemBuilder: (context, index) => Container(color: Colors.blue[((index) % 9) * 100]),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: _crossAxisCount,
        crossAxisSpacing: _crossAxisSpacing,
        mainAxisSpacing: _mainAxisSpacing,
        childAspectRatio: _aspectRatio,
      ),
    ),
  );
}

18

mainAxisExtent: 150, // 在这里设置您想要的自定义高度

注意:动态高度(如 size.height 或其他)不起作用

Container(
            padding: EdgeInsets.all(10),
            color: Colors.grey.shade300,
            child: GridView.builder(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
                mainAxisExtent: 150, // here set custom Height You Want
              ),
              itemCount: 10,
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(5),
                    color: Colors.white,
                  ),
                  child: Text('150px'),
                );
              },
            ),
          ),

输入图像描述


最好和简单的答案 - undefined

11

在此输入图片描述

对我来说它是有效的。

示例代码:

var _crossAxisSpacing = 8;
var _screenWidth = MediaQuery.of(context).size.width;
var _crossAxisCount = 2;
var _width = ( _screenWidth - ((_crossAxisCount - 1) * _crossAxisSpacing)) / _crossAxisCount;
var cellHeight = 60;
var _aspectRatio = _width /cellHeight;

网格视图:

         GridView.builder(
                          padding: EdgeInsets.only(
                              left: 5.0, right: 5.0, top: 10, bottom: 10),
                          shrinkWrap: false,
                          itemCount: searchList.length,
                          gridDelegate:
                              SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: _crossAxisCount,childAspectRatio: _aspectRatio),
                          itemBuilder: (context, index) {
                            final item = searchList[index];
                            return Card(
                              child: ListTile(
                                title: Text(item.name,maxLines: 1,overflow: TextOverflow.ellipsis),
                                trailing: Container(
                                  width: 15,
                                  height: 15,
                                  decoration: BoxDecoration(
                                      color: item.isActive == true
                                          ? Theme.of(context).primaryColor
                                          : Colors.red,
                                      borderRadius: BorderRadius.all(
                                          Radius.circular(50))),
                                ),
                                onTap: () {

                                },
                              ),
                              elevation: 0.5,
                            );
                          },
                        ) 

8

带有选中高亮和其他未高亮的GridView

mainAxisExtent: 200(期望高度)

GridView.builder(
                    shrinkWrap: true,
                    itemCount: _images.length,
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 2,
                        crossAxisSpacing: 10,
                        mainAxisSpacing: 10,
                        mainAxisExtent: 200),
                    itemBuilder: (BuildContext context, int index) {
                      return InkWell(
                        onTap: () {
                          setState(() {
                            _selectedTasteIndex = index;
                          });
                        },
                        child: Container(
                          decoration: BoxDecoration(
                            color: _selectedTasteIndex == index
                                ? Colors.green
                                : Colors.white,
                            borderRadius: BorderRadius.circular(WodlDimens.SPACE10),
                            border: Border.all(
                              color: Colors.grey,
                              width: 1,
                            ),
                          ),
                          alignment: Alignment.center,
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: [
                              Container(
                                height: 30,
                                width: 30,
                                child: Image.asset(_images[index]),
                              ),
                              SizedBox(
                                width: 10,
                              ),
                              Text(
                                _tastes[index],
                                style: TextStyle(
                                  fontSize: 20,
                                  color: _selectedTasteIndex == index
                                      ? Colors.white
                                      : Colors.black,
                                ),
                              ),
                            ],
                          ),
                        ),
                      );
                      //return Image.network(images[index]);
                    },
                  ))

8
你可以使用 mainAxisExtent 代替 childAspectRatio。
    GridView.builder(
                physics: BouncingScrollPhysics(),
                itemCount: resumes.length,
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  mainAxisExtent: 256,
                ),
                itemBuilder: (_, index) => Container(),
              ),

这正是我正在寻找的答案,它真的像魔法一样奏效了。 - prince david

6

首先,我看到的许多解决方案是使用 childAspectRatio

但是对于不同屏幕尺寸的设备,该方案会使得在 GridView 内部容器的高度不同!

但最终,我找到了完美的解决方案:

使用 mainAxisExtent 代替 childAspectRatio,该属性定义容器的固定高度。

GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          mainAxisSpacing: CommonDimens.MARGIN_20,
          crossAxisSpacing: CommonDimens.MARGIN_20,
          crossAxisCount: 2,
          mainAxisExtent: 152,
          // childAspectRatio: 1.1, DONT USE THIS when using mainAxisExtent
        ),
        shrinkWrap: true,
        padding: EdgeInsets.zero,
        itemCount: itemList.length,
        physics: const NeverScrollableScrollPhysics(),
        itemBuilder: (_, position) => itemList[position],
      ),

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接