简体   繁体   English

Flutter:水平列表视图的最小高度

[英]Flutter: Minimum height on horizontal list view

I'm trying to create a horizontal scrolling list of items in Flutter, and I want that list to only take up the necessary height based on its children.我正在尝试在 Flutter 中创建项目的水平滚动列表,并且我希望该列表仅根据其子项占用必要的高度。 By design “ ListView tries to expand to fit the space available in its cross-direction” (from the Flutter docs ), which I also notice in that it takes up the whole height of the viewport, but is there a way to make it not do this?通过设计“ ListView尝试扩展以适应其横向可用空间”(来自Flutter 文档),我还注意到它占据了视口的整个高度,但有没有办法让它不做这个? Ideally something similar to this (which obviously doesn't work):理想情况下与此类似(显然不起作用):

new ListView(
  scrollDirection: Axis.horizontal,
  crossAxisSize: CrossAxisSize.min,
  children: <Widget>[
    new ListItem(),
    new ListItem(),
    // ...
  ],
);

I realize that one way to do this is by wrapping the ListView in a Container with a fixed height.我意识到做到这一点的一种方法是将ListView包装在具有固定高度的Container中。 However, I don't necessarily know the height of the items:但是,我不一定知道物品的高度:

new Container(
  height: 97.0,
  child: new ListView(
    scrollDirection: Axis.horizontal,
    children: <Widget>[
      new ListItem(),
      new ListItem(),
      // ...
    ],
  ),
);

I was able to hack together a “solution” by nesting a Row in a SingleChildScrollView in a Column with a mainAxisSize: MainAxisSize.min .我能够通过在具有mainAxisSize: MainAxisSize.minColumn中的SingleChildScrollView中嵌套一个Row来组合一个“解决方案”。 However, this doesn't feel like a solution, to me:但是,对我来说,这并不是一个解决方案:

new Column(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    new SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: new Row(
        children: <Widget>[
          new ListItem(),
          new ListItem(),
          // ...
        ],
      ),
    ),
  ],
);

Just set shrink property of ListView to true and it will fit the space rather than expanding.只需将ListView shrink属性设置为true ,它就会适合空间而不是扩展。

Example:例子:

ListView(
        shrinkWrap: true, //just set this property
        padding: const EdgeInsets.all(8.0),
        children: listItems.toList(),
      ),

Use ConstrainedBox to set minHeight and maxHeight使用ConstrainedBox设置minHeightmaxHeight

ConstrainedBox(
  constraints: new BoxConstraints(
    minHeight: 35.0,
    maxHeight: 160.0,
  ),

  child: new ListView(
    shrinkWrap: true,
    children: <Widget>[
      new ListItem(),
      new ListItem(),
    ],

  ),
)

Similar to Gene's answer, but I didn't have a fixed amount of widgets to add.类似于 Gene 的回答,但我没有固定数量的小部件要添加。

You also don't need to know the heights of the widgets.也不需要知道小部件的高度

  Widget _horizontalWrappedRow(List data) {

    var list = <Widget>[Container(width: 16)]; // container is left padding

    //create a new row widget for each data element
    data.forEach((element) {
       list.add(MyRowItemWidget(element));
    });

    // add the list of widgets to the Row as children
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: list,
      ),
    );

  }  

This is very similar to a question asked here: Flutter ListView.builder() widget's cross Axis is taking up the entire Screen height这与此处提出的问题非常相似: Flutter ListView.builder() widget's cross Axis is take up the entire Screen height

I believe ListViews require every item to have the same Cross Axis size.我相信 ListViews 要求每个项目都具有相同的横轴大小。 That means in this case, we are unable to set a unique height for every object, the cross axis size is fixed.这意味着在这种情况下,我们无法为每个对象设置唯一的高度,交叉轴的大小是固定的。

If you want to have the height of a scrollable uniquely controlled by the child itself, then you can use a SingleChildScrollView paired with a Row (note: make sure to set the scrollDirection: Axis.horizontal)如果您想让子项本身唯一控制可滚动的高度,那么您可以使用与Row配对的SingleChildScrollView (注意:确保设置 scrollDirection: Axis.horizo​​ntal) 输出

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SafeArea(
          child: Container(
              // height: 100, // no need to specify here
              color: Colors.white,
              child: SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                  children: <Widget>[
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                    Container(
                      height: 300,
                      width: 300,
                      color: Colors.amber[600],
                      child: const Center(child: Text('Entry A')),
                    ),
                    Container(
                      height: 100,
                      color: Colors.amber[500],
                      child: const Center(child: Text('Entry B')),
                    ),
                    Container(
                      height: 50,
                      width: 100,
                      color: Colors.amber[100],
                      child: const Center(child: Text('Entry C')),
                    ),
                  ],
                ),
              )),
        ),
      ),
    );
  }
}

I'm using a horizontal listview builder of container so to stop the Container for taking the full height i just wrapped it with Center and it worked perfectly.我正在使用容器的水平列表视图构建器,以便停止 Container 获取全高,我只是用 Center 将它包裹起来,它工作得很好。

itemBuilder: (context, i){
              return Center(child: word_on_line(titles[i]));
          }

