Flutter Multi Горизонтальные списки - PullRequest
0 голосов
/ 14 сентября 2018

Я перепробовал тонну макетов и виджетов. Каждый раз я получаю новое сообщение об исключении. По сути, я стремлюсь иметь несколько горизонтальных списков друг под другом.

Примечание: я не могу использовать 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()
          ],
        )
    );
  }

1 Ответ

0 голосов
/ 14 сентября 2018

Ваш код на самом деле не очень хорошо показывает, что вы пытались, а вашего объяснения того, что вы пытаетесь сделать, немного не хватает, но я думаю, что могу помочь в любом случае.Но в будущем было бы лучше включить код, который вы получили, вместе с ошибкой, которую вы получили.

В следующем коде показаны три списка, которые изначально пустые, а затем заполняются данными (имитируязагрузка).

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 выкладывает вещи, зависит от нескольких вещей, в частности, важно то, как виджеты сами изменяют размер.Они имеют тенденцию использовать размер вложенного виджета, если он явный, или иным образом определять размер своих дочерних элементов.

В случае списка, у которого еще нет дочерних элементов, он может не знать, как самому определить размер.если вы не укажете высоту, она выдаст исключение.Виджеты также будут генерировать исключения, если они слишком велики для предоставленного им пространства или если у вас есть что-то, что может бесконечно расширяться внутри чего-то, что может расширяться бесконечно (например, если у вас есть горизонтальный список в горизонтальном списке - внутренний расширяетсяк наибольшему ограничению, которое он имеет на своей главной оси, но поскольку он находится в другом горизонтальном списке, максимум равен бесконечности, что вызывает исключение).

РЕДАКТИРОВАТЬ:

Я изменил приведенный выше кодчтобы показать это с помощью потоков.Обратите внимание, что вам на самом деле не нужно генерировать список <...>, если вы не хотите - вы можете вместо этого возвращать отдельные элементы.Если бы вы сделали это, вам просто нужно было бы отслеживать список в переменной-члене в состоянии виджета с состоянием.Вам не нужно вызывать 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 заключается в том, что вам нужно указать ширину каждого элемента списка, иначе флаттер не знает, как их размер.Но это стоит прочитать, так как, надеюсь, объясняет, почему это так.

Рассматривая пример учебного пособия для того, что вы пытаетесь воссоздать (не сразу было очевидно, что вы использовали в вопросе),проблема на самом деле связана с шириной дочерних элементов.

Я расскажу вам немного о том, как макет выполняется во флаттере, поскольку именно это вызывает у вас проблемы.

КогдаВиджет выполняет передачу макета, либо имеет заданный размер, либо использует размер родительского элемента (или его процент), либо размер дочернего элемента.Например, если у вас пустое приложение со следующим, Контейнер занимает весь экран:

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

Это приводит к красному квадрату в левом верхнем углу.Но если вместо этого вы не укажете высоту и ширину, а укажете дочерний элемент, он теперь масштабирует себя до своего дочернего элемента.

Это приводит к появлению текстового виджета в центре экрана (игнорируйте виджет «Направленность», он просто есть, потому что я не использую 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,
            ),
          ],
        ),
      ),
    );
  }
}

Если вы используете дочерний элемент, контейнер будет сам соответствовать этому дочернему элементу. Теперь скажите, что у вас двое детей, и вы хотите, чтобы один из них занял все доступное пространство. Для этого вы используете 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 ненулевое сгибание, но входящие ограничения ширины не ограничены.

Эта строка не особенно полезна, но остальная часть должна быть.

Когда строка находится в родительском элементе, который не обеспечивает ограничение конечной ширины, например, если она находится в горизонтальной прокрутке, она будет пытаться сжать дочерние элементы вдоль горизонтальной оси. Установка сгибания на дочернем элементе (например, с помощью 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),
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

Это расширяется до полного размера экрана, но вы будете оборачиваться в список строк по вертикали и по вертикали, поэтому он будет правильно масштабироваться.

Одна вещь, которую следует учитывать, состоит в том, что вместо установки явной ширины для дочернего элемента вы можете захотеть, чтобы размер вашего дочернего элемента определялся в зависимости от высоты родительского элемента. Это означает, что родителю нужна явная высота.

Вот пример этого:

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]),
            ),

Просто чтобы вы знали, хотя - когда я проверял это, он фактически не перестраивался после загрузки списка нажатием кнопки «Получить монеты», по крайней мере до тех пор, пока я не выполнил горячую перезагрузку.Это потому, что состояние не распространяется должным образом структурой потока флаттера, которая кажется недостатком проекта.Если вы этого не видите, тогда игнорируйте это, но если вы это сделаете, я бы порекомендовал открыть новый выпуск об этом, так как этот ответ уже более чем достаточно длинен, и мне лично не нравится флюс, поэтому вам придется найти кого-тоеще, чтобы помочь с этим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...