From 768e8f554703988f638446f368a2f269d99ae3b3 Mon Sep 17 00:00:00 2001 From: zflyluo Date: Fri, 10 Apr 2026 17:59:01 +0800 Subject: [PATCH 1/2] feat(picker): support dynamic column data loading via onColumnChanged (fixes #817) - Add `LinkedPickerColumnChangedCallback` typedef for async column data loading - Add `onColumnChanged` param to `TMultiLinkedPicker` and `TPicker.showMultiLinkedPicker` - Add `isLoading` state to `MultiLinkedPickerModel` for per-column loading indicator - Add `resetColumnsAfter`, `updateColumnData`, `setLoading` methods to model - Add `cascadeNext` param to `refreshPresentDataAndController` for external data control - Show loading indicator in column while async data is being fetched - Fully backward compatible: existing behavior unchanged when `onColumnChanged` is null - Add demo example with 300ms simulated network delay - Add 14 unit tests covering all scenarios (TC-01 to TC-06 + BC-02) - Regenerate picker_api.md with new API docs Made-with: Cursor --- .../example/assets/api/picker_api.md | 3 +- .../example/lib/page/t_picker_page.dart | 55 ++++ .../src/components/picker/t_multi_picker.dart | 123 ++++++++- .../lib/src/components/picker/t_picker.dart | 6 + tdesign-component/test/td_picker_test.dart | 239 ++++++++++++++++++ 5 files changed, 422 insertions(+), 4 deletions(-) create mode 100644 tdesign-component/test/td_picker_test.dart diff --git a/tdesign-component/example/assets/api/picker_api.md b/tdesign-component/example/assets/api/picker_api.md index 45c53f352..f90536a4a 100644 --- a/tdesign-component/example/assets/api/picker_api.md +++ b/tdesign-component/example/assets/api/picker_api.md @@ -54,6 +54,7 @@ | leftTextStyle | TextStyle? | - | 自定义左侧文案样式 | | onCancel | MultiPickerCallback? | - | 选择器取消按钮回调 | | onChange | MultiPickerCallback? | - | todo 选择器数据改变时回调 | +| onColumnChanged | LinkedPickerColumnChangedCallback? | - | 列选项变化时的回调,用于动态加载下一列数据 | | onConfirm | MultiPickerCallback? | - | 选择器确认按钮回调 | | padding | EdgeInsets? | - | 适配padding | | pickerHeight | double | 200 | | @@ -91,5 +92,5 @@ | 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | | showDatePicker | | required null context, String? title, double? titleHeight, Color? titleDividerColor, required DatePickerCallback? onConfirm, DatePickerCallback? onCancel, DatePickerCallback? onChange, Function(int wheelIndex, int index)? onSelectedItemChanged, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, String? rightText, TextStyle? rightTextStyle, EdgeInsets? padding, double? leftPadding, double? topPadding, double? rightPadding, double? topRadius, Color? backgroundColor, Widget? customSelectWidget, bool useYear, bool useMonth, bool useDay, bool useHour, bool useMinute, bool useSecond, bool useWeekDay, List dateStart, List? dateEnd, List? initialDate, List Function(DateTypeKey key, List nums)? filterItems, double pickerHeight, int pickerItemCount, bool isTimeUnit, ItemBuilderType? itemBuilder, Color? barrierColor, Duration duration, | 显示时间选择器 | -| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, Duration duration, | 显示多级联动选择器 | +| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, LinkedPickerColumnChangedCallback? onColumnChanged, Duration duration, | 显示多级联动选择器 | | showMultiPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List> data, double pickerHeight, int pickerItemCount, List? initialIndexes, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, Widget? customSelectWidget, ItemBuilderType? itemBuilder, Duration duration, Color? barrierColor, | 显示多级选择器 | diff --git a/tdesign-component/example/lib/page/t_picker_page.dart b/tdesign-component/example/lib/page/t_picker_page.dart index fca664601..2f4e1c47b 100644 --- a/tdesign-component/example/lib/page/t_picker_page.dart +++ b/tdesign-component/example/lib/page/t_picker_page.dart @@ -215,6 +215,9 @@ class _TPickerPageState extends State { ExampleItem( desc: '自定义left/right text', builder: buildCustomLeftRightText), ExampleItem(desc: '级联选择保持下一级选项', builder: buildKeepMultiArea), + ExampleItem( + desc: '动态加载下一列数据(onColumnChanged)', + builder: buildDynamicLinkedPicker), ], ); } @@ -428,4 +431,56 @@ class _TPickerPageState extends State { }, ); } + + /// 模拟异步数据源:根据当前列选中值返回下一列数据 + Future _fetchNextColumnData(int columnIndex, List selectedData) async { + // 模拟网络延迟 300ms + await Future.delayed(const Duration(milliseconds: 300)); + if (columnIndex == 0) { + final province = selectedData[0]?.toString() ?? ''; + if (province == '广东省') { + return ['深圳市', '广州市', '佛山市']; + } else if (province == '浙江省') { + return ['杭州市', '宁波市', '温州市']; + } + return ['(无数据)']; + } else if (columnIndex == 1) { + final city = selectedData[1]?.toString() ?? ''; + if (city == '深圳市') return ['南山区', '宝安区', '罗湖区']; + if (city == '广州市') return ['天河区', '越秀区', '白云区']; + if (city == '杭州市') return ['西湖区', '余杭区', '萧山区']; + if (city == '宁波市') return ['江东区', '北仑区', '奉化市']; + return ['(无数据)']; + } + return []; + } + + @Demo(group: 'picker') + Widget buildDynamicLinkedPicker(BuildContext context) { + return TCell( + title: '动态加载(onColumnChanged)', + note: selected_3.isEmpty ? '请选择' : selected_3, + arrow: true, + onClick: (click) { + TPicker.showMultiLinkedPicker( + context, + title: '选择地区(动态加载)', + onConfirm: (selected) { + setState(() { + selected_3 = selected.join(' '); + }); + Navigator.of(context).pop(); + }, + // data 传空 Map,完全由 onColumnChanged 提供数据 + data: { + '广东省': {}, + '浙江省': {}, + }, + columnNum: 3, + initialData: ['广东省', '', ''], + onColumnChanged: _fetchNextColumnData, + ); + }, + ); + } } diff --git a/tdesign-component/lib/src/components/picker/t_multi_picker.dart b/tdesign-component/lib/src/components/picker/t_multi_picker.dart index 43811cc24..97306c1a2 100644 --- a/tdesign-component/lib/src/components/picker/t_multi_picker.dart +++ b/tdesign-component/lib/src/components/picker/t_multi_picker.dart @@ -8,6 +8,15 @@ import 'no_wave_behavior.dart'; typedef MultiPickerCallback = void Function(List selected); +/// 列选项变化时的回调类型 +/// [columnIndex] 发生变化的列索引 +/// [selectedData] 当前各列已选中的数据 +/// 返回值:columnIndex+1 列需要展示的新数据列表 +typedef LinkedPickerColumnChangedCallback = Future Function( + int columnIndex, + List selectedData, +); + /// 项之间无联动的多项选择器 class TMultiPicker extends StatelessWidget { /// 选择器标题 @@ -424,6 +433,12 @@ class TMultiLinkedPicker extends StatefulWidget { /// 是否显示头部内容 final bool header; + /// 列选项变化时的回调,用于动态加载下一列数据 + /// + /// 当第 [columnIndex] 列选项发生变化时,调用此回调获取第 [columnIndex]+1 列的数据。 + /// 若不提供,则沿用 [data] Map 中的数据(向后兼容)。 + final LinkedPickerColumnChangedCallback? onColumnChanged; + const TMultiLinkedPicker({ this.title, required this.onConfirm, @@ -452,6 +467,7 @@ class TMultiLinkedPicker extends StatefulWidget { this.itemBuilder, this.keepSameSelection = false, this.header = true, + this.onColumnChanged, Key? key, }) : super(key: key); @@ -598,11 +614,14 @@ class _TMultiLinkedPickerState extends State { physics: const FixedExtentScrollPhysics(), onSelectedItemChanged: (index) { if (index >= 0 && index < model.presentData[position].length) { + final hasCallback = widget.onColumnChanged != null && + position < widget.columnNum - 1; setState(() { model.refreshPresentDataAndController( position, index, false, + cascadeNext: !hasCallback, ); if (index >= model.presentData[position].length - 5 && model.hasMoreData[position]) { @@ -621,12 +640,33 @@ class _TMultiLinkedPickerState extends State { pickerHeight = pickerHeight - Random().nextDouble() / 100000000; }); + + if (hasCallback) { + _loadNextColumnData(position); + } } }, childDelegate: ListWheelChildBuilderDelegate( childCount: model.presentData[position].length + (model.hasMoreData[position] ? 1 : 0), builder: (context, index) { + // 展示加载中占位 + if (model.isLoading[position] && + index == 0 && + model.presentData[position].length == 1 && + model.presentData[position].first == + MultiLinkedPickerModel.placeData) { + return Container( + alignment: Alignment.center, + height: pickerHeight / widget.pickerItemCount, + child: Text( + context.resource.loadingWithPoint, + style: TextStyle( + color: TTheme.of(context).textColorPlaceholder, + ), + ), + ); + } if (index >= model.presentData[position].length) { // 加载更多指示器 return Container( @@ -665,6 +705,30 @@ class _TMultiLinkedPickerState extends State { ); } + /// 调用 [widget.onColumnChanged] 异步加载下一列数据 + Future _loadNextColumnData(int columnIndex) async { + final nextColumn = columnIndex + 1; + setState(() { + model.resetColumnsAfter(columnIndex); + model.setLoading(nextColumn, true); + }); + try { + final newData = + await widget.onColumnChanged!(columnIndex, model.selectedData); + if (!mounted) return; + setState(() { + model.updateColumnData(nextColumn, newData); + model.setLoading(nextColumn, false); + }); + } catch (_) { + if (!mounted) return; + setState(() { + model.updateColumnData(nextColumn, []); + model.setLoading(nextColumn, false); + }); + } + } + Widget _buildHeader(BuildContext context) { final padding = TTheme.of(context).spacer16; @@ -781,6 +845,9 @@ class MultiLinkedPickerModel { /// 每列的总数据量 late List totalCounts; + /// 每列是否正在异步加载数据 + late List isLoading; + MultiLinkedPickerModel({ required this.data, required this.columnNum, @@ -792,6 +859,7 @@ class MultiLinkedPickerModel { currentPages = List.generate(columnNum, (_) => 0); hasMoreData = List.generate(columnNum, (_) => true); totalCounts = List.generate(columnNum, (_) => 0); + isLoading = List.generate(columnNum, (_) => false); for (var i = 0; i < columnNum; ++i) { if (i >= initialData.length) { selectedData.add(''); @@ -892,11 +960,14 @@ class MultiLinkedPickerModel { /// [position] 变动的列 /// [selectedIndex] 对应选中的index /// [jump] 是否需要jumpToItem + /// [cascadeNext] 是否自动级联更新后续列(默认 true); + /// 传 false 时仅更新当前列的选中状态,后续列由外部的 [LinkedPickerColumnChangedCallback] 负责 void refreshPresentDataAndController( int position, int selectedIndex, - bool jump, - ) { + bool jump, { + bool cascadeNext = true, + }) { // 严格的边界检查 if (position >= presentData.length || selectedIndex >= presentData[position].length || @@ -916,7 +987,7 @@ class MultiLinkedPickerModel { hasMoreData[position]) { loadMoreData(position); } - if (position < columnNum - 1) { + if (cascadeNext && position < columnNum - 1) { List nextColumnData; if (presentData[position].length == 1 && presentData[position].first == placeData) { @@ -939,4 +1010,50 @@ class MultiLinkedPickerModel { refreshPresentDataAndController(position + 1, 0, true); } } + + /// 将 [columnIndex] 之后的所有列重置为占位状态 + /// 重置后 hasMoreData 设为 false,避免在动态加载期间额外展示"加载更多"指示器 + void resetColumnsAfter(int columnIndex) { + for (var i = columnIndex + 1; i < columnNum; i++) { + while (presentData.length <= i) { + presentData.add([placeData]); + } + presentData[i] = [placeData]; + currentPages[i] = 0; + hasMoreData[i] = false; + if (i < controllers.length) { + controllers[i].jumpToItem(0); + } + } + } + + /// 将 [columnIndex] 列的展示数据更新为 [newData] + /// + /// 若 [newData] 为空,则使用占位符 [placeData]。 + /// 同时将该列 controller 重置到第 0 项。 + void updateColumnData(int columnIndex, List newData) { + while (presentData.length <= columnIndex) { + presentData.add([placeData]); + } + final data = newData.isEmpty ? [placeData] : newData; + presentData[columnIndex] = data; + currentPages[columnIndex] = 0; + hasMoreData[columnIndex] = false; + while (controllers.length <= columnIndex) { + controllers.add(FixedExtentScrollController(initialItem: 0)); + } + controllers[columnIndex].jumpToItem(0); + if (columnIndex < selectedData.length) { + selectedData[columnIndex] = + data.isNotEmpty ? data.first : placeData; + selectedIndexes[columnIndex] = 0; + } + } + + /// 设置 [columnIndex] 列的加载状态 + void setLoading(int columnIndex, bool loading) { + if (columnIndex >= 0 && columnIndex < columnNum) { + isLoading[columnIndex] = loading; + } + } } diff --git a/tdesign-component/lib/src/components/picker/t_picker.dart b/tdesign-component/lib/src/components/picker/t_picker.dart index d09bc04cb..7d828c528 100644 --- a/tdesign-component/lib/src/components/picker/t_picker.dart +++ b/tdesign-component/lib/src/components/picker/t_picker.dart @@ -198,6 +198,11 @@ class TPicker { bool keepSameSelection = false, Color? barrierColor, + /// 列选项变化时的回调,用于动态加载下一列数据 + /// 当第 [columnIndex] 列选项变化时调用,返回第 [columnIndex]+1 列的数据 + /// 若不提供,则沿用 [data] Map 中的数据(向后兼容) + LinkedPickerColumnChangedCallback? onColumnChanged, + /// todo 未传参 Duration duration = const Duration(milliseconds: 100), }) { @@ -231,6 +236,7 @@ class TPicker { itemBuilder: itemBuilder, customSelectWidget: customSelectWidget, keepSameSelection: keepSameSelection, + onColumnChanged: onColumnChanged, // itemDistanceCalculator: itemDistanceCalculator, ); }, diff --git a/tdesign-component/test/td_picker_test.dart b/tdesign-component/test/td_picker_test.dart new file mode 100644 index 000000000..b78f4ac4a --- /dev/null +++ b/tdesign-component/test/td_picker_test.dart @@ -0,0 +1,239 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +void main() { + group('MultiLinkedPickerModel — 基础初始化', () { + late Map testData; + + setUp(() { + testData = { + '广东省': { + '深圳市': ['南山区', '宝安区', '罗湖区'], + '广州市': ['天河区', '越秀区'], + }, + '浙江省': { + '杭州市': ['西湖区', '余杭区'], + '宁波市': ['江东区', '北仑区'], + }, + }; + }); + + // TC-06 + test('初始化时 isLoading 全为 false', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + expect(model.isLoading.length, 3); + expect(model.isLoading.every((v) => v == false), isTrue); + }); + + test('初始化时 presentData 正确填充第 0 列', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + expect(model.presentData[0], containsAll(['广东省', '浙江省'])); + }); + + test('初始化时 selectedData 与 initialData 对齐', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + expect(model.selectedData[0], '广东省'); + expect(model.selectedData[1], '深圳市'); + expect(model.selectedData[2], '南山区'); + }); + }); + + group('MultiLinkedPickerModel — resetColumnsAfter', () { + late Map testData; + + setUp(() { + testData = { + '广东省': { + '深圳市': ['南山区', '宝安区'], + }, + '浙江省': { + '杭州市': ['西湖区', '余杭区'], + }, + }; + }); + + // TC-04:切换第 0 列后,第 1、2 列均重置为占位 + test('resetColumnsAfter(0) 将第 1、2 列重置为占位', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + model.resetColumnsAfter(0); + expect(model.presentData[1], [MultiLinkedPickerModel.placeData]); + expect(model.presentData[2], [MultiLinkedPickerModel.placeData]); + }); + + test('resetColumnsAfter(1) 只重置第 2 列', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + final col1DataBefore = List.from(model.presentData[1]); + model.resetColumnsAfter(1); + expect(model.presentData[1], col1DataBefore); + expect(model.presentData[2], [MultiLinkedPickerModel.placeData]); + }); + + test('resetColumnsAfter 在最后一列时不操作', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + final col2DataBefore = List.from(model.presentData[2]); + model.resetColumnsAfter(2); + expect(model.presentData[2], col2DataBefore); + }); + }); + + group('MultiLinkedPickerModel — updateColumnData', () { + late Map testData; + + setUp(() { + testData = { + '广东省': { + '深圳市': ['南山区'], + }, + }; + }); + + // TC-02:同步回调返回新数据后更新列 + test('updateColumnData 正确替换指定列数据', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + model.updateColumnData(1, ['北京市', '上海市']); + expect(model.presentData[1], ['北京市', '上海市']); + }); + + // TC-05:回调返回空列表时,使用占位 + test('updateColumnData 传入空列表时使用占位符', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + model.updateColumnData(1, []); + expect(model.presentData[1], [MultiLinkedPickerModel.placeData]); + }); + + test('updateColumnData 同时重置该列 controller 到第 0 项', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + model.updateColumnData(1, ['杭州市', '宁波市']); + expect(model.controllers[1].initialItem, 0); + }); + }); + + group('MultiLinkedPickerModel — refreshPresentDataAndController cascadeNext', () { + late Map testData; + + setUp(() { + testData = { + '广东省': { + '深圳市': ['南山区', '宝安区'], + '广州市': ['天河区', '越秀区'], + }, + '浙江省': { + '杭州市': ['西湖区', '余杭区'], + '宁波市': ['江东区', '北仑区'], + }, + }; + }); + + // TC-01:不传 onColumnChanged 时,原有逻辑正常级联(回归) + test('cascadeNext=true 时第 0 列变化后第 1 列正确级联', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + final zhejiangIndex = model.presentData[0].indexOf('浙江省'); + model.refreshPresentDataAndController(0, zhejiangIndex, false); + expect( + model.presentData[1], + containsAll(['杭州市', '宁波市']), + ); + }); + + // cascadeNext=false 时,第 1 列不应从 data Map 更新(由外部 onColumnChanged 负责) + test('cascadeNext=false 时第 0 列变化后第 1 列保持原样', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + final prevCol1Data = List.from(model.presentData[1]); + final zhejiangIndex = model.presentData[0].indexOf('浙江省'); + model.refreshPresentDataAndController(0, zhejiangIndex, false, cascadeNext: false); + expect(model.presentData[1], prevCol1Data); + expect(model.selectedData[0], '浙江省'); + }); + }); + + group('MultiLinkedPickerModel — isLoading 状态管理', () { + late Map testData; + + setUp(() { + testData = { + '广东省': { + '深圳市': ['南山区'], + }, + }; + }); + + // TC-03:异步加载期间 isLoading 状态正确 + test('setLoading 设置指定列的加载状态', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + model.setLoading(1, true); + expect(model.isLoading[1], isTrue); + model.setLoading(1, false); + expect(model.isLoading[1], isFalse); + }); + + test('setLoading 不影响其他列的加载状态', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + model.setLoading(1, true); + expect(model.isLoading[0], isFalse); + expect(model.isLoading[2], isFalse); + }); + + // BC-02:最后一列变化时不应触发加载(无下一列) + test('最后一列无需加载下一列数据', () { + final model = MultiLinkedPickerModel( + data: testData, + columnNum: 3, + initialData: ['广东省', '深圳市', '南山区'], + ); + // 最后一列 columnIndex = 2 = columnNum - 1,不应触发下一列更新 + expect(2 >= model.columnNum - 1, isTrue); + }); + }); +} From b61e2d99440fc7d4004dbd809f8acb70bbab7738 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:14:03 +0000 Subject: [PATCH 2/2] [autofix.ci] apply automated fixes --- .../assets/api/date-time-picker_api.md | 2 +- .../code/picker.buildDynamicLinkedPicker.txt | 28 +++++++++++++++++++ tdesign-site/src/date-time-picker/README.md | 2 +- tdesign-site/src/picker/README.md | 3 +- 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 tdesign-component/example/assets/code/picker.buildDynamicLinkedPicker.txt diff --git a/tdesign-component/example/assets/api/date-time-picker_api.md b/tdesign-component/example/assets/api/date-time-picker_api.md index b5b57b933..94a29125f 100644 --- a/tdesign-component/example/assets/api/date-time-picker_api.md +++ b/tdesign-component/example/assets/api/date-time-picker_api.md @@ -6,7 +6,7 @@ | 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | | showDatePicker | | required null context, String? title, double? titleHeight, Color? titleDividerColor, required DatePickerCallback? onConfirm, DatePickerCallback? onCancel, DatePickerCallback? onChange, Function(int wheelIndex, int index)? onSelectedItemChanged, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, String? rightText, TextStyle? rightTextStyle, EdgeInsets? padding, double? leftPadding, double? topPadding, double? rightPadding, double? topRadius, Color? backgroundColor, Widget? customSelectWidget, bool useYear, bool useMonth, bool useDay, bool useHour, bool useMinute, bool useSecond, bool useWeekDay, List dateStart, List? dateEnd, List? initialDate, List Function(DateTypeKey key, List nums)? filterItems, double pickerHeight, int pickerItemCount, bool isTimeUnit, ItemBuilderType? itemBuilder, Color? barrierColor, Duration duration, | 显示时间选择器 | -| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, Duration duration, | 显示多级联动选择器 | +| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, LinkedPickerColumnChangedCallback? onColumnChanged, Duration duration, | 显示多级联动选择器 | | showMultiPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List> data, double pickerHeight, int pickerItemCount, List? initialIndexes, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, Widget? customSelectWidget, ItemBuilderType? itemBuilder, Duration duration, Color? barrierColor, | 显示多级选择器 | ``` diff --git a/tdesign-component/example/assets/code/picker.buildDynamicLinkedPicker.txt b/tdesign-component/example/assets/code/picker.buildDynamicLinkedPicker.txt new file mode 100644 index 000000000..720651ea2 --- /dev/null +++ b/tdesign-component/example/assets/code/picker.buildDynamicLinkedPicker.txt @@ -0,0 +1,28 @@ + + Widget buildDynamicLinkedPicker(BuildContext context) { + return TCell( + title: '动态加载(onColumnChanged)', + note: selected_3.isEmpty ? '请选择' : selected_3, + arrow: true, + onClick: (click) { + TPicker.showMultiLinkedPicker( + context, + title: '选择地区(动态加载)', + onConfirm: (selected) { + setState(() { + selected_3 = selected.join(' '); + }); + Navigator.of(context).pop(); + }, + // data 传空 Map,完全由 onColumnChanged 提供数据 + data: { + '广东省': {}, + '浙江省': {}, + }, + columnNum: 3, + initialData: ['广东省', '', ''], + onColumnChanged: _fetchNextColumnData, + ); + }, + ); + } \ No newline at end of file diff --git a/tdesign-site/src/date-time-picker/README.md b/tdesign-site/src/date-time-picker/README.md index c968c4fbc..7f31ea2c3 100644 --- a/tdesign-site/src/date-time-picker/README.md +++ b/tdesign-site/src/date-time-picker/README.md @@ -338,7 +338,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; | 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | | showDatePicker | | required null context, String? title, double? titleHeight, Color? titleDividerColor, required DatePickerCallback? onConfirm, DatePickerCallback? onCancel, DatePickerCallback? onChange, Function(int wheelIndex, int index)? onSelectedItemChanged, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, String? rightText, TextStyle? rightTextStyle, EdgeInsets? padding, double? leftPadding, double? topPadding, double? rightPadding, double? topRadius, Color? backgroundColor, Widget? customSelectWidget, bool useYear, bool useMonth, bool useDay, bool useHour, bool useMinute, bool useSecond, bool useWeekDay, List dateStart, List? dateEnd, List? initialDate, List Function(DateTypeKey key, List nums)? filterItems, double pickerHeight, int pickerItemCount, bool isTimeUnit, ItemBuilderType? itemBuilder, Color? barrierColor, Duration duration, | 显示时间选择器 | -| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, Duration duration, | 显示多级联动选择器 | +| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, LinkedPickerColumnChangedCallback? onColumnChanged, Duration duration, | 显示多级联动选择器 | | showMultiPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List> data, double pickerHeight, int pickerItemCount, List? initialIndexes, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, Widget? customSelectWidget, ItemBuilderType? itemBuilder, Duration duration, Color? barrierColor, | 显示多级选择器 | ``` diff --git a/tdesign-site/src/picker/README.md b/tdesign-site/src/picker/README.md index b93f2712e..f6d36c181 100644 --- a/tdesign-site/src/picker/README.md +++ b/tdesign-site/src/picker/README.md @@ -255,6 +255,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; | leftTextStyle | TextStyle? | - | 自定义左侧文案样式 | | onCancel | MultiPickerCallback? | - | 选择器取消按钮回调 | | onChange | MultiPickerCallback? | - | todo 选择器数据改变时回调 | +| onColumnChanged | LinkedPickerColumnChangedCallback? | - | 列选项变化时的回调,用于动态加载下一列数据 | | onConfirm | MultiPickerCallback? | - | 选择器确认按钮回调 | | padding | EdgeInsets? | - | 适配padding | | pickerHeight | double | 200 | | @@ -292,7 +293,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; | 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | | showDatePicker | | required null context, String? title, double? titleHeight, Color? titleDividerColor, required DatePickerCallback? onConfirm, DatePickerCallback? onCancel, DatePickerCallback? onChange, Function(int wheelIndex, int index)? onSelectedItemChanged, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, String? rightText, TextStyle? rightTextStyle, EdgeInsets? padding, double? leftPadding, double? topPadding, double? rightPadding, double? topRadius, Color? backgroundColor, Widget? customSelectWidget, bool useYear, bool useMonth, bool useDay, bool useHour, bool useMinute, bool useSecond, bool useWeekDay, List dateStart, List? dateEnd, List? initialDate, List Function(DateTypeKey key, List nums)? filterItems, double pickerHeight, int pickerItemCount, bool isTimeUnit, ItemBuilderType? itemBuilder, Color? barrierColor, Duration duration, | 显示时间选择器 | -| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, Duration duration, | 显示多级联动选择器 | +| showMultiLinkedPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List initialData, required Map data, required int columnNum, double pickerHeight, int pickerItemCount, Widget? customSelectWidget, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, ItemBuilderType? itemBuilder, bool keepSameSelection, Color? barrierColor, LinkedPickerColumnChangedCallback? onColumnChanged, Duration duration, | 显示多级联动选择器 | | showMultiPicker | | required null context, String? title, required MultiPickerCallback? onConfirm, MultiPickerCallback? onCancel, required List> data, double pickerHeight, int pickerItemCount, List? initialIndexes, String? rightText, String? leftText, TextStyle? leftTextStyle, TextStyle? centerTextStyle, TextStyle? rightTextStyle, double? titleHeight, double? topPadding, double? leftPadding, double? rightPadding, Color? titleDividerColor, Color? backgroundColor, double? topRadius, EdgeInsets? padding, Widget? customSelectWidget, ItemBuilderType? itemBuilder, Duration duration, Color? barrierColor, | 显示多级选择器 |