flexible_scrollable_table_view 2.0.0 copy "flexible_scrollable_table_view: ^2.0.0" to clipboard
flexible_scrollable_table_view: ^2.0.0 copied to clipboard

A table view who can scroll both vertical and horizontal.

example/lib/main.dart

import 'dart:math';

import 'package:flexible_scrollable_table_view/flexible_scrollable_table_view.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final Map<String, Widget> pages = <String, Widget>{
      '普通列表': const NormalList(),
      'Sliver内': const InSliverList(),
    };
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        useMaterial3: true,
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('双向滚动TableView'),
        ),
        body: DefaultTabController(
          length: pages.length,
          child: Column(
            children: [
              TabBar(tabs: pages.keys.map<Widget>((e) => Tab(text: e)).toList()),
              Expanded(
                child: TabBarView(
                  children: pages.values.toList(),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

///数据实体
class TableDataBean {
  const TableDataBean({
    required this.id,
    required this.title,
    required this.value1,
    required this.value2,
    required this.value3,
  });

  final int id;
  final String title;
  final String value1;
  final int value2;
  final double value3;

  @override
  bool operator ==(Object other) =>
      identical(this, other) || other is TableDataBean && runtimeType == other.runtimeType && id == other.id;

  @override
  int get hashCode => id.hashCode;
}

///普通列
class NormalColumn<T> extends AbsFlexibleColumn<T> {
  const NormalColumn(
    super.id, {
    required this.fixedWidth,
    required this.headerText,
    required this.infoText,
    this.onHeaderPressed,
    this.onInfoPressed,
    this.comparator,
  });

  final String headerText;
  final String Function(T data) infoText;
  final VoidCallback? onHeaderPressed;
  final ValueChanged<T>? onInfoPressed;
  @override
  final double fixedWidth;

  @override
  final Comparator<T>? comparator;

  @override
  Widget buildHeader(FlexibleTableController<T> controller) {
    Widget child = SizedBox.expand(
      child: Center(
        child: Text(headerText),
      ),
    );
    if (onHeaderPressed != null || comparableColumn) {
      child = GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          onHeaderPressed?.call();
          controller.sortByColumn(this);
        },
        child: child,
      );
    }
    return child;
  }

  @override
  Widget buildInfo(FlexibleTableController<T> controller, int dataIndex, T data) {
    Widget child = SizedBox.expand(
      child: Center(
        child: Text(infoText.call(data)),
      ),
    );
    if (onInfoPressed != null) {
      child = GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () => onInfoPressed?.call(data),
        child: child,
      );
    }
    return child;
  }
}

///可选择列
class CustomSelectableColumn<T> extends AbsSelectableColumn<T> {
  const CustomSelectableColumn(
    super.id, {
    required this.selectableWidth,
    this.unSelectableWidth = 0,
  });

  @override
  final double selectableWidth;

  @override
  final double unSelectableWidth;

  @override
  Widget buildSelectableHeader(FlexibleTableController<T> controller) {
    return SelectableColumnHeader<T>(
      controller,
      builder: (context, selected, onChanged, child) => Checkbox(
        value: selected,
        onChanged: onChanged,
      ),
    );
  }

  @override
  Widget buildUnSelectableHeader(FlexibleTableController<T> controller) {
    return const SizedBox.expand(
      child: ColoredBox(color: Colors.purple),
    );
  }

  @override
  Widget buildSelectableInfo(FlexibleTableController<T> controller, int dataIndex, T data) {
    return SelectableColumnInfo<T>(
      controller,
      data: data,
      builder: (context, selected, onChanged, child) => Checkbox(
        value: selected,
        onChanged: onChanged,
      ),
    );
  }

  @override
  Widget buildUnSelectableInfo(FlexibleTableController<T> controller, int dataIndex, T data) {
    return const SizedBox.expand(
      child: ColoredBox(color: Colors.red),
    );
  }
}

class NormalList extends StatefulWidget {
  const NormalList({Key? key}) : super(key: key);

  @override
  State<NormalList> createState() => _NormalListState();
}

class _NormalListState extends State<NormalList> {
  late FlexibleTableController<TableDataBean> controller;
  late AbsFlexibleTableConfigurations<TableDataBean> configurations;

  @override
  void initState() {
    super.initState();
    controller = FlexibleTableController<TableDataBean>();
    configurations = FlexibleTableConfigurations<TableDataBean>(
      headerRowHeight: 40,
      infoRowHeight: 50,
      pinnedColumns: {
        NormalColumn(
          'title',
          fixedWidth: 130,
          headerText: 'title列',
          infoText: (data) => data.title,
          onHeaderPressed: () {
            debugPrint('点击了title列头');
          },
        ),
        const CustomSelectableColumn(
          'selectable',
          selectableWidth: 48,
          unSelectableWidth: 32,
        ),
      },
      scrollableColumns: {
        NormalColumn(
          'value1',
          fixedWidth: 150,
          headerText: 'value1列',
          infoText: (data) => data.value1,
        ),
        NormalColumn(
          'value2',
          fixedWidth: 100,
          headerText: 'value2列',
          infoText: (data) => data.value2.toString(),
          comparator: (a, b) => a.value2.compareTo(b.value2),
          onInfoPressed: (data) {
            debugPrint('点击了value2列信息:${data.value2}');
          },
        ),
        NormalColumn(
          'value3',
          fixedWidth: 130,
          headerText: 'value3列',
          infoText: (data) => data.value3.toStringAsFixed(4),
          comparator: (a, b) => a.value3.compareTo(b.value3),
          onHeaderPressed: () {
            debugPrint('点击了value3列头');
          },
        ),
      },
    );
    refreshData();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  void refreshData() {
    final Random random = Random.secure();
    controller.value = List<TableDataBean>.generate(
      random.nextInt(40) + 30,
      (index) => TableDataBean(
        id: index,
        title: '数据标题$index',
        value1: 'String值$index',
        value2: random.nextInt(1000),
        value3: random.nextDouble() * 100,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(children: [
          IconButton(
            onPressed: refreshData,
            icon: const Icon(Icons.refresh_rounded),
          ),
          Expanded(
            child: ValueListenableBuilder<bool>(
              valueListenable: controller.selectable,
              builder: (context, value, child) => CheckboxListTile(
                value: value,
                title: const Text('切换可选与不可选状态'),
                onChanged: (newValue) {
                  if (newValue != null) {
                    controller.switchSelectable(newValue);
                  }
                },
              ),
            ),
          ),
        ]),
        Material(
          elevation: 2,
          child: FlexibleTableHeaderRow<TableDataBean>(
            controller,
            configurations: configurations,
          ),
        ),
        Expanded(
          child: FlexibleTableContent<TableDataBean>(
            controller,
            configurations: configurations,
            backgroundRowDecoration: FlexibleTableRowDecoration(
              builder: (dataIndex, data) => ColoredBox(
                color: dataIndex.isOdd ? Colors.grey.shade200 : Colors.grey.shade300,
                child: const SizedBox.expand(),
              ),
            ),
            foregroundRowDecoration: FlexibleTableRowDecoration(
              builder: (dataIndex, data) => GestureDetector(
                onTap: () {
                  debugPrint('点击了前景装饰行:$dataIndex');
                },
              ),
            ),
            footer: OutlinedButton(
              onPressed: () {
                debugPrint('点击了列表的Footer');
              },
              child: const Text('这里是列表的Footer,一个OutlinedButton'),
            ),
          ),
        ),
      ],
    );
  }
}

class InSliverList extends StatefulWidget {
  const InSliverList({Key? key}) : super(key: key);

  @override
  State<InSliverList> createState() => _InSliverListState();
}

class _InSliverListState extends State<InSliverList> {
  late FlexibleTableController<TableDataBean> controller;
  late AbsFlexibleTableConfigurations<TableDataBean> configurations;

  @override
  void initState() {
    super.initState();
    controller = FlexibleTableController<TableDataBean>();
    configurations = FlexibleTableConfigurations<TableDataBean>(
      //如果不想显示表头,可以把表头的高度设置为 0,但是 FlexibleTableHeaderRow<T> 必须要放置到布局内。
      //并且尽量和 FlexibleTableContent<T> 保持宽度一致。
      headerRowHeight: 0,
      infoRowHeight: 50,
      pinnedColumns: {
        NormalColumn(
          'title',
          fixedWidth: 130,
          headerText: 'title列',
          infoText: (data) => data.title,
          onHeaderPressed: () {
            debugPrint('点击了title列头');
          },
        ),
        const CustomSelectableColumn(
          'selectable',
          selectableWidth: 48,
          unSelectableWidth: 32,
        ),
      },
      scrollableColumns: {
        NormalColumn(
          'value1',
          fixedWidth: 150,
          headerText: 'value1列',
          infoText: (data) => data.value1,
        ),
        NormalColumn(
          'value2',
          fixedWidth: 100,
          headerText: 'value2列',
          infoText: (data) => data.value2.toString(),
          comparator: (a, b) => a.value2.compareTo(b.value2),
          onInfoPressed: (data) {
            debugPrint('点击了value2列信息:${data.value2}');
          },
        ),
        NormalColumn(
          'value3',
          fixedWidth: 130,
          headerText: 'value3列',
          infoText: (data) => data.value3.toStringAsFixed(4),
          comparator: (a, b) => a.value3.compareTo(b.value3),
          onHeaderPressed: () {
            debugPrint('点击了value3列头');
          },
        ),
      },
    );
    refreshData();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  void refreshData() {
    final Random random = Random.secure();
    controller.value = List<TableDataBean>.generate(
      random.nextInt(20) + 10,
      (index) => TableDataBean(
        id: index,
        title: '数据标题$index',
        value1: 'String值$index',
        value2: random.nextInt(1000),
        value3: random.nextDouble() * 100,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverToBoxAdapter(
          child: IconButton(
            onPressed: refreshData,
            icon: const Icon(Icons.refresh_rounded),
          ),
        ),
        SliverToBoxAdapter(
          child: FlexibleTableHeaderRow<TableDataBean>(
            controller,
            configurations: configurations,
          ),
        ),
        SliverToBoxAdapter(
          child: ValueListenableBuilder<bool>(
            valueListenable: controller.selectable,
            builder: (context, value, child) => CheckboxListTile(
              value: value,
              title: const Text('切换可选与不可选状态'),
              onChanged: (newValue) {
                if (newValue != null) {
                  controller.switchSelectable(newValue);
                }
              },
            ),
          ),
        ),
        SliverPadding(
          padding: const EdgeInsets.all(16),
          sliver: FlexibleTableContent<TableDataBean>.sliver(
            controller,
            configurations: configurations,
            backgroundRowDecoration: FlexibleTableRowDecoration(
              builder: (dataIndex, data) => ColoredBox(
                color: dataIndex.isOdd ? Colors.grey.shade100 : Colors.grey.shade200,
                child: InkWell(
                  onTap: () {
                    debugPrint('点击了Sliver列表的背景装饰:$dataIndex');
                  },
                  child: const SizedBox.expand(),
                ),
              ),
            ),
            footer: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                ElevatedButton(
                  onPressed: () {
                    debugPrint('点击了Footer');
                  },
                  child: const Text('这里是Footer,一个ElevatedButton'),
                ),
                const ColoredBox(
                  color: Colors.yellow,
                  child: SizedBox.square(
                    dimension: 200,
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}
1
likes
0
pub points
64%
popularity

Publisher

unverified uploader

A table view who can scroll both vertical and horizontal.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flexible_scrollable_table_view