2025-12-12 09:11:30 +08:00
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter/cupertino.dart';
|
2026-04-08 15:03:56 +08:00
|
|
|
|
|
|
|
|
|
|
/// 选择结果(新增)
|
|
|
|
|
|
class PickerResult<T> {
|
|
|
|
|
|
final T value;
|
|
|
|
|
|
final int index;
|
|
|
|
|
|
|
|
|
|
|
|
PickerResult(this.value, this.index);
|
|
|
|
|
|
}
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
|
|
|
|
|
/// 通用底部弹窗选择器
|
|
|
|
|
|
class BottomPicker {
|
|
|
|
|
|
/// 显示底部选择器弹窗
|
|
|
|
|
|
///
|
2026-04-08 15:03:56 +08:00
|
|
|
|
/// [withIndex] = true 时返回 PickerResult<T>
|
|
|
|
|
|
/// [withIndex] = false 时返回 T(兼容旧代码)
|
|
|
|
|
|
static Future<dynamic> show<T>(
|
|
|
|
|
|
BuildContext context, {
|
|
|
|
|
|
required List<T> items,
|
|
|
|
|
|
required Widget Function(T item) itemBuilder,
|
|
|
|
|
|
int initialIndex = 0,
|
|
|
|
|
|
double itemExtent = 50.0,
|
|
|
|
|
|
double height = 250,
|
|
|
|
|
|
|
|
|
|
|
|
/// 新增参数
|
|
|
|
|
|
bool withIndex = false,
|
|
|
|
|
|
}) {
|
2025-12-12 09:11:30 +08:00
|
|
|
|
if (items.isEmpty) return Future.value(null);
|
|
|
|
|
|
|
|
|
|
|
|
final safeIndex = initialIndex.clamp(0, items.length - 1);
|
2026-04-08 15:03:56 +08:00
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
T selected = items[safeIndex];
|
2026-04-08 15:03:56 +08:00
|
|
|
|
int selectedIndex = safeIndex;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
|
2026-04-08 15:03:56 +08:00
|
|
|
|
return showModalBottomSheet<dynamic>(
|
2025-12-12 09:11:30 +08:00
|
|
|
|
context: context,
|
|
|
|
|
|
backgroundColor: Colors.white,
|
|
|
|
|
|
shape: const RoundedRectangleBorder(
|
|
|
|
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
|
|
|
|
|
),
|
|
|
|
|
|
builder: (ctx) {
|
|
|
|
|
|
return SizedBox(
|
|
|
|
|
|
height: height,
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
// 按钮行
|
|
|
|
|
|
Padding(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
horizontal: 16,
|
|
|
|
|
|
vertical: 8,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
TextButton(
|
|
|
|
|
|
onPressed: () {
|
|
|
|
|
|
FocusScope.of(context).unfocus();
|
|
|
|
|
|
Navigator.of(ctx).pop();
|
|
|
|
|
|
},
|
2026-04-08 15:03:56 +08:00
|
|
|
|
child: const Text(
|
|
|
|
|
|
'取消',
|
|
|
|
|
|
style: TextStyle(color: Colors.black54, fontSize: 16),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
TextButton(
|
|
|
|
|
|
onPressed: () {
|
|
|
|
|
|
FocusScope.of(context).unfocus();
|
2026-04-08 15:03:56 +08:00
|
|
|
|
|
|
|
|
|
|
if (withIndex) {
|
|
|
|
|
|
Navigator.of(ctx).pop(
|
|
|
|
|
|
PickerResult(selected, selectedIndex),
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Navigator.of(ctx).pop(selected);
|
|
|
|
|
|
}
|
2025-12-12 09:11:30 +08:00
|
|
|
|
},
|
2026-04-08 15:03:56 +08:00
|
|
|
|
child: const Text(
|
|
|
|
|
|
'确定',
|
|
|
|
|
|
style: TextStyle(color: Colors.blue, fontSize: 16),
|
|
|
|
|
|
),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const Divider(height: 1),
|
2026-04-08 15:03:56 +08:00
|
|
|
|
|
2025-12-12 09:11:30 +08:00
|
|
|
|
// 滚动选择器
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
child: CupertinoPicker(
|
|
|
|
|
|
scrollController: FixedExtentScrollController(
|
|
|
|
|
|
initialItem: initialIndex,
|
|
|
|
|
|
),
|
|
|
|
|
|
itemExtent: itemExtent,
|
|
|
|
|
|
onSelectedItemChanged: (index) {
|
|
|
|
|
|
selected = items[index];
|
2026-04-08 15:03:56 +08:00
|
|
|
|
selectedIndex = index;
|
2025-12-12 09:11:30 +08:00
|
|
|
|
},
|
2026-04-08 15:03:56 +08:00
|
|
|
|
children: items
|
|
|
|
|
|
.map((item) => Center(child: itemBuilder(item)))
|
|
|
|
|
|
.toList(),
|
2025-12-12 09:11:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-04-08 15:03:56 +08:00
|
|
|
|
}
|