|
@@ -0,0 +1,158 @@
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+
|
|
|
+/// 按钮式多选框组
|
|
|
+class VCheckBoxButtonGroup<T, TValue> extends StatefulWidget {
|
|
|
+ final List<T> source;
|
|
|
+ final List<TValue>? values;
|
|
|
+ final String Function(T data) labelGetter;
|
|
|
+ final TValue Function(T data) valueGetter;
|
|
|
+ final ValueChanged<List<TValue>>? onChanged;
|
|
|
+ final double? itemWidth;
|
|
|
+
|
|
|
+ const VCheckBoxButtonGroup({
|
|
|
+ super.key,
|
|
|
+ required this.source,
|
|
|
+ this.values,
|
|
|
+ required this.labelGetter,
|
|
|
+ required this.valueGetter,
|
|
|
+ this.onChanged,
|
|
|
+ this.itemWidth,
|
|
|
+ });
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<StatefulWidget> createState() => _VCheckBoxGroupState<T, TValue>();
|
|
|
+}
|
|
|
+
|
|
|
+class _VCheckBoxGroupState<T, TValue>
|
|
|
+ extends State<VCheckBoxButtonGroup<T, TValue>> {
|
|
|
+ late List<TValue> _checkedValues;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ _checkedValues = widget.values ?? [];
|
|
|
+ super.initState();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final children = <Widget>[];
|
|
|
+ final length = widget.source.length;
|
|
|
+ for (var i = 0; i < length; i++) {
|
|
|
+ final e = widget.source[i];
|
|
|
+ children.add(
|
|
|
+ SizedBox(
|
|
|
+ width: widget.itemWidth ?? 160,
|
|
|
+ child: VCheckBoxButton(
|
|
|
+ label: widget.labelGetter(e),
|
|
|
+ isChecked: false,
|
|
|
+ onChanged: (value) {
|
|
|
+ _onItemChanged(e, value);
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return Wrap(
|
|
|
+ spacing: 12,
|
|
|
+ runSpacing: 8,
|
|
|
+ children: children,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onItemChanged(T data, bool isChecked) {
|
|
|
+ final value = widget.valueGetter(data);
|
|
|
+ if (isChecked) {
|
|
|
+ if (_checkedValues.contains(value) == false) {
|
|
|
+ _checkedValues.add(value);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _checkedValues.remove(value);
|
|
|
+ }
|
|
|
+ widget.onChanged?.call(_checkedValues);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// 按钮式多选框
|
|
|
+class VCheckBoxButton extends StatefulWidget {
|
|
|
+ /// 文本
|
|
|
+ final String label;
|
|
|
+
|
|
|
+ /// 是否默认选中
|
|
|
+ final bool? isChecked;
|
|
|
+
|
|
|
+ /// 选中状态变更
|
|
|
+ final ValueChanged<bool>? onChanged;
|
|
|
+
|
|
|
+ const VCheckBoxButton({
|
|
|
+ super.key,
|
|
|
+ required this.label,
|
|
|
+ this.isChecked,
|
|
|
+ this.onChanged,
|
|
|
+ });
|
|
|
+ @override
|
|
|
+ State<StatefulWidget> createState() => _VCheckBoxState();
|
|
|
+}
|
|
|
+
|
|
|
+class _VCheckBoxState extends State<VCheckBoxButton> {
|
|
|
+ bool _isChecked = false;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ if (widget.isChecked != null) {
|
|
|
+ _isChecked = widget.isChecked!;
|
|
|
+ }
|
|
|
+ super.initState();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ const height = 56.0;
|
|
|
+ const borderRadius = height / 2;
|
|
|
+ final primaryColor = Theme.of(context).primaryColor;
|
|
|
+ return Material(
|
|
|
+ child: Ink(
|
|
|
+ child: InkWell(
|
|
|
+ borderRadius: BorderRadius.circular(borderRadius),
|
|
|
+ onTap: () {
|
|
|
+ setState(() {
|
|
|
+ _isChecked = !_isChecked;
|
|
|
+ widget.onChanged?.call(_isChecked);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ padding: const EdgeInsets.only(left: 0, right: borderRadius),
|
|
|
+ alignment: Alignment.center,
|
|
|
+ height: height,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: _isChecked ? primaryColor : Colors.grey.shade200,
|
|
|
+ borderRadius: BorderRadius.circular(borderRadius),
|
|
|
+ border: _isChecked ? null : Border.all(color: primaryColor),
|
|
|
+ ),
|
|
|
+ child: Row(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ if (_isChecked) ...[
|
|
|
+ const Icon(
|
|
|
+ Icons.check_rounded,
|
|
|
+ color: Colors.green,
|
|
|
+ size: 24,
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ ],
|
|
|
+ if (!_isChecked) const SizedBox(width: 32),
|
|
|
+ Text(
|
|
|
+ widget.label,
|
|
|
+ style: TextStyle(
|
|
|
+ color: _isChecked ? Colors.white : primaryColor,
|
|
|
+ fontSize: 20,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|