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

我正在尝试创建一个横向滚动列表的项目在 Flutter,我希望该列表只采取必要的高度基于其子。通过设计“ ListView试图扩展以适应其跨方向的可用空间”(从 颤动医生) ,我也注意到它占据了整个视窗的高度,但有没有办法使它不这样做?理想情况下,类似于这样的东西(显然不起作用) :

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

我意识到这样做的一种方法是将 ListView包装在一个固定高度的 Container中。不过,我不一定知道这些物品的高度:

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

我能够通过将一个 Row嵌套在一个 SingleChildScrollView中,在一个 Column中嵌套一个 mainAxisSize: MainAxisSize.min,从而拼凑出一个“解决方案”。然而,对我来说,这似乎不是一个解决方案:

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

只要将 ListViewshrink属性设置为 true,它就可以适应空间而不是扩展。

例如:

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

使用 ConstrainedBox设置 minHeightmaxHeight

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


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


),
)

使用扩展

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


},)
)  ,

mainAxisSize: MainAxisSize.min包装 Column的小部件对我来说很有用。你可以试着在这里改变 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(),
));

我使用了一个容器的水平列表视图构建器,所以停止容器采取全高度我刚刚包装它与中心,它工作得非常好。

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

通过应用上述所有解决方案,还没有找到合适的答案,这有助于我们设置水平列表视图,如果我们不知道高度。我们必须在所有情况下设置高度。因此,我应用了下面的解决方案,没有指定任何高度的列表项目为我工作。@ sindrenm 在他的问题中已经提到了。所以在找到可行的解决办法之前,我会一直坚持下去。我应用了 shrinkWrap: true,但是它会沿着主轴收缩 ListView。(只适用于垂直的 scrollView) ,而不是沿着问题中提到的横轴。所以在不久的将来,任何人都可以在生产中使用这个解决方案,它对我的所有水平列表非常有用。

//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(myModelObj){
return Column(
children[
widget1(),
widget2().... etc..
]);
}

据我所知,您不能在垂直 ListView 中使用水平 ListView 并动态设置其高度。如果您的需求允许(不允许无限滚动、少量元素等) ,则可以使用 SingleChildScrollView。

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

这非常类似于这里提出的一个问题: Builder ()小部件的交叉轴占据了整个 Screen 的高度

我相信列表视图要求每个项目具有相同的十字轴大小。这意味着在这种情况下,我们无法为每个物体设置一个唯一的高度,交叉轴的大小是固定的。

如果您想让子元素自己唯一地控制可滚动元素的高度,那么您可以使用 SingleChildScrollView划船 < em > (注意: 请确保设置 scrollDirection.Axis.level) output

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

使用 SingleChildScrollViewscrollDirection: Axis.horizontalRow内。

巨大的优势:

  1. 你需要多少小工具并不重要。

  2. You 不需要知道高度 of the widgets.

    Widget _horizontalWrappedRow(List data) {
    
    
    var list = <Widget>[SizedBox(width: 16)]; // 16 is start 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,
    ),
    );
    

    }

缺点:

  • 不是懒惰构建的项,因此对于长列表,可以避免使用它。

如果您不希望 listview 强制您的小部件适合横轴,请确保将它们放置在 Column ()或 Row ()小部件中,而不是直接放置在 listview ()中

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

],
);

(这个答案涵盖了当物品具有不同大小时的变通方法。如果您的项目具有相同的大小/高度,请参阅 this answer了解 ListView方法。)

如果项目有不同的大小,它不应该做一个 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).

另一方面,Row小部件总是同时显示所有的孩子,所以很容易找到最大的一个。因此,如果您没有太多的项目(也许少于100个项目左右) ,您可以使用 Row代替。如果需要滚动,请用 SingleChildScrollView包装 Row

demo gif

示例中使用的代码:

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

我想出了一个方法来做水平滚动 ListView动态调整尺寸。列表可以有无限的项目,但是每个项目应该具有相同的高度。

(如果您只有几个项目,或者您的项目必须有不同的高度,请参阅 这个答案。)

Key point:

这个想法是为了测量一个项目,让我们称之为“原型”。基于假设所有项目都是相同的高度,然后我们设置 ListView的高度来匹配原型项目。

这样,我们可以避免硬编码任何值,并且列表可以自动调整大小,例如,当用户在系统中设置较大的字体并导致卡片扩展时。

演示:

a demo gif of infinite items scrolling

密码:

I made a custom widget, called PrototypeHeight (name inspired from IntrinsicHeight). To use it, simply pass in a Widget as a "prototype", then pass in a horizontally scrolling ListView, for example:

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

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

解说:

让我简单解释一下这是如何工作的。

简单地说,Stack小部件的大小取决于它的所有“非定位”子部件。基本上,如果一个 Stack有3个孩子,但其中2个是 Positioned,那么 Stack将简单地匹配其余的“非定位”孩子。

在这种情况下,我使用“原型项目”作为一个非定位的子项目,以帮助 Stack决定其高度(这将是相同的高度作为原型)。然后我使用一个非常宽的 SizedBox来帮助 Stack决定它的宽度(宽度将与父设备的宽度相同,通常是设备的屏幕宽度,因为记住: 约束减少,大小增加)。我还添加了一个 Opacity小部件来隐藏原型(这也通过跳过部分呈现管道来提高性能) ,以及一个 IgnorePointer小部件来防止用户交互,以防原型项上有按钮。

接下来,我将使用 Positioned.fill使其子节点(ListView)匹配 Stack的大小,而 Stack又将匹配原型项的高度,并匹配父节点的宽度。这将是传递给 ListView的约束,因此 ListView将创建这个大小的“ viewport”,这正是我们想要实现的。

这里实际上涵盖了 Flutter 的一些概念,我打算稍后制作一个视频教程。