根据flutter中的动态内容调整GridViewchild高度

Adjust GridView child height according to the dynamic content in flutter

如何在flutter中实现这个复杂的视图?

我正在尝试实现具有 n 列的 GridView,并且 child 应该具有特定的纵横比(比如 1.3),但是 child 的高度应该是(用 Android 术语包装内容)。

我被卡住了,因为据我所知,GridView 的 childAspectRatio:1.3(默认值:1)总是以相同的宽高比而不是动态内容布置 child。

注意: Child要根据图片的高度展开高度

用例: 我正在尝试实现如下所示的视图,其中包裹了图像 height = wrap content 以防拉伸高度的图像看起来不错并形成一个 StaggeredGridView 结构。

这里有两件事:

  1. 有一个existing package做这样的布局

  2. 为了使 图像看起来不错DecorationImage 小部件上使用 BoxFit.cover

package repo here

中有大量示例

我只是使用了其中一个示例并对其进行了修改以包含图片:

class GridViewExample extends StatefulWidget {
  @override
  _GridViewExampleState createState() => new _GridViewExampleState();
}

class _GridViewExampleState extends State<GridViewExample> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Padding(
        padding: const EdgeInsets.all(8.0),
        child: new StaggeredGridView.countBuilder(
  crossAxisCount: 4,
  itemCount: 8,
  itemBuilder: (BuildContext context, int index) => new Container(
        decoration: new BoxDecoration(
          image: new DecorationImage(
            image: new NetworkImage("https://i.imgur.com/EVTkpZL.jpg"),
            fit: BoxFit.cover
          )
        )

        ),

  staggeredTileBuilder: (int index) =>
      new StaggeredTile.count(2, index.isEven ? 2 : 1),
  mainAxisSpacing: 4.0,
  crossAxisSpacing: 4.0,
),),

    );
  }
}

对于任何相对简单的方法(即没有深入了解 flutter 中的布局如何工作),您将需要在构建任何东西之前获取图像的大小。 that describes how to do that by using ImageProvier and ImageStream.

一旦您知道图像的基本尺寸,您就可以使用@aziza 的 flutter_staggered_grid_view 示例。

另一种方法是在存储 images/urls 列表的任何地方存储图像大小或至少纵横比(我不知道你是如何填充图像列表的,所以我不能帮助你。

如果您希望它完全基于图像的大小而不是 grid-like,您可以在滚动部分使用 Flow widget. There is a caveat to flow though - I believe that it won't handle a large amount of items very well as it would have to lay all of the children out each time, but I could be wrong about that. If you don't have a huge amount of items, you could use Flow + a SingleChildScrollView

如果您要拥有大量项目(and/or 想要执行动态加载新项目之类的操作),您可能需要使用 CustomMultiChildLayout - 我认为它会更有效率,但你仍然需要做一些事情来了解图像的大小。

最后一个可能的解决方案(虽然我不知道具体如何工作)是拥有两个可滚动视图 side-by-side 并同步它们的位置。你必须设置 shrinkwrap=true 才能进行滚动,而且你仍然必须知道每张图片的高度,这样你才能决定将每张图片放在哪一侧。

希望至少能帮助您入门!

编辑: 我在 0.2.0 中添加了构造函数 StaggeredTile.fit。有了它,您应该能够构建您的应用程序 ;-)。

第一条评论: 现在使用 StaggeredGridView,布局和子渲染是完全独立的。正如@rmtmckenzie 所说,您必须获得图像大小才能创建您的图块。 然后,您可以使用 StaggeredTile.count 构造函数和 mainAxisCellCount 参数的双精度值:new StaggeredTile.count(x, x*h/w)(其中 h 是图像的高度,w 是图像的宽度。以便图块具有与您的图像相同的纵横比。

您想要完成的工作需要更多工作,因为您希望在图像下方有一个区域,其中包含一些信息。为此,我认为您必须在创建图块之前计算图块的实际宽度并使用 StaggeredTile.extent 构造函数。

我知道这并不理想,我目前正在研究一种创建布局的新方法。我希望它能帮助构建像您这样的场景。

首先让我告诉你我是如何来到这里的:

在我的应用程序中,我想要一个网格视图来显示我的广告卡,所有来自服务器数据库的数据和图像都来自服务器,并且图像大小不同。我使用 FutureBuilder 将这些数据映射到 GridView。首先我尝试使用:

double cardWidth = MediaQuery.of(context).size.width / 3.3;
double cardHeight = MediaQuery.of(context).size.height / 3.6;
//....
GridView.count(
  childAspectRatio: cardWidth / cardHeight,
  //..

如您所见,它不会对所有卡片动态显示。我和你一样来到这里,并尝试使用所有很棒的答案,你必须解决一些问题才能理解如何,但任何这些答案都完全解决了我的问题。

使用 @RomainRastel 回答并感谢他的 StaggeredGridView 包。我不得不使用 StaggeredGridView.count 作为我的构造函数来映射所有卡片,对于 staggeredTiles 属性 我不得不再次映射所有卡片并为每个 StaggeredTile.fit(2).[=26= 添加]

我相信你还是没明白,所以让我们尝试一个简单的例子,这样你就不需要去其他地方寻找答案了:

首先添加对pubspec.yaml的依赖,现在版本是0.2.5。您可以查看最新的 here.

dependencies:
 flutter_staggered_grid_view: ^0.2.5

如果你从互联网上获取数据或者你要复制粘贴这个例子,你还必须添加这个依赖:http: ^0.12.0.

import 'package:flutter/material.dart';

//this is what you need to have for flexible grid
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';

//below two imports for fetching data from somewhere on the internet
import 'dart:convert';
import 'package:http/http.dart' as http;

//boilerplate that you use everywhere
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flexible GridView",
      home: HomePage(),
    );
  }
}

//here is the flexible grid in FutureBuilder that map each and every item and add to a gridview with ad card
class HomePage extends StatelessWidget {
  //this is should be somewhere else but to keep things simple for you,
  Future<List> fetchAds() async {
    //the link you want to data from, goes inside get
    final response = await http
        .get('https://blasanka.github.io/watch-ads/lib/data/ads.json');

    if (response.statusCode == 200) return json.decode(response.body);
    return [];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Dynamic height GridView Demo"),
      ),
      body: FutureBuilder<List>(
          future: fetchAds(),
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            if (snapshot.hasData) {
              return new Padding(
                padding: const EdgeInsets.all(4.0),
                //this is what you actually need
                child: new StaggeredGridView.count(
                  crossAxisCount: 4, // I only need two card horizontally
                  padding: const EdgeInsets.all(2.0),
                  children: snapshot.data.map<Widget>((item) {
                    //Do you need to go somewhere when you tap on this card, wrap using InkWell and add your route
                    return new AdCard(item);
                  }).toList(),

                  //Here is the place that we are getting flexible/ dynamic card for various images
                  staggeredTiles: snapshot.data
                      .map<StaggeredTile>((_) => StaggeredTile.fit(2))
                      .toList(),
                  mainAxisSpacing: 3.0,
                  crossAxisSpacing: 4.0, // add some space
                ),
              );
            } else {
              return Center(
                  child:
                      new CircularProgressIndicator()); // If there are no data show this
            }
          }),
    );
  }
}

//This is actually not need to be a StatefulWidget but in case, I have it
class AdCard extends StatefulWidget {
  AdCard(this.ad);

  final ad;

  _AdCardState createState() => _AdCardState();
}

class _AdCardState extends State<AdCard> {
  //to keep things readable
  var _ad;
  String _imageUrl;
  String _title;
  String _price;
  String _location;

  void initState() {
    setState(() {
      _ad = widget.ad;
      //if values are not null only we need to show them
      _imageUrl = (_ad['imageUrl'] != '')
          ? _ad['imageUrl']
          : 'https://uae.microless.com/cdn/no_image.jpg';
      _title = (_ad['title'] != '') ? _ad['title'] : '';
      _price = (_ad['price'] != '') ? _ad['price'] : '';
      _location = (_ad['location'] != '') ? _ad['location'] : '';
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      semanticContainer: false,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.all(Radius.circular(4.0)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Image.network(_imageUrl),
          Text(_title),
          Text('$ $_price'),
          Text(_location),
        ],
      ),
    );
  }
}

如果您有任何问题,请点击这里 complete example in a git repository

祝你好运!

crossAxisCount: 

这定义了存在的列数,

StaggeredTile.fit(value)

表示子项在垂直方向上适合单列的数量

示例:

crossAxisCount: 2,
staggeredTileBuilder: (int index) => new StaggeredTile.fit(1),

会给你上面想要的输出

回复可能为时已晚,但由于@Romani letsar 和他的 flutter_staggered_grid_view 包,我以某种方式实现了这一点。 大家都已经解释了这个库是如何工作的,我就开门见山了。
我能够使 StaggeredTiles 的高度与图像相似,但我必须在它下面添加额外的内容,这就是为什么我在计算出的高度上额外增加了 0.5 的高度。幸运的是 API 我正在玩的是返回图像及其高度和宽度 属性。

快速简单的计算。
crossAxisCount: 4 //这里我定义了我想要的列数。

StaggeredTile.count(2,height + 0.5); //这个构造函数接受2个参数,第一个参数是1个tile应该有多少列(因为我有4列,我想连续2个tile,参数值为2。在换句话说,1 个图块需要 2 列)。 第二个参数是高度。我想要高度和 resect to tile width(即 2 列)。

我使用了简单的公式:
平铺 width/title 高度=图像 width/image 高度
//这里我将图块宽度设置为 2,因为它有 2 列。 我在计算出的图块高度中添加了硬编码值 0.5,目前它正在运行。

这是我的代码:

class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            child: FutureBuilder(
              future: NetworkHelper.getData(),
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                if (snapshot.data == null)
                  return Center(child: CircularProgressIndicator());
                else {
                  var data = snapshot.data;
                  print(data);
                  List<dynamic> jsonData = jsonDecode(snapshot.data);
                  return StaggeredGridView.countBuilder(
                    crossAxisCount: 4,
                    itemCount: jsonData.length,
                    itemBuilder: (BuildContext context, int index) {
                      JsonModelClass models =
                          JsonModelClass.fromJson(jsonData[index]);
                      String url = models.urls.regular;
                      return GestureDetector(
                        onTap: () {
                          print(url);
                        },
                        child: Card(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                            children: [
                              Image.network(url),
                              Container(
                                child: Row(
                                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                                  crossAxisAlignment: CrossAxisAlignment.center,
                                  children: [
                                    Text("${models.user.name}"),
                                    Card(
                                      child: Padding(
                                        padding: const EdgeInsets.all(3.0),
                                        child: Row(
                                          mainAxisAlignment:
                                              MainAxisAlignment.spaceEvenly,
                                          children: [
                                            Icon(
                                              Icons.favorite,
                                              color: Colors.red,
                                            ),
                                            SizedBox(
                                              width: 6,
                                            ),
                                            Text("${models.likes}")
                                          ],
                                        ),
                                      ),
                                    ),
                                  ],
                                ),
                              )
                            ],
                          ),
                        ),
                      );
                    },
                    staggeredTileBuilder: (int index) {
                      JsonModelClass models =
                          JsonModelClass.fromJson(jsonData[index]);
                      var height = (models.height * 2) /
                          models.width; //calculating respective 'height' of image in view, 'model.height' is image's original height received from json.
                      return StaggeredTile.count(
                          2,
                          height +
                              0.5); //Adding extra 0.5 space for other content under the image
                    },
                    mainAxisSpacing: 4,
                    crossAxisSpacing: 4,
                  );
                }
              },
            ),
          ),
        );
      }
    }
My Final Output

这个问题的简单解决方法是添加“键:列数”。 我正在使用版本 StaggeredGridview 0.4.0 。您需要添加的只是 Key 参数。当您调整浏览器大小时,该小部件将再次重新加载 window。你可以找到 example code here.

例如,浏览器处于全宽状态,您调整 window 的大小。我也在使用响应式设计来检测 - 它是小屏幕(小部件 < 800)、中等尺寸屏幕(> 800 & < 1200)、大屏幕(> 1200)的移动视图。

StaggeredGridView.countBuilder(
      primary: false,
      shrinkWrap: true,
      key: ObjectKey(ResponsiveWidget.isSmallScreen(context) ? 2 :4), // **add this line**
      crossAxisCount: ResponsiveWidget.isSmallScreen(context) ? 4 : 8,
      itemCount: ResponsiveWidget.isSmallScreen(context) ? 4 : _productsList.length,
      itemBuilder: (BuildContext context, int index) {
        BannersResult product = _productsList.elementAt(index);
        return Home_CustBanner_twobytwo_Item(
          home_banner_list_item: product,
          heroTag: 'categorized_products_grid',
        );
      },

      staggeredTileBuilder: (int index) => new StaggeredTile.fit(2),
      mainAxisSpacing: 15.0,
      crossAxisSpacing: 15.0,
    ),