如何在 Flutter 中实现 2D 可滚动 TableView,探索 Flutter 中的二维可滚动项

背景

表格是在应用程序中高效组织数据的有用工具。 Flutter 的最新更新引入了 TwoDimensionalScrollView 小部件。

TwoDimensionalScrollView 是一个小部件,它结合了 TwoDimensionalScrollableTwoDimensionalViewport 在垂直和水平维度上创建内容的交互式滚动窗格,但直接使用它有点具有挑战性。

下面是在 Flutter 中实现 2D ScrollView 的演示代码。

介绍

Flutter 的预构建 widget 具有出色的性能,仅当子视图位于视图中时才延迟渲染子视图,从而提高了性能,但 Flutter 发布了一个新包 two_dimensional_scrollables 来实现在垂直轴和水平轴上滚动的 TableView。

在本教程中,我们将探索这个包来实现一个非常简单的 Tableview,并了解如何自定义它。

您可以在这里找到完整的源代码。

执行

添加依赖项

要添加依赖项,请首先打开 pubspec.yaml 文件,然后添加依赖项。

yaml 复制代码
dependencies:
  two_dimensional_scrollables: <latest version>

将 TableView 添加到屏幕

添加 TableView 时,可以使用两个构造函数:

  • TableView.list :这个与 Flutter 的 ListView 类似。它一次添加所有单元格,并且适用于较短的列表。
  • Tableview.builder :当视图进入视口时,它会延迟添加子项。非常适合您不想一次加载所有内容的较大列表!

让我们创建一个简单的 Tableview 来显示以下信息。

dart 复制代码
class Employee {
 final String id;
 final String name;
 final String role;
 final String email;
     Employee({
         required this.id,
         required this.name,
         required this.role,
         required this.email});
 static get getEmployees{
    return [
      Employee(id: '1', name: 'John Doe', role: 'Manager', email: 'john@example.com'),
      Employee(id: '2', name: 'Jane Smith', role: 'Developer', email: 'jane@example.com'),
      Employee(id: '3', name: 'Mike Johnson', role: 'Designer', email: 'mike@example.com'),
      Employee(id: '4', name: 'Emily Brown', role: 'HR Specialist',email: 'emily@example.com'),
      Employee(id: '5', name: 'Alex Lee', role: 'Marketing Analyst', email: 'alex@example.com'),
      Employee(id: '6', name: 'John Doe', role: 'Manager', email: 'john@example.com'),
      Employee(id: '7', name: 'Jane Smith', role: 'Developer', email: 'jane@example.com'),
      Employee(id: '8', name: 'Mike Johnson', role: 'Designer', email: 'mike@example.com'),
      Employee(id: '9', name: 'Emily Brown', role: 'HR Specialist',email: 'emily@example.com'),
      Employee(id: '10', name: 'Alex Lee', role: 'Marketing Analyst', email: 'alex@example.com'),
     ];
  }
}

这是 TableView.builder 的基本示例代码。

dart 复制代码
class TwoDimensionalScrollableDemo extends StatelessWidget {
   TwoDimensionalScrollableDemo({super.key});

final  List<Employee> employees = Employee.getEmployees;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Table view Demo"),
      ),
      body: TableView.builder(
          columnCount: 2,
          rowCount: 11,
          columnBuilder: buildTableSpan,
          rowBuilder: buildTableSpan,
          cellBuilder: (BuildContext context, TableVicinity vicinity) {
            return Container(child: Center(child: addText(vicinity)));
          }),
    );
  }

  TableSpan buildTableSpan(int index){
    return TableSpan(extent: FixedTableSpanExtent(50));
  }

  Widget addText(TableVicinity vicinity) {
    if (vicinity.yIndex == 0 && vicinity.xIndex == 0) {
      return const Text("Index");
    } else if (vicinity.yIndex == 0 && vicinity.xIndex == 1) {
      return const Text("name");
    } else if (vicinity.xIndex == 0) {
      return Text(employees[vicinity.yIndex-1].id);
    } else if (vicinity.xIndex == 1) {
      return Text(employees[vicinity.yIndex-1].name);
    }
    return Text("");
  }
}

