在Flutter中,在垂直滚动视图内部创建水平列表视图

81

我试图实现一种现在非常常见的行为,即在另一个可滚动的小部件中具有水平列表。想象一下 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 将 Column 包裹起来

  • 将 CustomScrollView 作为父级,使用 SliverList 并在 SliverChildListDelegate 中放置列表

这些方法都无法解决问题,我一直收到相同类型的错误。这应该是很常见的事情,不应该有任何难度,但是我就是无法让它正常工作 :(

如果有任何帮助,将不胜感激,谢谢!

编辑:

我认为这个链接可以帮助我,但实际上没有。


你的竖直ListView在哪里? - dshukertjr
2
没有垂直的ListView。我希望整个屏幕都可以滚动。想象一下一个可滚动的列。然后在该列中,我想要一个可以水平滚动的ListView。该列中的其余子项将是不同的项目,例如标题、卡片和其他内容。 - Javier Mendonça
11个回答

136

好的,您的代码使用ListView.builder包装Expanded小部件,并将Column小部件的mainAxisSize: MainAxisSize.min设置为可行。

示例代码:

 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

更新:

整个页面可以通过使用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')),
      ),
    ],
  ),
),

输入图像描述


谢谢您的快速回复!我也实现了这个,但不完全是我需要的。我希望“动机”项目不是以列表形式出现,这样整个屏幕就会向下滚动,如果您滚动足够多,水平列表将会消失在屏幕外。有什么想法可以实现吗? - Javier Mendonça
3
太棒了,正是我想要的。确实不复杂,谢谢! - Javier Mendonça
此评论代表罗曼·马特罗斯金添加:如果水平可滚动项的高度不同怎么办? - Adrian Mole
1
我试图不使用SizedBox来固定列表的大小,为什么当你的父级是一个可滚动的组件时,shrinkWrap: true不起作用呢? - Daniel Gomez Rico
我浪费了几个小时尝试让它工作,直到我在这里找到了这个完整的示例。找到这篇文章后没过多久就解决了。对我来说真正的问题是滚动在Linux桌面上无法工作,而这是我首选的速度测试平台。 - Bhikkhu Subhuti
太棒了的回答!省下了一整天的时间!:) - sppc42

55

截图:

在此输入图片描述


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

17
可以在没有固定高度的情况下使用水平列表视图吗? - Balaji
@BalajiRamadoss,请看我在这里的答案:https://dev59.com/pVQJ5IYBdhLWcg3wIiO5#67556125 - thanhbinh84

9
我们需要在一个SingleScrollView中再使用另一个SingleScrollView,如果使用ListView将需要固定高度。
SingleChildScrollView(
   child: Column(
      children: <Widget>[
        SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Row(
          children: [Text('H1'), Text('H2'), Text('H3')])),
        Text('V1'),
        Text('V2'),
        Text('V3')]))

1
this is not the solution. - Rishabh Deep Singh
@RishabhDeepSingh 的原因是什么?我在我的应用程序中使用它,虽然性能比 ListView 差,但高度更灵活。 - thanhbinh84
1
但是项目不是动态的 - Rishabh Deep Singh
@RishabhDeepSingh 你可以创建一个文本小部件列表,并将其作为行的子项,这与ListView类似。唯一的区别是视图不会被回收,因此ListView具有更好的性能。我只是提供另一种选择。 - thanhbinh84
如果列表很长,那么在使用这个解决方案时可能会出现卡顿的情况。 - undefined
是的,这是真的 - undefined

4

我在这段代码中尝试并解决了我的问题,希望能解决你的需求。

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

如果我们实施这个解决方案,那么如果列表很长,你能做些什么呢?可能会出现延迟的情况。 - undefined

4
如果有人得到 "renderview port was exceeded" 错误,请将 ListView 包裹在一个 Container widget 中,并给它设置高度和宽度属性以解决问题。
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],
             ),
         ),
       )
      ]
)

0

对于Web Chrome,您需要添加MaterialScrollBehavior以使水平滚动正常工作。请参见(在Web上无法滚动水平列表,但在移动设备上可以滚动)。我演示了如何使用scrollcontroller同时向左和向右动画显示列表。

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

0
将所有列表项放在Row小部件内,然后用SingleChildScrollView包裹Row。现在我们得到了一个水平的列表视图,其中列表项的高度是动态的,彼此之间的高度不同。
但缺点是它不像Listview.builderListview.separated那样按需加载项目。
另一方面,如果您不先指定高度,Listview.builderListview.separated无法用于动态调整高度的项目。

0

你只需要调整你的Listview的高度(例如通过将其包装在SizedBox中)。

这是因为在绘制框架之前无法知道列表视图的内容。想象一下有数百个项目的列表... 没有直接知道它们所有项目中最大高度的方法。


0

使用Builder在垂直ListView中创建水平ListView

没有一个答案能够解决我的问题,我需要在垂直ListView中使用水平ListView,同时仍然使用ListBuilder(这比一次性渲染所有子元素更有效)。

original question image

结果证明这相当简单。只需将您的垂直列表子项包装在Column中,并检查索引是否为0(或index % 3 == 0),然后渲染水平列表即可。

看起来很好用:

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

0

尝试使用物理引擎:BouncingScrollPhysics()

如果您正在使用ListView - 在ListView内部进行水平滚动 - 垂直滚动,这可能会为子Listview创建一个超滚动问题。 在这种情况下,对于子ListView,我使用了physics: BouncingScrollPhysics(),它提供了良好的弹性滚动效果并解决了我的错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接