Flutter 垂直滚动视图中的水平列表视图

我试图实现一个非常普遍的行为,现在是有一个水平列表内的另一个小部件,是在同一时间滚动。想象一下 IMDb 应用程序的主屏幕:

enter image description here

因此,我希望有一个小部件,垂直滚动几个项目上他们。在它的顶部,应该有一个水平的 ListView,后续的一些项目称为 motivationCard。在列表和卡片之间也有一些标题。

我的 Widget上有这样的东西:

@override
Widget build(BuildContext context) => BlocBuilder<HomeEvent, HomeState>(
bloc: _homeBloc,
builder: (BuildContext context, HomeState state) => Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
Text(
Strings.dailyTasks,
),
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: tasks.length,
itemBuilder: (BuildContext context, int index) =>
taskCard(
taskNumber: index + 1,
taskTotal: tasks.length,
task: tasks[index],
),
),
Text(
Strings.motivations,
),
motivationCard(
motivation: Motivation(
title: 'Motivation 1',
description:
'this is a description of the motivation'),
),
motivationCard(
motivation: Motivation(
title: 'Motivation 2',
description:
'this is a description of the motivation'),
),
motivationCard(
motivation: Motivation(
title: 'Motivation 3',
description:
'this is a description of the motivation'),
),
],
),
),
);

这就是我得到的错误:

I/flutter (23780): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter (23780): The following assertion was thrown during performResize():
I/flutter (23780): Horizontal viewport was given unbounded height.
I/flutter (23780): Viewports expand in the cross axis to fill their container and constrain their children to match
I/flutter (23780): their extent in the cross axis. In this case, a horizontal viewport was given an unlimited amount of
I/flutter (23780): vertical space in which to expand.

我试过了:

  • Expanded小部件包装 ListView

  • SingleChildScrollView > ConstrainedBox > IntrinsicHeight包装列

  • CustomScrollView作为父元素,在 SliverChildListDelegate中包含 SliverList和 List

这些都不起作用,我继续得到同样的错误。这是一件很平常的事情,不应该有什么困难,不知为什么我就是不能让它起作用:

任何帮助都将不胜感激,谢谢!

编辑:

我以为 这个可以帮助我,但它没有。

115365 次浏览

Well, Your Code Work Fine with wrapping your- ListView.builder with Expanded Widget & setting mainAxisSize: MainAxisSize.min, of Column Widget.

E.x Code of what you Have.

 body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Headline',
style: TextStyle(fontSize: 18),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 15,
itemBuilder: (BuildContext context, int index) => Card(
child: Center(child: Text('Dummy Card Text')),
),
),
),
Text(
'Demo Headline 2',
style: TextStyle(fontSize: 18),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (ctx,int){
return Card(
child: ListTile(
title: Text('Motivation $int'),
subtitle: Text('this is a description of the motivation')),
);
},
),
),
],
),

enter image description here

Update:

Whole page Is Scroll-able with - SingleChildScrollView.

body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Headline',
style: TextStyle(fontSize: 18),
),
SizedBox(
height: 200.0,
child: ListView.builder(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 15,
itemBuilder: (BuildContext context, int index) => Card(
child: Center(child: Text('Dummy Card Text')),
),
),
),
Text(
'Demo Headline 2',
style: TextStyle(fontSize: 18),
),
Card(
child: ListTile(title: Text('Motivation $int'), subtitle: Text('this is a description of the motivation')),
),
Card(
child: ListTile(title: Text('Motivation $int'), subtitle: Text('this is a description of the motivation')),
),
Card(
child: ListTile(title: Text('Motivation $int'), subtitle: Text('this is a description of the motivation')),
),
Card(
child: ListTile(title: Text('Motivation $int'), subtitle: Text('this is a description of the motivation')),
),
Card(
child: ListTile(title: Text('Motivation $int'), subtitle: Text('this is a description of the motivation')),
),
],
),
),

enter image description here

Screenshot:

enter image description here


class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 7,
itemBuilder: (_, i) {
if (i < 2)
return _buildBox(color: Colors.blue);
else if (i == 3)
return _horizontalListView();
else
return _buildBox(color: Colors.blue);
},
),
);
}


Widget _horizontalListView() {
return SizedBox(
height: 120,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (_, __) => _buildBox(color: Colors.orange),
),
);
}