As far as I understand, you can't have a horizontal ListView inside a vertical ListView and have its height dynamically set.据我了解,您不能在垂直 ListView 中包含水平 ListView 并动态设置其高度。 If your requirements allow (no infinite scrolling, small amount of elements, etc), you could use a SingleChildScrollView instead.如果您的要求允许(没有无限滚动、少量元素等),您可以改用 SingleChildScrollView。

SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: [...],
  ),
);

Use Expanded使用扩展

 Expanded(
        child:ListView.separated(
          shrinkWrap: true,
        padding: EdgeInsets.all(10),
        separatorBuilder: (BuildContext context, int index) {
          return Align(
            alignment: Alignment.centerRight,
            child: Container(
              height: 0.5,
              width: MediaQuery.of(context).size.width / 1.3,
              child: Divider(),
            ),
          );
        },
        itemCount: dataMasterClass.length,
        itemBuilder: (BuildContext context, int index) {

          Datum datum = dataMasterClass[index];
         return sendItem(datum);

        },)
      )  ,

By applying all the above solution there is no suitable answer found yet, which help us to set horizontal Listview If we don't know the height.通过应用上述所有解决方案,还没有找到合适的答案,这有助于我们设置水平 Listview 如果我们不知道高度。 we must have to set height in all the cases.我们必须在所有情况下都设置高度。 so I applied the below solution which works for me without specifying any height for the list items.所以我应用了以下对我有用的解决方案,而没有为列表项指定任何高度。 which @sindrenm has already mentioned in his question. @sindrenm 在他的问题中已经提到过。 so I would like to go with it till a feasible solution will found.所以我想继续下去,直到找到可行的解决方案。 I applied shrinkWrap: true but it will shrink the ListView along the main-axis.我应用了 shrinkWrap: true 但它会沿主轴缩小 ListView 。 (only for vertical scrollView) and Not along the cross-axis as asked in the question. (仅适用于垂直滚动视图)而不是问题中所问的沿横轴。 so in the nearest future, anyone can go with this solution in the production as well, it works great for me for my all horizontal lists.所以在不久的将来,任何人都可以在生产中使用这个解决方案,它对我的​​所有水平列表都很有用。

//below declared somewhere in a class above any method and assume list has filled from some API.
var mylist = List<myModel>();


SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          children: [
                for (int index = 0; index < mylist.length; index++) 
                      listItem(mylist[index]);
         ],
        ),
      ),
    ),

    listItem(){
      return Column(
         children[
            widget1(),
            widget2().... etc..
      ]);
    }

Wrapping the widget by Column with mainAxisSize: MainAxisSize.min worked for me.mainAxisSize: MainAxisSize.minColumn包装小部件对我mainAxisSize: MainAxisSize.min You can try to change the height here.您可以尝试在此处更改height

var data = [
      {'name': 'Shopping', 'icon': Icons.local_shipping},
      {'name': 'Service', 'icon': Icons.room_service},
      {'name': 'Hotel', 'icon': Icons.hotel},
      {'name': 'More', 'icon': Icons.more}
];

new Container(
        constraints: new BoxConstraints(
          minHeight: 40.0,
          maxHeight: 60.0,
        ),
        color: Colors.transparent,
        child: new ListView(
          scrollDirection: Axis.horizontal,
          children: data
              .map<Widget>((e) => Column(
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      new Container(
                        width: 40,
                        height: 50, // try to change this.
                        color: Colors.transparent,
                        margin: EdgeInsets.only(right: 20, left: 4),
                        child: ClipOval(
                          child: Container(
                            padding: EdgeInsets.all(4),
                            color: Colors.white,
                            child: Icon(
                              e["icon"],
                              color: Colors.grey,
                              size: 30,
                            ),
                          ),
                        ),
                      ),
                    ],
                  ))
              .toList(),
        ));

This should not be done with a ListView , and for a good reason:这不应该用ListView来完成,这是有充分理由的:

A ListView could potentially have a lot of items (or even unlimited), but figuring out "the biggest one" requires going through every item, which is against the whole point of using a ListView (dynamically load each item using the builder method). ListView可能有很多项目(甚至无限),但要找出“最大的项目”需要遍历每个项目,这违背了使用ListView的全部意义(使用builder方法动态加载每个项目)。

On the other hand, a Row widget always lay out all children at once, so it's easy to find the biggest one.另一方面, Row小部件总是同时布置所有子级,因此很容易找到最大的一个。 So if you don't have too many items (perhaps less than 100 items or so), you can use Row instead.因此,如果您没有太多项目(可能少于 100 个左右),您可以使用Row代替。 If you need scrolling, wrap Row with a SingleChildScrollView .如果您需要滚动,请使用SingleChildScrollView包装Row

演示 gif

Code used in the example:示例中使用的代码:

Column(
  children: [
    Container(
      color: Colors.green.shade100,
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Row(
          children: [
            FlutterLogo(),
            FlutterLogo(size: 100),
            for (int i = 0; i < 50; i++) FlutterLogo(),
          ],
        ),
      ),
    ),
    const Text('The row has ended.'),
  ],
)

