我尝试了大量的布局和小部件。每次我收到新的异常消息。基本上,我的目标是相互之间有多个水平列表。
请注意:我无法使用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()
],
)
);
}
答案 0 :(得分:2)
您的代码实际上并不能很好地显示您的尝试,对您的尝试所做的解释有些欠缺,但是我认为我仍然可以提供帮助。但是在将来,最好将代码尽可能地包含进来,并附上收到的错误。
以下代码显示了三个列表,这些列表最初是空的,然后被数据填充(模拟加载)。
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不知道如何调整它们的大小。但是值得一读,因为它有望解释为什么会这样。
在示例教程中查看您要尝试重新创建的内容(并没有立即发现问题中所使用的内容),问题实际上是与孩子的宽度有关。
我将为您提供一些背景知识,以了解布局是如何引起问题的。
当小部件进行布局传递时,它要么具有指定的大小,要么使用其父代的大小(或其百分比),或者使用其子代的大小。例如,如果有一个具有以下内容的空白应用程序,则容器将占据整个屏幕:
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,
),
],
),
),
);
}
}
如果您确实使用了一个孩子,那么容器将根据该孩子调整其大小。现在说您有两个孩子,并且您希望其中一个占据所有可用空间。为此,您可以使用扩展。
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为非零值,但传入的宽度限制不受限制。
该行并不是特别有用,但其余部分应该有用。
当某一行位于不提供有限宽度限制的父级中时(例如,如果该行位于水平可滚动状态中),它将尝试沿水平轴收缩包装其子级。在子级上设置柔韧性(例如,使用“扩展”)表示该子级将扩展以填充水平方向上的剩余空间。
这两个指令是互斥的。如果父母要收缩包裹其子女,则子女不能同时扩张以适合其父母。
请考虑将mainAxisSize设置为MainAxisSize.min并使用FlexFit.loose适合于灵活的子代(使用Flexible而不是Expanded)。这将使灵活的子代能够将其大小调整为小于他们将被迫占用的无限剩余空间,然后使RenderFlex收缩包装子代,而不是扩展以适应父代提供的最大约束。 / p>
不要陷入RenderFlex部件。这是因为ListView现在是水平的,因此其子级理论上可以在水平方向上扩展到无穷大。这些孩子中的一个(确实是唯一一个)是具有扩展孩子的Row。这意味着展开后的图片会尝试将其尺寸调整为最大,这恰好是无穷大。
一个相当简单的解决方案是给孩子一个固定的宽度。
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]),
),
尽管如此,您仍然知道-当我进行测试时,一旦按下“获取硬币”按钮加载了列表,它就不会真正重建,至少直到我进行热重新加载为止。这是因为颤动通量框架无法正确传播状态,这似乎是设计缺陷。如果您没有看到,请忽略此问题,但如果这样做,我建议您打开一个新问题,因为此答案已经足够长了,而且我个人不喜欢这种变化,因此您必须找到某个人其他方面可以帮助您。