Flutter Multi 水平列表

7sk*_*ies 0 flutter

我尝试了很多布局和小部件。每次我收到新的异常消息。基本上,我的目标是在彼此之下有多个水平列表。

注意:我无法使用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()
          ],
        )
    );
  }
Run Code Online (Sandbox Code Playgroud)

rmt*_*zie 5

您的代码并没有真正很好地展示您所尝试的内容,并且您对尝试执行的操作的解释也略有缺乏,但我认为无论如何我都可以提供帮助。但将来,最好包含您收到的错误的代码。

以下代码显示了三个列表,它们最初为空,然后填充数据(模拟加载)。

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,
            ),
          );
        },
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

我将猜测您遇到问题的原因。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,
            ),
          );
        },
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

正如我在评论中提到的,我没有使用与您完全相同的小部件结构或对象,部分原因是您没有提供完整展示它们的示例,部分原因是它对您来说学习一些东西比简单地学习更有用复制粘贴别人的代码。

编辑2:这是一个非常长的解释,我现在已经意识到......TLDR是你需要指定每个列表项的宽度,否则flutter不知道如何调整它们的大小。但它值得一读,因为它有望解释为什么会出现这种情况。

查看您尝试重新创建的内容的示例教程(您在问题中使用的内容并不是很明显),问题实际上与子项的宽度有关。

我将为您提供一些关于如何在 flutter 中完成布局的背景知识,因为这就是导致您出现问题的原因。

当小部件进行布局传递时,它要么具有指定的大小,要么使用其父级的大小(或其百分比),要么使用其子级的大小。例如,如果有一个包含以下内容的空白应用程序,则容器占据整个屏幕:

import 'package:flutter/material.dart';

void main() => runApp(MyWidget());

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.red);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您指定该容器的高度和宽度,它实际上仍然会保持相同的大小,但在您考虑之前这会有点令人困惑。

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      height: 200.0,
      width: 200.0,
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

发生的情况是容器想要将自身大小调整为您给定的大小,但如果它这样做,它将不知道如何将自己定位在其父级中。因此它保持与父级相同的大小。

如果您将其包装在一个小部件中,使其不受其父级大小的限制,例如“对齐”或“居中”,那么它将是您指定的大小。

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,
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

这会导致左上角出现一个红色方块。但是,如果您不指定高度和宽度,但指定了一个子项,那么它现在会根据其子项调整自身大小。

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),
          ),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

这会导致屏幕中间出现一个文本小部件(忽略方向性小部件,它就在那里,因为我没有使用 MaterialApp,如果您不使用 MaterialApp,则文本需要它)。如果您随后想将其放入列中,则行为会再次发生变化。如果没有子容器,容器会将自身大小调整为允许的最小值(恰好为零)。

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,
            ),
          ],
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您确实使用了子项,容器将根据该子项调整自身大小。现在假设您有两个孩子,您希望其中一个孩子占据所有可用空间。为此,您可以使用 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),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意展开如何使其占用主(垂直)轴上的所有可用空间,但不占用水平轴上的所有可用空间。现在已经介绍完毕,让我们继续讨论您正在使用的示例。

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),
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

这里有几件事需要观察。第一个是垂直的 ListView,就像您遇到问题的硬币列表一样。子级是水平扩展适应 ListView 大小的一行 。IntrinsicHeight 基本上是告诉该行是其最大子项(文本)的高度,以便彩色容器也将具有相同的高度。

现在,要将此列表切换为水平列表,技术上只需要一个步骤 - 将 ListView 设置scrollDirectionAxis.horizontal. 然而,这是一个很大的问题......这会导致您无疑已经看到的异常。

RenderFlex 子级具有非零弯曲,但传入的宽度约束是无界的。

该行并不是特别有帮助,但其余部分应该很有帮助。

当一行位于不提供有限宽度约束的父行中时,例如,如果它位于水平可滚动行中,它将尝试沿水平轴收缩包裹其子行。在子项上设置 Flex(例如使用 Expanded)表示子项将扩展以填充水平方向上的剩余空间。

这两个指令是互斥的。如果父级要收缩其子级,则子级不能同时扩展以适合其父级。

考虑将 mainAxisSize 设置为 MainAxisSize.min 并使用 FlexFit.loose 适合灵活的子项(使用灵活而不是扩展)。这将允许灵活的子级将自身大小调整为小于它们将被迫占用的无限剩余空间,然后将导致 RenderFlex 收缩包裹子级而不是扩展以适应父级提供的最大约束。

不要陷入 RenderFlex 部分。它的意思是,因为您的 ListView 现在是水平的,所以理论上它的子级现在可以在水平方向上扩展到无穷大。这些子项之一(实际上是唯一的一个)是具有 Expanded 子项的 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),
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

它会扩展到屏幕的整个尺寸,但您将包裹在行/垂直列表视图中,因此它会正确调整自身大小。

需要考虑的一件事是,您可能希望根据父级高度调整子级的大小,而不是为子级设置显式宽度。但这确实意味着父母需要明确的身高。

这是一个例子:

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(),
          ),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

因此对于本教程来说,它看起来像这样:

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)}"),
                  ),
                )
              ],
            ),
          )),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

郑重声明,您实际上应该在该教程中使用构建器。你不是,所以它实际上必须实例化每个对象,然后首先创建列表,而不是在视图滚动时按需构建它们。要解决此问题,您可以将 ListView 替换为:

ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: store.coins.length,
              itemBuilder: (context, i) => CoinWidget(store.coins[i]),
            ),
Run Code Online (Sandbox Code Playgroud)

但你知道 - 当我对此进行测试时,一旦通过按“获取硬币”按钮加载列表,它实际上就不会重建,至少在我进行热重新加载之前是这样。这是因为颤振通量框架没有正确传播状态,这似乎是一个设计缺陷。如果您没有看到这一点,请忽略这一点,但如果您这样做了,我建议您开一个新问题,因为这个答案已经足够长了,而且我个人不喜欢通量,所以您必须找人否则可以帮忙。