Widget _buildBox({Color color}) => Container(margin: EdgeInsets.all(12), height: 100, width: 200, color: color);
}

We have to use SingleScrollView inside another SingleScrollView, using ListView will require fixed height

SingleChildScrollView(
child: Column(
children: <Widget>[
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [Text('H1'), Text('H2'), Text('H3')])),
Text('V1'),
Text('V2'),
Text('V3')]))

If someone gets the renderview port was exceeded error. warp your ListView in a Container widget and give it the height and width property to fix the issue

Column(
children: <Widget>[
Text(
Strings.dailyTasks,
),
Container(
height: 60,
width: double.infinity,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: tasks.length,
itemBuilder: (BuildContext context, int index) =>
taskCard(
taskNumber: index + 1,
taskTotal: tasks.length,
task: tasks[index],
),
),
)
]
)

for Web Chome you have to add MaterialScrollBehavior for horizontal scrolling to work. see(Horizontal listview not scrolling on web but scrolling on mobile) I demonstrate how to use the scrollcontroller to animate the list both left and right.

import 'package:flutter/gestures.dart';
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods and getters like dragDevices
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}


return MaterialApp(
title: 'Flutter Demo',
scrollBehavior: MyCustomScrollBehavior(),
)




class TestHorizontalListView extends StatefulWidget {
TestHorizontalListView({Key? key}) : super(key: key);




@override
State<TestHorizontalListView> createState() => _TestHorizontalListViewState();
}


class _TestHorizontalListViewState extends State<TestHorizontalListView> {
List<String> lstData=['A','B','C','D','E','F','G'];
  

final ScrollController _scrollcontroller = ScrollController();


_buildCard(String value)
{
return Expanded(child:Container(
margin: const EdgeInsets.symmetric(vertical: 20.0),
width:300,height:400,child:Card(child: Expanded(child:Text(value,textAlign: TextAlign.center, style:TextStyle(fontSize:30))),)));
}


void _scrollRight() {
_scrollcontroller.animateTo(
_scrollcontroller.position.maxScrollExtent,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}


void _scrollLeft() {
_scrollcontroller.animateTo(
0,
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}
_segment1()
{
return     SingleChildScrollView(child:
Expanded(child:
Container(height:300,
width:MediaQuery.of(context).size.width,
child:Row(children: [
FloatingActionButton.small(onPressed: _scrollRight, child: const Icon(Icons.arrow_right),),
Expanded(child:Scrollbar(child:ListView.builder(
itemCount: lstData.length,
controller: _scrollcontroller,
scrollDirection: Axis.horizontal,
itemBuilder:(context,index)
{
return _buildCard(lstData[index]);
})
,),
),
FloatingActionButton.small(onPressed: _scrollLeft, child: const Icon(Icons.arrow_left),),
]))
,
)
);


}
@override
void initState() {
//   TODO: implement initState
super.initState();


}
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("horizontal listview",)),body:
segment1(),
);
}
}

I tried in this code and I fixed my problem I hope solved your want it.

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

You just have to fix your height of your Listview (by wrapping it in a SizedBox for example).

This is because the content of your listview can't be known before the frame is drawn. Just imagine a list of hundreds of items.. There is no way to directly know the maximum height among all of them.

Horizontal ListView inside Vertical ListView using Builder

None of the answers proved to solve my issue, which was to have a horizontal ListView inside a Vertical ListView while still using ListBuilder (which is more performant than simply rendering all child elements at once).

original question image

Turned out it was rather simple. Simply wrap your vertical list child inside a Column, and check if index is 0 (or index % 3 == 0) then render the horizontal list.

Seems to work fine:

final verticalListItems = [];
final horizontalListItems = [];


ListView.builder(
shrinkWrap: true,
itemCount: verticalListItems.length,
itemBuilder: (context, vIndex) {
final Chat chat = verticalListItems[vIndex];


return Column( // Wrap your child inside this column
children: [
// And then conditionally render your Horizontal list
if (vIndex == 0) ListView.builder(itemCount: horizontalListItems.length itemBuilder: (context, hIndex) => Text('Horizontal List $hIndex')),


// Vertical list
Text('Item No. $vIndex')
],
);
},
),