如何在flutter中实现这个复杂的视图?
我正在尝试使用n列实现GridView
,并且子项应具有一定的宽高比(例如1.3),但子项的高度应该是(在Android术语中包装内容)。
我被困了,因为我理解GridView' childAspectRatio:1.3
(默认值:1)总是以相同的宽高比而不是动态内容布置孩子。
注意:儿童应根据图片的高度扩展其高度
用例:我正在尝试实现如下所示的视图,其中图像被包裹height = wrap content
,以便在高度拉伸的图像看起来很好并且形成{{ 1}}喜欢结构。
答案 0 :(得分:8)
编辑:我在0.2.0中添加了构造函数StaggeredTile.fit
。有了这个,你应该能够建立你的应用程序; - )。
第一条评论:
目前使用StaggeredGridView,布局和子渲染完全独立。所以@rmtmckenzie说,你必须得到图像大小来创建你的瓷砖。
然后,您可以使用StaggeredTile.count
构造函数为mainAxisCellCount
参数设置double值:new StaggeredTile.count(x, x*h/w)
(其中h
是图片的高度,w
是其宽度。使图块具有与图像相同的宽高比。
您想要完成的工作需要更多工作,因为您希望在图像下方有一些区域并提供一些信息。为此我认为你必须在创建它之前计算它的实际宽度并使用StaggeredTile.extent
构造函数。
我理解这并不理想,我正在研究创建布局的新方法。我希望它有助于构建像你这样的场景。
答案 1 :(得分:2)
对于任何相对简单的方法(即没有深入了解颤动中的布局如何工作),您需要在构建任何内容之前获取图像的大小。 This is an answer使用ImageProvier和ImageStream来描述如何执行此操作。
一旦您了解了图像的基本尺寸,就可以使用@ 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)
这里有两件事:
执行此类布局有existing package
为了使图片看起来不错在BoxFit.cover
小部件上使用DecorationImage
。
我刚刚使用了示例并将其修改为包含图片:
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。
祝你好运!
答案 5 :(得分:0)
python3 [file name]
这定义了当前有多少列,
crossAxisCount:
表示子代如何垂直容纳在单个列中
示例:
StaggeredTile.fit(value)
将在上方提供所需的输出