我尝试了大量的布局和小部件。每次我收到一条新的异常消息。基本上,我的目标是在彼此之下有多个水平列表。
注意:我不能使用ListView.builder
,因为项目没有开始加载,而是每个项目在6秒后加载。
@override
Widget build(BuildContext context) {
return
ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: store.coins.map((coin) => ListTilWidgetFirst(coin)).toList(),
);
}
class ListTilWidgetFirst extends StatelessWidget {
ListTilWidgetFirst(this.coin);
final Channel coin;
@override
Widget build(BuildContext context) {
return Container( height: 300.0,
child: Card(
child:
ListTile(
leading: new Image.network(
coin.img,
fit: BoxFit.fill,
width: 150.0,
height: 40.0,
),
title: new Text(
coin.name,
style: new TextStyle(
fontSize: 14.0,
),
),
subtitle: new Text(
coin.name,
style: new TextStyle(fontSize: 11.0),
),
),
),
);
}
}
// to be views in anotehr class
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body:Column(
children: <Widget>[
CategorySecond()
],
)
);
}
你的代码并没有很好地展示你所尝试过的东西,你对你要做的事情的解释略显缺乏,但我认为无论如何我都可以提供帮助。但是在将来,最好包括你收到的错误所带来的代码。
以下代码显示了三个最初开始为空的列表,然后填充了数据(模拟加载)。
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Multiple horizontal lists")),
body: MyWidget(),
),
);
}
}
class UrlObject {
final String url;
UrlObject(this.url);
}
Stream<List<UrlObject>> generate(int num, Duration timeout) async* {
List<UrlObject> list = [];
int seed = Random().nextInt(200);
for (int i = 0; i < num; ++i) {
await Future.delayed(timeout);
yield list..add(UrlObject("https://loremflickr.com/640/480/dog?random=$i$seed"));
}
}
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Stream<List<UrlObject>> stream1;
Stream<List<UrlObject>> stream2;
Stream<List<UrlObject>> stream3;
@override
void initState() {
stream1 = generate(10, Duration(seconds: 1, milliseconds: 500));
stream2 = generate(20, Duration(seconds: 1));
stream3 = generate(30, Duration(seconds: 2));
}
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.vertical,
children: [
streamItems(stream1, (context, list) => HList(objects: list)),
streamItems(stream2, (context, list) => HList(objects: list)),
streamItems(stream3, (context, list) => HList(objects: list)),
],
);
}
Widget streamItems(Stream stream, Widget builder(BuildContext context, List<UrlObject> objects)) {
return StreamBuilder(
initialData: new List<UrlObject>(),
builder: (context, snapshot) => builder(context, snapshot.data),
stream: stream,
);
}
}
class HList extends StatelessWidget {
final List<UrlObject> objects;
const HList({Key key, this.objects}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 250.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: objects.length,
itemBuilder: (context, index) {
return new AspectRatio(
aspectRatio: 3.0 / 2.0,
child: Image.network(
objects[index].url,
fit: BoxFit.cover,
),
);
},
),
);
}
}
我要猜一下你遇到问题的原因。 Flutter放置东西的方式依赖于一些事情 - 特别是,窗口小部件的大小本身的方式很重要。如果它是明确的,它们倾向于使用封闭小部件的大小,或者以其他方式将它们自己定义为它们的子节点。
对于没有任何子节点的列表视图,如果您没有指定高度,它可能不知道如何调整自身大小,因此它会引发异常。如果窗口小部件对于给它们的空间来说太大,或者如果你有一些可以在无限扩展的东西内无限扩展的东西(例如,如果你在水平列表中有一个水平列表 - 内部扩展,那么窗口小部件也会抛出异常它在主轴上的最大约束,但因为它在另一个水平列表中,最大值是无穷大,这会导致异常)。
编辑:
我已经修改了上面的代码来使用流来显示它。请注意,如果您不想生成List <...>,则实际上不需要 - 您可以返回单个项目。如果你这样做,你只需要在状态小部件的状态中跟踪成员变量中的列表。您不需要调用setState,因为StreamBuilder处理它的一部分。我个人更喜欢编写一个小函数来聚合结果,因为它会产生更清晰的代码,但这是个人偏好,可能并不是最佳实践。
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Multiple horizontal lists")),
body: MyWidget(),
),
);
}
}
class UrlObject {
final String url;
UrlObject(this.url);
}
Stream<UrlObject> generate(int num, Duration timeout) async* {
int seed = Random().nextInt(200);
for (int i = 0; i < num; ++i) {
await Future.delayed(timeout);
yield UrlObject("https://loremflickr.com/640/480/dog?random=$i$seed");
}
}
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Stream<UrlObject> stream1;
Stream<UrlObject> stream2;
Stream<UrlObject> stream3;
@override
void initState() {
stream1 = generate(10, Duration(seconds: 1, milliseconds: 500));
stream2 = generate(20, Duration(seconds: 1));
stream3 = generate(30, Duration(seconds: 2));
}
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.vertical,
children: [
StreamedItemHolder(stream: stream1, builder: (context, list) => HList(objects: list)),
StreamedItemHolder(stream: stream2, builder: (context, list) => HList(objects: list)),
StreamedItemHolder(stream: stream3, builder: (context, list) => HList(objects: list)),
],
);
}
}
typedef Widget UrlObjectListBuilder(BuildContext context, List<UrlObject> objects);
class StreamedItemHolder extends StatefulWidget {
final UrlObjectListBuilder builder;
final Stream<UrlObject> stream;
const StreamedItemHolder({Key key, @required this.builder, @required this.stream}) : super(key: key);
@override
StreamedItemHolderState createState() => new StreamedItemHolderState();
}
class StreamedItemHolderState extends State<StreamedItemHolder> {
List<UrlObject> items = [];
@override
Widget build(BuildContext context) {
return StreamBuilder(
builder: (context, snapshot) {
if (snapshot.hasData) items.add(snapshot.data);
return widget.builder(context, items);
},
stream: widget.stream,
);
}
}
class HList extends StatelessWidget {
final List<UrlObject> objects;
const HList({Key key, this.objects}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 250.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: objects.length,
itemBuilder: (context, index) {
return new AspectRatio(
aspectRatio: 3.0 / 2.0,
child: Image.network(
objects[index].url,
fit: BoxFit.cover,
),
);
},
),
);
}
}
正如我在评论中提到的,我没有使用完全相同的小部件结构或对象,部分原因是因为你没有包含一个完全和部分显示它们的例子,因为它对你学习的东西比简单的更有用从别人那里复制粘贴代码。
编辑2:这是我现在已经意识到的一个非常长的解释...... TLDR是你需要指定每个列表项的宽度,否则flutter不知道如何调整它们的大小。但它值得一读,因为它有希望解释为什么会这样。
查看示例教程,了解您正在尝试重新创建的内容(这并不是显而易见的是您在问题中使用的内容),问题实际上与子项的宽度有关。
我会给你一些关于如何在颤动中完成布局的背景知识,因为这会导致你出现问题。
当窗口小部件执行布局传递时,它具有指定的大小,使用其父级的大小(或其百分比),或使用其子级的大小。例如,如果具有以下的空白应用程序,则Container将占用整个屏幕:
import 'package:flutter/material.dart';
void main() => runApp(MyWidget());
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(color: Colors.red);
}
}
如果你在那个容器上指定一个高度和一个宽度,它实际上仍会保持相同的大小,这在你想到它之前会有点混乱。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
height: 200.0,
width: 200.0,
);
}
}
发生的事情是容器想要将自己的大小调整到你给它的大小,但如果它这样做,它将不知道如何在其父级中定位自己。所以它保持与父母相同的大小。
如果将它包装在一个小部件中,允许它不受其父级的大小限制,例如Align或Center,那么它将是你告诉它的大小。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.red,
height: 200.0,
width: 200.0,
),
);
}
}
这会在左上角产生一个红色方块。但是如果您没有指定高度和宽度,但确实指定了一个子项,它现在将其自身调整为其子项。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Align(
alignment: Alignment.center,
child: Container(
color: Colors.red,
child: Text(
"Some Text",
style: TextStyle(color: Colors.black, decoration: null),
),
),
),
);
}
}
这导致屏幕中间出现一个文本小部件(忽略Directionality小部件,它只是因为我没有使用MaterialApp,如果你不使用MaterialApp,它需要Text)。如果您希望将其放在列中,则行为会再次更改。没有孩子,容器将自己调整到允许的最小值(恰好为零)。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Align(
alignment: Alignment.center,
child: Column(
children: <Widget>[
Container(
color: Colors.red,
),
],
),
),
);
}
}
如果您确实使用了孩子,那么容器将自己调整为该孩子的大小。现在假设您有两个孩子,并且您希望其中一个孩子占用所有可用空间。为此你使用Expanded。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Align(
alignment: Alignment.center,
child: Column(
children: <Widget>[
Expanded(
child: Container(
color: Colors.red,
child: Text(
"Some Text",
style: TextStyle(color: Colors.black, decoration: null),
),
),
),
Container(
color: Colors.blue,
child: Text(
"Some More Text",
style: TextStyle(color: Colors.black, decoration: null),
),
),
],
),
),
);
}
}
请注意扩展如何使主(垂直)轴中的所有可用空间,而不是水平轴。现在已经介绍了,让我们继续讨论您正在使用的示例。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: ListView(
children: <Widget>[
Container(
child: IntrinsicHeight(
child: Row(
children: <Widget>[
Expanded(
child: Container(color: Colors.blue),
),
Text(
"Some Text",
style: TextStyle(color: Colors.white, decoration: null),
),
],
),
),
)
],
),
);
}
}
这里有几件事需要注意。第一个是ListView,它是垂直的,就像你遇到问题的硬币列表一样。并且子项是一行,可以水平扩展以适合ListView的大小。 IntrinsicHeight基本上是将行描述为其中最大的子(文本)的高度,以便彩色容器也是相同的高度。
现在,要将此列表切换为水平,技术上只需要一步 - 将ListView的scrollDirection
设置为Axis.horizontal
。然而,这是一个很大的...然后这会导致你无疑看到的异常。
RenderFlex子项具有非零flex,但传入宽度约束是无限制的。
这条线不是特别有用,但其余部分应该是。
当行在父级中不提供有限宽度约束时,例如,如果它在水平可滚动条件下,它将尝试沿水平轴收缩包装其子项。为孩子设置弹性(例如,使用Expanded)表示孩子要扩展以在水平方向上填充剩余空间。
这两个指令是互斥的。如果父级要收缩包装其子级,则子级不能同时扩展以适合其父级。
考虑将mainAxisSize设置为MainAxisSize.min并使用FlexFit.loose适合灵活的子项(使用Flexible而不是Expanded)。这将允许灵活的子节点将其自身的大小设置为小于它们将被强制占用的无限剩余空间,然后将使RenderFlex收缩包装子节点而不是扩展以适应父节点提供的最大约束。
不要陷入RenderFlex部分。它的说法是因为你的ListView现在是水平的,它的'儿童现在理论上可以在水平方向上扩展到无限远。其中一个孩子(真正唯一的一个)是一个有一个膨胀孩子的行。这意味着扩展将尝试大小到行的最大值,这恰好是无穷大。
相当简单的解决方案是给孩子一个固定的宽度。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
width: 200.0,
child: IntrinsicHeight(
child: Row(
children: <Widget>[
Expanded(
child: Container(color: Colors.blue),
),
Text(
"Some Text",
style: TextStyle(color: Colors.white, decoration: null),
),
],
),
),
)
],
),
);
}
}
这会扩展到屏幕的完整大小,但是您将在行/垂直Listview中进行包装,因此它将自行调整大小。
需要考虑的一件事是,您可能希望根据父高度调整孩子的大小,而不是在孩子上设置明确的宽度。这确实意味着父母需要一个明确的高度。
这是一个例子:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Container(
height: 200.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: [1, 2]
.map(
(v) => AspectRatio(
aspectRatio: 1.5,
child: Container(
color: Colors.red,
child: Row(
children: <Widget>[
Expanded(
child: Container(color: Colors.blue),
),
Text(
"Some Text",
style: TextStyle(color: Colors.white, decoration: null),
),
],
),
),
),
)
.toList(),
),
),
),
);
}
}
因此,对于本教程,它看起来像这样:
class HomeScreen extends StatefulWidget {
@override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> with StoreWatcherMixin<HomeScreen> {
CoinStore store;
@override
void initState() {
super.initState();
store = listenToStore(coinStoreToken);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flux Crypto Ticker'),
actions: <Widget>[
RaisedButton(
color: Colors.blueGrey,
onPressed: () {
print("Loading coins");
loadCoinsAction.call();
},
child: Text('Get Coins'),
)
],
),
body: Column(
children: <Widget>[
Container(
height: 200.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: store.coins.map((coin) => CoinWidget(coin)).toList(),
),
),
],
),
);
}
}
class CoinWidget extends StatelessWidget {
CoinWidget(this.coin);
final Coin coin;
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 2.0,
child: Container(
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(width: 5.0),
),
child: Card(
elevation: 10.0,
color: Colors.lightBlue,
child: Row(
children: <Widget>[
Expanded(
child: ListTile(
title: Text(coin.name),
leading: CircleAvatar(
child: Text(
coin.symbol,
style: TextStyle(
fontSize: 13.0,
),
),
radius: 90.0,
foregroundColor: Colors.white,
backgroundColor: Colors.amber,
),
subtitle: Text("\$${coin.price.toStringAsFixed(2)}"),
),
)
],
),
)),
);
}
}
为了记录,您实际上应该在该教程中使用构建器。你不是,所以它实际上必须实例化这些对象中的每一个,然后首先创建列表,而不是在滚动视图时按需构建它们。要解决此问题,您可以使用以下命令替换ListView:
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: store.coins.length,
itemBuilder: (context, i) => CoinWidget(store.coins[i]),
),
只是你知道 - 当我测试它时,一旦按下Get Coins按钮加载列表,它实际上不会重建,至少在我进行热重新加载之前。那是因为颤振通量框架没有正确传播状态,这似乎是一个设计缺陷。如果你没有看到,那么忽略这一点,但如果你这样做,我建议开一个新的问题,因为这个答案已经足够长了,我个人不喜欢助焊剂,所以你必须找人否则帮助它。