如何在Flutter中实现固定横竖表头的表格?例如,水平标题只应水平滚动,而垂直标题应垂直滚动。两个标题都应该始终可见。如何设置布局?
我已经尝试使用带有两个嵌套列的行来设置整体 2x2 布局:(0, 0) empty; (0, 1) 垂直标题; (1,0) 水平标题和 (1, 1) 数据。为了可视化实际数据,我对两个标题和数据使用了 GridView。此外,我想使用滚动 Controller 来实现滚动行为。
- 行
- 列:(0) 空,(1) GridView
- 列:(0) GridView (1) GridView
我想到的另一个解决方案是嵌套 GridView,而不是 Row 和两个 Columns。
此代码显示第一列:
Widget build(BuildContext context) {
return Container(
width: double.maxFinite,
child: Row(
children: <Widget>[
Column(
children: <Widget>[
Text("empty"), // (0,0)
Container( // (0, 1)
child: Flexible(
child: GridView.count(
controller: _vScrollController1,
crossAxisCount: 1,
childAspectRatio: 3.0,
children: List.generate(
widget.data.length,
(index) => Text("my cell")
),
),
),
),
],
),
],
),
);
但是,它会产生以下错误消息:
════════ Exception caught by rendering library
The method '>' was called on null.
Receiver: null. Tried calling: >(1e-10). User-created ancestor of the error-causing widget was Container. ═════════════════════════════════
可能是某些宽度/高度属性设置不正确?你将如何实现这种表格布局?感谢您的帮助!
最佳答案
实际上已经有针对此的现有 Flutter 插件。考虑使用一个。
这是取自 horizontal_data_table 的示例:
import 'package:flutter/material.dart';
import 'package:horizontal_data_table/horizontal_data_table.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
HDTRefreshController _hdtRefreshController = HDTRefreshController();
static const int sortName = 0;
static const int sortStatus = 1;
bool isAscending = true;
int sortType = sortName;
@override
void initState() {
user.initData(100);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _getBodyWidget(),
);
}
Widget _getBodyWidget() {
return Container(
child: HorizontalDataTable(
leftHandSideColumnWidth: 100,
rightHandSideColumnWidth: 600,
isFixedHeader: true,
headerWidgets: _getTitleWidget(),
leftSideItemBuilder: _generateFirstColumnRow,
rightSideItemBuilder: _generateRightHandSideColumnRow,
itemCount: user.userInfo.length,
rowSeparatorWidget: const Divider(
color: Colors.black54,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: Color(0xFFFFFFFF),
rightHandSideColBackgroundColor: Color(0xFFFFFFFF),
verticalScrollbarStyle: const ScrollbarStyle(
thumbColor: Colors.yellow,
isAlwaysShown: true,
thickness: 4.0,
radius: Radius.circular(5.0),
),
horizontalScrollbarStyle: const ScrollbarStyle(
thumbColor: Colors.red,
isAlwaysShown: true,
thickness: 4.0,
radius: Radius.circular(5.0),
),
enablePullToRefresh: true,
refreshIndicator: const WaterDropHeader(),
refreshIndicatorHeight: 60,
onRefresh: () async {
//Do sth
await Future.delayed(const Duration(milliseconds: 500));
_hdtRefreshController.refreshCompleted();
},
htdRefreshController: _hdtRefreshController,
),
height: MediaQuery.of(context).size.height,
);
}
List<Widget> _getTitleWidget() {
return [
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: _getTitleItemWidget(
'Name' + (sortType == sortName ? (isAscending ? '↓' : '↑') : ''),
100),
onPressed: () {
sortType = sortName;
isAscending = !isAscending;
user.sortName(isAscending);
setState(() {});
},
),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: _getTitleItemWidget(
'Status' +
(sortType == sortStatus ? (isAscending ? '↓' : '↑') : ''),
100),
onPressed: () {
sortType = sortStatus;
isAscending = !isAscending;
user.sortStatus(isAscending);
setState(() {});
},
),
_getTitleItemWidget('Phone', 200),
_getTitleItemWidget('Register', 100),
_getTitleItemWidget('Termination', 200),
];
}
Widget _getTitleItemWidget(String label, double width) {
return Container(
child: Text(label, style: TextStyle(fontWeight: FontWeight.bold)),
width: width,
height: 56,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
);
}
Widget _generateFirstColumnRow(BuildContext context, int index) {
return Container(
child: Text(user.userInfo[index].name),
width: 100,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
);
}
Widget _generateRightHandSideColumnRow(BuildContext context, int index) {
return Row(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Icon(
user.userInfo[index].status
? Icons.notifications_off
: Icons.notifications_active,
color:
user.userInfo[index].status ? Colors.red : Colors.green),
Text(user.userInfo[index].status ? 'Disabled' : 'Active')
],
),
width: 100,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
Container(
child: Text(user.userInfo[index].phone),
width: 200,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
Container(
child: Text(user.userInfo[index].registerDate),
width: 100,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
Container(
child: Text(user.userInfo[index].terminationDate),
width: 200,
height: 52,
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
),
],
);
}
}
User user = User();
class User {
List<UserInfo> userInfo = [];
void initData(int size) {
for (int i = 0; i < size; i++) {
userInfo.add(UserInfo(
"User_$i", i % 3 == 0, '+001 9999 9999', '2019-01-01', 'N/A'));
}
}
///
/// Single sort, sort Name's id
void sortName(bool isAscending) {
userInfo.sort((a, b) {
int aId = int.tryParse(a.name.replaceFirst('User_', '')) ?? 0;
int bId = int.tryParse(b.name.replaceFirst('User_', '')) ?? 0;
return (aId - bId) * (isAscending ? 1 : -1);
});
}
///
/// sort with Status and Name as the 2nd Sort
void sortStatus(bool isAscending) {
userInfo.sort((a, b) {
if (a.status == b.status) {
int aId = int.tryParse(a.name.replaceFirst('User_', '')) ?? 0;
int bId = int.tryParse(b.name.replaceFirst('User_', '')) ?? 0;
return (aId - bId);
} else if (a.status) {
return isAscending ? 1 : -1;
} else {
return isAscending ? -1 : 1;
}
});
}
}
class UserInfo {
String name;
bool status;
String phone;
String registerDate;
String terminationDate;
UserInfo(this.name, this.status, this.phone, this.registerDate,
this.terminationDate);
}
实际输出:
您可以检查其他现有的插件。
此外,这里有一些与您相关的已回答的 SO 问题:
- Fixed column and row header for DataTable on Flutter Dart
- How to create a horizontally scrolling table with fixed column in Flutter?
其他引用可以查看博客“Flutter: Creating a two direction scrolling table with fixed head and column”
关于flutter - 如何在 Flutter 中实现固定表头的表格?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58107974/