If you do not want the listview to force your widget to fit in the cross axis make suure that they are placed in a Column() or Row() widget instead of placing it directly in a listview()如果您不希望 listview 强制您的小部件适合横轴,请确保将它们放置在 Column() 或 Row() 小部件中,而不是直接将其放置在 listview() 中

new ListView(
  scrollDirection: Axis.horizontal,
  crossAxisSize: CrossAxisSize.min,
  children: <Widget>[
    Column(
      children:[
    new ListItem(),
    new ListItem(),
       ], 
     )
    
  ],
);

I figured out a way to do dynamic sizing for a horizontally scrolling ListView .我想出了一种为水平滚动的ListView进行动态调整大小的方法。 The list can have infinite items, but each item should have the same height.列表可以有无限项,但每个项应具有相同的高度。

(If you only have a few items, or if your items must have different heights, see this answer instead.) (如果您只有几件物品,或者您的物品必须有不同的高度,请参阅此答案。)

Key point:关键:

The idea is to measure one item, let's call it a "prototype".这个想法是测量一个项目,我们称之为“原型”。 And based on the assumption that all items are the same height, we then set the height of the ListView to match the prototype item.并基于所有项目高度相同的假设,然后我们将ListView的高度设置为与原型项目匹配。

This way, we can avoid having to hardcode any values, and the list can automatically resize when, for example, the user sets a larger font in the system and caused the cards to expand.这样,我们可以避免硬编码任何值,并且列表可以自动调整大小,例如,当用户在系统中设置较大的字体并导致卡片展开时。

Demo:演示:

无限项目滚动的演示 gif

Code:代码:

I made a custom widget, called PrototypeHeight (name inspired from IntrinsicHeight ).我制作了一个名为PrototypeHeight的自定义小部件(名称灵感来自IntrinsicHeight )。 To use it, simply pass in a Widget as a "prototype", then pass in a horizontally scrolling ListView , for example:要使用它,只需传入一个Widget作为“原型”,然后传入一个水平滚动的ListView ,例如:

PrototypeHeight(
  prototype: MyItem(),
  listView: ListView.builder(
    scrollDirection: Axis.horizontal,
    itemBuilder: (_, index) => MyItem(),
  ),
)

And the implementation of PrototypeHeight is as follows (you can just paste it somewhere and start using it, or modify it as you need): PrototypeHeight的实现如下(可以直接粘贴到某处开始使用,也可以根据需要进行修改):

class PrototypeHeight extends StatelessWidget {
  final Widget prototype;
  final ListView listView;

  const PrototypeHeight({
    Key? key,
    required this.prototype,
    required this.listView,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        IgnorePointer(
          child: Opacity(
            opacity: 0.0,
            child: prototype,
          ),
        ),
        SizedBox(width: double.infinity),
        Positioned.fill(child: listView),
      ],
    );
  }
}

Explaination:解释:

Let me briefly explain how this works.让我简要解释一下这是如何工作的。

Simply put, the size of a Stack widget, depends on all of its "non-positioned" children.简而言之, Stack小部件的大小取决于它所有的“非定位”子级。 Basically, if a Stack has 3 children, but 2 of which are Positioned , then the Stack will simply match the remaining "non-positioned" child.基本上,如果Stack有 3 个孩子,但其中 2 个是Positioned ,那么Stack将简单地匹配剩余的“非定位”孩子。

In this case, I'm using the "prototype item" as one of the non-positioned children, to help the Stack decide its height (which will be the same height as the prototype).在这种情况下,我使用“原型项”作为非定位子项之一,以帮助Stack确定其高度(与原型的高度相同)。 Then I'm using a very wide SizedBox to help the Stack decide its width (which will be the same width as the parent's width, typically the device's screen width, because remember: constraints go down, size go up. ).然后我使用一个非常宽的SizedBox来帮助Stack决定它的宽度(这将与父级的宽度相同,通常是设备的屏幕宽度,因为请记住: 约束 go 向下,尺寸 go 向上。 )。 I'm also adding an Opacity widget to hide the prototype (this also increases performance by skipping parts of the rendering pipeline), and an IgnorePointer widget to prevent user interactions, in case there are buttons on the prototype item.我还添加了一个Opacity小部件来隐藏原型(这也通过跳过部分渲染管道来提高性能),以及一个IgnorePointer小部件来防止用户交互,以防原型项目上有按钮。

Next, I'm using a Positioned.fill to make its child (the ListView ) match the size of the Stack , which in turn, would match the height of the prototype item, and match the width of the parent.接下来,我使用Positioned.fill使其子项( ListView )与 Stack 的大小相匹配,而Stack的大小又将与原型项的高度相匹配,并与父项的宽度相匹配。 This will be the constraint passed to the ListView , so the ListView will create a "viewport" of that size, which is exactly what we wanted to achieve.这将是传递给ListView的约束,因此ListView将创建一个该大小的“视口”,这正是我们想要实现的。

This actually covers quite a few concepts in Flutter.这实际上涵盖了Flutter中的不少概念。 I plan to make a video tutorial in a bit.我打算稍后制作一个视频教程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM