根据颤动中的动态内容调整GridView子高度

时间:2018-04-11 17:57:52

标签: gridview dart flutter staggered-gridview

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

我正在尝试使用n列实现GridView,并且子项应具有一定的宽高比(例如1.3),但子项的高度应该是(在Android术语中包装内容)。

我被困了,因为我理解GridView' childAspectRatio:1.3(默认值:1)总是以相同的宽高比而不是动态内容布置孩子。

注意:儿童应根据图片的高度扩展其高度

用例:我正在尝试实现如下所示的视图,其中图像被包裹height = wrap content,以便在高度拉伸的图像看起来很好并且形成{{ 1}}喜欢结构。

Sample Image

6 个答案:

答案 0 :(得分:8)

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

Dynamic tile sizes

第一条评论: 目前使用StaggeredGridView,布局和子渲染完全独立。所以@rmtmckenzie说,你必须得到图像大小来创建你的瓷砖。 然后,您可以使用StaggeredTile.count构造函数为mainAxisCellCount参数设置double值:new StaggeredTile.count(x, x*h/w)(其中h是图片的高度,w是其宽度。使图块具有与图像相同的宽高比。

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

我理解这并不理想,我正在研究创建布局的新方法。我希望它有助于构建像你这样的场景。

答案 1 :(得分:2)

对于任何相对简单的方法(即没有深入了解颤动中的布局如何工作),您需要在构建任何内容之前获取图像的大小。 This is an answer使用ImageProvierImageStream来描述如何执行此操作。

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

替代方法可以是存储图像大小或至少存储宽高比,无论您存储图像/网址列表(我不知道您如何填充图像列表,所以我无法帮助您)

如果您希望它完全基于图像的大小而不是网格状,那么您可以使用Flow小部件来完成。虽然有一点需要注意 - 我相信它不能很好地处理大量的物品,因为每次都要把所有的孩子都放下来,但我可能错了。如果您没有大量的项目,可以使用Flow + a SingleChildScrollView作为滚动部分。

如果您要购买大量商品(和/或想要动态加载新商品),您可能需要对CustomMultiChildLayout执行某些操作 - 我认为会更多有效但你仍然需要做一些事情来了解图像的大小。

最后一个可能的解决方案(我不确切知道它是如何工作的)将是并排的两个可滚动视图并同步它们的位置。你必须设置shrinkwrap = true,所以你可以进行滚动,你仍然需要知道每个图像的高度,这样你就可以决定将每个图像放在哪一边。

希望至少可以帮助您入门!

答案 2 :(得分:1)

回复可能为时已晚,但不知何故我做到了这一点,感谢@Romani letar 和他的 flutter_staggered_grid_view 包。 每个人都已经解释了这个库是如何工作的,我会直接进入正题。
我能够让 StaggeredTiles 的高度与图像相似,但我必须在它下面添加额外的内容,这就是为什么我在计算高度上添加了额外的 0.5 高度。幸运的是,我使用的 API 返回的是带有高度和宽度属性的图像。

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

StaggeredTile.count(2,height + 0.5); //这个构造函数有2个参数,第一个参数是1个tile应该有多少列(因为我有4列,我想要一行2个tile,Argument值是2。换句话说1 个磁贴需要 2 列)。 第二个参数是高度。我想要高度与切片宽度(即 2 列)。

我用了简单的公式:
平铺宽度/标题高度=图像宽度/图像高度
//这里我将平铺宽度设为 2,因为它占用 2 列。 我将硬编码值 0.5 添加到计算的 tile 高度,到目前为止它正在工作。

这是我的代码:

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

答案 3 :(得分:0)

这里有两件事:

  1. 执行此类布局有existing package

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

  3. package repo here

    中有很多例子

    我刚刚使用了示例并将其修改为包含图片:

    enter image description 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,
    ),),
    
        );
      }
    }
    

答案 4 :(得分:0)

首先让我告诉您我如何结束这里:

在我的应用程序中,我希望使用网格视图来显示广告卡,并且所有来自服务器数据库的数据和图像均来自服务器,并且图像大小不同。我使用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)添加。

我确定您仍然无法解决问题,因此让我们尝试一个简单的示例,这样您就无需去其他地方找到答案了:

首先向pubspec.yaml添加依赖项,现在版本为0.2.5。您可以签出最新的here

dependencies:
 flutter_staggered_grid_view: ^0.2.5

如果您要从Internet上获取数据,或者要复制粘贴此示例,则还必须设置此依存关系: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

Flutter flexible grid view example

祝你好运!

答案 5 :(得分:0)

python3 [file name]

这定义了当前有多少列,

crossAxisCount: 

表示子代如何垂直容纳在单个列中

示例:

StaggeredTile.fit(value)

将在上方提供所需的输出