如何在Flutter中创建具有固定列的水平滚动表?

时间:2019-07-08 20:42:28

标签: flutter flutter-layout

我想创建一系列可以垂直滚动的表,每个表可能具有彼此不同的行数/列数。

在每个表中,我希望冻结最左边的列,并且该表中的其余列可以水平滚动,以防万一某些列不适合屏幕宽度。查看屏幕截图:

example

我最初的计划是使用ListView在表之间进行页面级垂直滚动,并且在每个表中都有一列列,其中第一列是静态宽度,其余列包含在水平滚动ListView。我从Flutter收到的错误并不能帮助我确定需要做的事情,但显然与必须在子Widget上设置边界有关。

错误:(通过用固定高度的容器包装水平ListView并收缩包装ListView来修复19/7/9

  

在performResize()期间引发了以下断言:   水平视口的宽度无限制。   视口沿滚动方向扩展以填充其容器。   视口被赋予了无限的水平空间来扩展。这个情况   通常在将可滚动小部件嵌套在另一个可滚动小部件内时发生。   如果此窗口小部件始终嵌套在可滚动窗口小部件中,则无需使用视口,因为   孩子们总是会有足够的水平空间。在这种情况下,请考虑使用行   代替。否则,请考虑使用“ shrinkWrap”属性(或ShrinkWrappingViewport)调整大小   视口的宽度与其子项的宽度之和。

新错误19年7月9日:

  

在布局期间引发了以下消息:   RenderFlex在右侧溢出了74个像素。   溢出的RenderFlex的方向为Axis.horizo​​ntal。   RenderFlex溢出的边缘已在渲染中用黄色和   黑色条纹的图案。这通常是因为内容对于RenderFlex而言太大。   考虑应用弹性系数(例如,使用扩展小部件)来强制使用   RenderFlex可以容纳在可用空间中,而不是按照其自然大小进行调整。   这被认为是错误情况,因为它表明存在无法   看过。如果内容合法大于可用空间,请考虑使用   在将ClipRect小部件放入Flex中或使用可滚动容器而不是Flex之前,   像ListView。   有问题的特定RenderFlex是:
  RenderFlex#9bf67 relayoutBoundary = up5 OVERFLOWING
    创建者:行←RepaintBoundary-[<0>]←IndexedSemantics←
    NotificationListener←KeepAlive←AutomaticKeepAlive←SliverList←
    SliverPadding←视口←IgnorePointer- [GlobalKey#74513]←语义←侦听器←⋯
    parentData :(可以使用大小)
    约束:BoxConstraints(w = 404.0,0.0 <= h <= Infinity)
    尺寸:尺寸(404.0,300.0)
    方向:水平
    mainAxisAlignment:开始
    mainAxisSize:max
    crossAxisAlignment:中心
    textDirection:ltr

这是我最初遇到的问题,后来才被报道的第一个问题所困扰。我不明白为什么我的ListView没有创建可滚动的容器。

代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('My App'),
          backgroundColor: Colors.teal[400],
        ),
        body: MyClass(),
      ),
    );
  }
}

const double headerCellWidth = 108.0;
const double cellPadding = 8.0;
const double focusedColumnWidth = 185.0;
const double rowHeight = 36.0;

class MyClass extends StatefulWidget {
  @override
  _MyClassState createState() => _MyClassState();
}

class _MyClassState extends State<MyClass> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: EdgeInsets.all(5.0),
      children: <Widget>[
        Row(
          children: <Widget>[
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Container(
                  color: Colors.grey,
                  padding: EdgeInsets.all(cellPadding),
                  width: headerCellWidth,
                ),
                HeaderCell('ABC'),
                HeaderCell('123'),
                HeaderCell('XYZ'),
              ],
            ),
            Container(
              height: 300.0,  // Could compute height with fixed rows and known number of rows in advance
              child: ListView(
                shrinkWrap: true,
                scrollDirection: Axis.horizontal,
                children: <Widget>[
                  Column(
                    children: <Widget>[
                      Container(
                        color: Colors.grey[300],
                        padding: EdgeInsets.all(cellPadding),
                        height: rowHeight,
                        width: focusedColumnWidth,
                      ),
                      NumberCell('89'),
                      NumberCell('92'),
                      NumberCell('91'),
                      NumberCell('90'),
                      NumberCell('91'),
                      NumberCell('89'),
                    ],
                  ),
                  Column(
                    children: <Widget>[
                      Container(
                        color: Colors.grey[300],
                        padding: EdgeInsets.all(cellPadding),
                        height: rowHeight,
                        width: focusedColumnWidth,
                      ),
                      NumberCell('89'),
                      NumberCell('92'),
                      NumberCell('91'),
                      NumberCell('90'),
                      NumberCell('91'),
                      NumberCell('89'),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ],
    );
  }
}

class HeaderCell extends StatelessWidget {
  HeaderCell(this.text);

  final String text;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: rowHeight,
      padding: EdgeInsets.all(cellPadding),
      width: headerCellWidth,
      child: Text(
        text,
        textAlign: TextAlign.left,
        overflow: TextOverflow.ellipsis,
        maxLines: 1,
        style: TextStyle(
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

class NumberCell extends StatelessWidget {
  NumberCell(this.text);

  final String text;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: rowHeight,
      width: focusedColumnWidth,
      padding: EdgeInsets.all(cellPadding),
      child: Text(
        text,
      ),
    );
  }
}

4 个答案:

答案 0 :(得分:4)

这是一个简单的示例,其结果如下:Video

List<Widget> _buildCells(int count) {
  return List.generate(
    count,
    (index) => Container(
      alignment: Alignment.center,
      width: 120.0,
      height: 60.0,
      color: Colors.white,
      margin: EdgeInsets.all(4.0),
      child: Text("${index + 1}", style: Theme.of(context).textTheme.title),
    ),
  );
}

List<Widget> _buildRows(int count) {
  return List.generate(
    count,
    (index) => Row(
      children: _buildCells(10),
    ),
  );
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(),
    body: SingleChildScrollView(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: _buildCells(20),
          ),
          Flexible(
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: _buildRows(20),
              ),
            ),
          )
        ],
      ),
    ),
  );
}

答案 1 :(得分:0)

您是否尝试将shrinkWrap: true设置为水平listView?

答案 2 :(得分:0)

因此,我尝试产生最少的代码工作量,并最终得出了可行的解决方案(即使所有细节都没有解决,例如第一个锁定列的宽度是灵活的,而不是所需的固定宽度)。希望这将有助于其他人尝试产生类似的东西。有趣的是,这里需要Table构造,因为仅用Row替换TableRow(由Table包裹)会导致溢出错误。我仍然会对理解为什么如此感兴趣,因为它对于布局引擎而言至关重要。

@override
  Widget build(BuildContext context) {
    return ListView(
      padding: EdgeInsets.all(5.0),
      children: <Widget>[
        Table(
          children: <TableRow>[
            TableRow(
              children: <Widget>[
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    // first locked column items
                  ],
                ),
                SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Row(
                        children: <Widget>[
                          // table header items
                        ],
                      ),
                      Row(
                        children: <Widget>[
                          // data cells
                        ],
                      ),
                      Row(
                        children: <Widget>[
                          // data cells
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ],
        ),
      ],
    );
  }

答案 3 :(得分:0)

如果不需要太多自定义,则对于那些需要的自定义标头和第一列表,也可以考虑使用horizo​​ntal_data_table包: https://pub.dev/packages/horizontal_data_table

它基本上是使用两个列表视图的方法。