【问题标题】:How can I make a scrollable wrapping view with Flutter?如何使用 Flutter 制作可滚动的包装视图?
【发布时间】:2026-02-05 12:45:01
【问题描述】:

我想显示有限数量的项目并在用户向任一方向滚动时换行。我该怎么做?

【问题讨论】:

    标签: dart flutter


    【解决方案1】:

    你不能用无限长的ListView.builder 轻松解决这个问题,因为它只在一个方向上。如果您想双向环绕,可以使用两个相反方向的视口的Stack 模拟双向环绕。

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(new MaterialApp(
        home: new HomePage(),
      ));
    }
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Wrapping List View'),
          ),
          body: new WrappingListView.builder(
            itemCount: 10,
            itemBuilder: (BuildContext context, int index) {
              return new Card(
                child: new Container(
                  height: 50.0,
                  color: Colors.blue.withOpacity(index / 10),
                  child: new Center(
                    child: new Text('Card $index')
                  ),
                ),
              );
            },
          ),
        );
      }
    }
    
    class WrappingListView extends StatefulWidget {
    
      factory WrappingListView({ Key key, List<Widget> children }) {
        return new WrappingListView.builder(
          itemCount: children.length,
          itemBuilder: (BuildContext context, int index) {
            return children[index % children.length];
          },
        );
      }
    
      WrappingListView.builder({ Key key, this.itemBuilder, this.itemCount })
        : super(key: key);
    
      final int itemCount;
      final IndexedWidgetBuilder itemBuilder;
    
      WrappingListViewState createState() => new WrappingListViewState();
    }
    
    class UnboundedScrollPosition extends ScrollPositionWithSingleContext {
      UnboundedScrollPosition({
        ScrollPhysics physics,
        ScrollContext context,
        ScrollPosition oldPosition,
      }) : super(physics: physics, context: context, oldPosition: oldPosition);
    
      @override
      double get minScrollExtent => double.negativeInfinity;
    }
    
    class UnboundedScrollController extends ScrollController {
      @override
      UnboundedScrollPosition createScrollPosition(
        ScrollPhysics physics,
        ScrollContext context,
        ScrollPosition oldPosition,
      ) {
        return new UnboundedScrollPosition(
          physics: physics,
          context: context,
          oldPosition: oldPosition,
        );
      }
    }
    
    class WrappingListViewState extends State<WrappingListView> {
      UnboundedScrollController _controller = new UnboundedScrollController();
      UnboundedScrollController _negativeController = new UnboundedScrollController();
    
      @override
      void initState() {
        _controller.addListener(() {
          _negativeController.jumpTo(
            -_negativeController.position.extentInside -
            _controller.position.pixels,
          );
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return new Stack(
          children: <Widget>[
            new CustomScrollView(
              physics: new AlwaysScrollableScrollPhysics(),
              controller: _negativeController,
              reverse: true,
              slivers: <Widget>[
                new SliverList(
                  delegate: new SliverChildBuilderDelegate(
                    (BuildContext context, int index) {
                      return widget.itemBuilder(
                        context,
                        (widget.itemCount - 1 - index) % widget.itemCount,
                      );
                    }
                  ),
                ),
              ],
            ),
            new ListView.builder(
              controller: _controller,
              itemBuilder: (BuildContext context, int index) {
                return widget.itemBuilder(context, index % widget.itemCount);
              },
            ),
          ],
        );
      }
    }
    

    【讨论】:

    • 通过使用这种技术,顶部列表内的卡片无法被点击,因为下面的列表正在吸收事件。确实,此解决方案仅在您不希望与项目进行手势交互时才有效。
    【解决方案2】:

    截至 2018 年 12 月,您可以使用它(通过合适的构建器):https://pub.dartlang.org/packages/infinite_listview

    或者这个: https://pub.dev/packages/indexed_list_view

    【讨论】:

      最近更新 更多