这对于几行代码来说已经相当不错了,让我们分解一下 TableView.builder 所需的参数。

  • columnCount :该参数设置表中的列数。
  • rowCount :该参数设置表中的行数。
  • columnBuilder :它是一个帮助定义表中每列的布局和功能的函数。它接受一个整数作为参数,并返回一个 TableSpan ,它构造和配置列。
  • rowbuilder :与 columnBuilder 类似,此函数定义 TableView 中每行的布局和行为。
  • cellBuilder :它处理表中每个单元格的布局。它采用 TableVicinity 参数,其中包含特定单元格的行索引和列索引。这有助于您自定义表格中各个单元格的外观和行为。

运行上面的代码,您将看到以下输出。

现在,让我们研究一下 TableView.builder 提供的功能!那么,让我们看看如何自定义和控制 TableView 并添加更多功能。

TableSpan 表跨度

TableSpan 表示 TableView 中的一行或一列。

添加边框

dart 复制代码
TableSpan buildTableSpan(int index) {
  TableSpanDecoration decoration = const TableSpanDecoration(
      border: TableSpanBorder(
          trailing: BorderSide(color: Colors.black),
          leading: BorderSide(color: Colors.black)));
  return  TableSpan(
      extent: const FixedTableSpanExtent(100),
      foregroundDecoration: decoration);
}

TableSpanDecoration 类用于指定 TableSpan 的装饰。我们可以通过 TableSpanBorder 类中的 border 参数为 TableSpan 添加边框,该类具有代表行左右边框和列上下边框的拖尾和前导属性。

添加颜色

dart 复制代码
TableSpan buildTableSpan(int index) {
  TableSpanDecoration decoration =  TableSpanDecoration(
      color: index == 0 ? Colors.grey[300] : null,
      border: const TableSpanBorder(
          trailing: BorderSide(color: Colors.black),
          leading: BorderSide(color: Colors.black)));
  return  TableSpan(
      extent: const FixedTableSpanExtent(100),
      backgroundDecoration: decoration);
}

TableSpanDecoration 中的 color 属性用于设置 TableSpan 的颜色。

运行代码,输出将如下所示

因为

  • TableView 中,装饰按特定顺序绘制。
  • 首先绘制 mainAxisbackgroundDecoration (作为主轴的行或列)。
  • 然后,绘制其他轴上的装饰。
  • 接下来,绘制跨度内的各个单元格内容。
  • 最后,任何指定的 foregroundDecoration 都会绘制在内容之上。

这里默认的轴是 Axis.vertical ,所以,先绘制列,然后绘制行,边框与行装饰重叠,所以,让我们为前景装饰添加边框。

dart 复制代码
TableSpan buildTableSpan(int index) {
  TableSpanDecoration foreGroundDecoration = const TableSpanDecoration(
      border: TableSpanBorder(
          trailing: BorderSide(color: Colors.black),
          leading: BorderSide(color: Colors.black)));
  TableSpanDecoration backGroundDecoration = TableSpanDecoration(
    color: index == 0 ? Colors.grey[300] : null,
  );
  return TableSpan(
      extent: const FixedTableSpanExtent(100),
      backgroundDecoration: backGroundDecoration,
      foregroundDecoration: foreGroundDecoration);
}

在前景装饰中添加边框可确保其呈现在 TableView 中单元格内容的顶部。

TableSpan.extent

它表示行的高度和列的宽度,TableSpanExtent 有四种类型。

1.FixedTableSpanExtent

具有固定[像素]的跨度。

dart 复制代码
extent: const FixedTableSpanExtent(50),

2.FractionTableSpanExtent

它将跨度范围指定为视口范围的一部分。它与 Expanded 小部件相同,根据提供的分数占用空间。

dart 复制代码
extent: const FractionalTableSpanExtent(0.5),

3.RemainingTableSpanExtent

它指定跨度应占据视口中的剩余空间。

dart 复制代码
TableSpan buildColumnSpan(int index) {
  TableSpanDecoration decoration =  const TableSpanDecoration(
      border: TableSpanBorder(
          trailing: BorderSide(color: Colors.black),
          leading: BorderSide(color: Colors.black)));
  return TableSpan(
      extent: index==0? FixedTableSpanExtent(100):RemainingTableSpanExtent(), backgroundDecoration: decoration);
}

4.CombiningTableSpanExtent

它以两个范围作为参数,并通过组合器函数运行这两个范围的结果。

dart 复制代码
TableSpan buildRowSpan(int index) {
    TableSpanExtent extent1 = FixedTableSpanExtent(100);
    TableSpanExtent extent2 = FixedTableSpanExtent(100);
    double combiner(double value1, double value2) {
      return value1 + value2;
    }

    TableSpanDecoration foreGroundDecoration = const TableSpanDecoration(
        border: TableSpanBorder(
            trailing: BorderSide(color: Colors.black),
            leading: BorderSide(color: Colors.black)));
    TableSpanDecoration backGroundDecoration = TableSpanDecoration(
      color: index == 0 ? Colors.grey[300] : null,
    );
    if (index == 1) {
      return TableSpan(
          extent: CombiningTableSpanExtent(extent1, extent2, combiner),
          backgroundDecoration: backGroundDecoration,
          foregroundDecoration: foreGroundDecoration);
    }
    return TableSpan(
        extent: const FixedTableSpanExtent(100),
        backgroundDecoration: backGroundDecoration,
        foregroundDecoration: foreGroundDecoration);
  }

当鼠标指针(按下或不按下按钮)进入此跨度描述的行或列时触发。

dart 复制代码
void Function(PointerEnterEvent)? onEnter

void Function(PointerExitEvent)? onExit

当鼠标指针(按下或未按下按钮)退出此跨度描述的行或列时,它会触发。

dart 复制代码
void Function(PointerExitEvent)? onExit

recognizerFactories

dart 复制代码
recognizerFactories: <Type, GestureRecognizerFactory>{
  TapGestureRecognizer:
  GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(),
        (TapGestureRecognizer t) {
          t.onTapDown=(TapDownDetails tapdownDetails){
            print(tapdownDetails.localPosition);
          };
          t.onTapUp=(TapUpDetails tapupDetails){
            print(tapupDetails.localPosition);
          };
        }   
  ),
},

recognizerFactories 是一个映射,其中键是手势类型,值是 GestureRecognizerFactory 的实例。

GestureRecognizerFactoryWithHandlers 有两个参数。调用时返回 TapGestureRecognizer 实例的函数和初始化 TapGestureRecognizer 并设置事件处理程序以处理特定事件详细信息的回调函数。

添加填充

padding 属性用于为每行和每列添���填充。

dart 复制代码
padding: TableSpanPadding(leading: 10),

向 UI 添加更多列和行

让我们在 TableView 中显示来自 Employee 类的更多数据,例如 emailrole

dart 复制代码
Widget addText(TableVicinity vicinity) {
    if (vicinity.yIndex == 0 && vicinity.xIndex == 0) {
      return const Text("Index");
    } else if (vicinity.yIndex == 0 && vicinity.xIndex == 1) {
      return const Text("name");
    } else if (vicinity.yIndex == 0 && vicinity.xIndex == 2) {
      return const Text("Email");
    } else if (vicinity.yIndex == 0 && vicinity.xIndex == 3) {
      return const Text("Role");
    } else if (vicinity.xIndex == 0) {
      return Text(employees[vicinity.yIndex - 1].id);
    } else if (vicinity.xIndex == 1) {
      return Text(employees[vicinity.yIndex - 1].name);
    } else if (vicinity.xIndex == 2) {
      return Text(employees[vicinity.yIndex - 1].email);
    } else if (vicinity.xIndex == 3) {
      return Text(employees[vicinity.yIndex - 1].role);
    }
    return Text("");
  }
...

      body: TableView.builder(
          mainAxis: Axis.horizontal,
          columnCount: 4,
          rowCount: 21,
          columnBuilder: buildColumnSpan,
          rowBuilder: buildTableSpan,
          cellBuilder: (BuildContext context, TableVicinity vicinity) {
            return Center(child: addText(vicinity));
          }),
...

TableSpan buildColumnSpan(int index) {
  TableSpanDecoration decoration = const TableSpanDecoration(
      border: TableSpanBorder(
          trailing: BorderSide(color: Colors.black),
          leading: BorderSide(color: Colors.black)));
  if (index == 2) {
    return TableSpan(
      extent: const RemainingTableSpanExtent(),
      backgroundDecoration: decoration,
    );
  } else if (index == 3) {
    return TableSpan(
      extent: const FractionalTableSpanExtent(0.5),
      backgroundDecoration: decoration,
    );
  }
  return TableSpan(
      extent: FixedTableSpanExtent(100), backgroundDecoration: decoration);
}

输出是

固定行和列

固定持续出现在 TableView 视口边缘的特定数量的行和列。

dart 复制代码
TableView.builder(
...
    pinnedRowCount: 1,
    pinnedColumnCount: 1,
   ),

滚动细节

ScrollableDetail 允许对小部件的垂直和水平滚动行为进行特定配置。

dart 复制代码
verticalDetails: ScrollableDetails.vertical(
  reverse: true,
  controller: verticalController,
  physics: const AlwaysScrollableScrollPhysics(),
  decorationClipBehavior: Clip.hardEdge
),

ScrollableDetails 中的 verticalDetails 允许对小部件的垂直滚动行为进行特定配置,它封装了各种属性。

dart 复制代码
verticalDetails: ScrollableDetails.vertical(
  reverse: true,
  controller: verticalController,
  physics: const AlwaysScrollableScrollPhysics(),
  decorationClipBehavior: Clip.hardEdge,
)

以下是属性的详细说明:

  • reverse :当设置为 true 时,小部件内的内容向相反方向滚动。
  • controller :指定用于管理和控制滚动行为的 ScrollController 。它允许您滚动到特定位置或收听滚动偏移量的变化。
  • physics :确定滚动物理的行为。
  • decorationClipBehavior :指定可滚动区域装饰的剪切行为

cacheExtent 缓存范围

dart 复制代码
cacheExtent: 200,

与ListView类似, cacheExtent 是在进入屏幕可见部分之前绘制的区域的大小。

DiagonalDragBehavior 对角线拖动行为

该枚举允许开发人员指定如何使用 kTouchSlop 处理对角滚动。

结论

在本教程中,我们学习了如何使用 Flutter 中的 two_dimensional_scrollable 实现 Flutter 中的 tableView,我们可以用它做更多的事情并使用它添加更多功能。这是我对 Flutter 中 TableView 的一个小介绍。


原文:blog.canopas.com/how-to-impl...

相关推荐
小白学前端66643 分钟前
React Router 深入指南:从入门到进阶
前端·react.js·react
web130933203981 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
outstanding木槿1 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08212 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
隐形喷火龙2 小时前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241122 小时前
Selenium之Web元素定位
前端·selenium·测试工具
yuanlaile2 小时前
纯Dart Flutter库适配HarmonyOS
flutter·华为·harmonyos·flutter开发鸿蒙·harmonyos教程
yuanlaile2 小时前
Flutter开发HarmonyOS 鸿蒙App的好处、能力以及把Flutter项目打包成鸿蒙应用
flutter·华为·harmonyos·flutter开发鸿蒙
风无雨2 小时前
react杂乱笔记(一)
前端·笔记·react.js
前端小魔女2 小时前
2024-我赚到自媒体第一桶金
前端·rust