在移动应用开发中,过滤器组件几乎是列表页面的标配功能。最近我在重构公司电商APP的商品筛选模块时,系统梳理了Flutter中实现过滤器组件的四种典型方案。这些方案各有利弊,适用于不同复杂度的场景,今天就把我的实践心得分享给大家。
先看一个典型场景:我们需要实现一个多选过滤器,允许用户按商品属性(如颜色、尺寸)进行筛选。组件需要满足三个核心需求:
这是最直观的实现方式,父组件直接传递一个可变的选中状态集合给子组件:
dart复制Filters<int>(
selectedValues: [1, 3], // 当前选中的值
items: [
FilterItem(label: '红色', value: 1),
FilterItem(label: '蓝色', value: 2),
FilterItem(label: '黑色', value: 3),
],
onChanged: (newValues) {
setState(() {
selectedValues = newValues;
});
}
)
实现原理:
selectedValues状态onChanged回调通知状态变更setState更新状态并重建子组件优点:
缺点:
提示:当过滤器层级较深时,这种方案会导致状态需要通过多个中间组件传递,形成"prop drilling"问题。
dart复制final filterKey = GlobalKey<FiltersState>();
// 使用时
Filters(
key: filterKey,
items: [...],
);
// 获取状态时
filterKey.currentState?.selectedValues;
实现要点:
适用场景:
潜在问题:
这是最符合Flutter设计理念的方案:
dart复制typedef FilterCallback = void Function(Set<int> selectedValues);
Filters(
items: [...],
onSelectionChanged: (values) {
// 处理新的选中状态
}
)
最佳实践:
dart复制class _FiltersState extends State<Filters> {
final Set<int> _selectedValues = {};
void _handleTap(int value) {
setState(() {
if (_selectedValues.contains(value)) {
_selectedValues.remove(value);
} else {
_selectedValues.add(value);
}
widget.onSelectionChanged(_selectedValues);
});
}
}
性能优化技巧:
对于复杂状态管理,可以使用ValueNotifier:
dart复制final selectedValues = ValueNotifier<Set<int>>({});
Filters(
selectedValues: selectedValues,
items: [...]
)
// 监听变化
selectedValues.addListener(() {
print('选择变化:${selectedValues.value}');
});
架构优势:
实现细节:
dart复制ValueListenableBuilder<Set<int>>(
valueListenable: selectedValues,
builder: (context, values, _) {
return Wrap(
children: widget.items.map((item) {
return FilterChip(
selected: values.contains(item.value),
onSelected: (selected) {
final newValues = Set<int>.from(values);
selected ? newValues.add(item.value) : newValues.remove(item.value);
selectedValues.value = newValues;
},
label: Text(item.label),
);
}).toList(),
);
}
)
| 方案 | 维护难度 | 性能表现 | 适用场景 | 状态同步方式 |
|---|---|---|---|---|
| 可变状态传递 | ★★☆ | ★★☆ | 简单组件、浅层级 | 回调触发setState |
| GlobalKey访问 | ★☆☆ | ★★★ | 需要主动查询状态 | 手动获取当前状态 |
| 回调函数 | ★★★ | ★★★ | 大多数常规场景 | 变更即时回调 |
| ValueNotifier | ★★☆ | ★★☆ | 复杂状态、跨组件共享 | 自动通知监听器 |
选型建议:
下面是一个基于回调函数的生产级实现:
dart复制class FilterItem<T> {
final String label;
final T value;
final IconData? icon;
const FilterItem({
required this.label,
required this.value,
this.icon,
});
}
class Filters<T> extends StatefulWidget {
final List<FilterItem<T>> items;
final Set<T> initialSelected;
final void Function(Set<T> selected)? onSelectionChanged;
final Axis direction;
final double spacing;
const Filters({
super.key,
required this.items,
this.initialSelected = const {},
this.onSelectionChanged,
this.direction = Axis.horizontal,
this.spacing = 8.0,
});
@override
State<Filters<T>> createState() => _FiltersState<T>();
}
class _FiltersState<T> extends State<Filters<T>> {
late Set<T> _selectedValues;
@override
void initState() {
super.initState();
_selectedValues = Set<T>.from(widget.initialSelected);
}
void _handleSelect(T value, bool selected) {
setState(() {
if (selected) {
_selectedValues.add(value);
} else {
_selectedValues.remove(value);
}
widget.onSelectionChanged?.call(Set<T>.from(_selectedValues));
});
}
@override
Widget build(BuildContext context) {
return Wrap(
direction: widget.direction,
spacing: widget.spacing,
children: widget.items.map((item) {
return FilterChip(
selected: _selectedValues.contains(item.value),
onSelected: (selected) => _handleSelect(item.value, selected),
label: Text(item.label),
avatar: item.icon != null ? Icon(item.icon, size: 18) : null,
);
}).toList(),
);
}
}
使用示例:
dart复制// 在父组件中
Set<int> selectedSizes = {};
Filters<int>(
items: const [
FilterItem(label: 'S', value: 1),
FilterItem(label: 'M', value: 2),
FilterItem(label: 'L', value: 3),
],
initialSelected: selectedSizes,
onSelectionChanged: (values) {
setState(() {
selectedSizes = values;
});
// 可以在这里触发筛选逻辑
},
)
dart复制// 将FilterItem定义为const
const FilterItem(label: '红色', value: 1)
// 使用const构造函数
Filters(
items: const [...],
)
dart复制@immutable
class FilterItem<T> {
// ...
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is FilterItem &&
runtimeType == other.runtimeType &&
label == other.label &&
value == other.value;
@override
int get hashCode => label.hashCode ^ value.hashCode;
}
dart复制PaginatedFilters(
itemBuilder: (context, index) {
if (index >= items.length) {
loadMoreItems();
return LoadingIndicator();
}
return FilterItem(item: items[index]);
}
)
问题1:回调函数被频繁触发
dart复制Timer? _debounceTimer;
void _handleSelect(T value) {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
// 实际处理逻辑
});
}
问题2:状态不同步
dart复制@override
void didUpdateWidget(Filters<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialSelected != oldWidget.initialSelected) {
_selectedValues = Set<T>.from(widget.initialSelected);
}
}
问题3:内存泄漏
dart复制@override
void dispose() {
selectedValues.removeListener(_listener);
super.dispose();
}
对于需要多个过滤条件组合的场景:
dart复制class CombinedFilters extends StatelessWidget {
final Map<String, Set<int>> selectedValues;
final void Function(String filterType, Set<int> values) onChanged;
Widget _buildFilter(String type, List<FilterItem> items) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(type, style: TextStyle(fontWeight: FontWeight.bold)),
Filters(
items: items,
initialSelected: selectedValues[type] ?? {},
onSelectionChanged: (values) => onChanged(type, values),
),
],
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
_buildFilter('颜色', colorItems),
SizedBox(height: 16),
_buildFilter('尺寸', sizeItems),
],
);
}
}
对于使用BLoC的大型应用:
dart复制class FilterBloc extends Bloc<FilterEvent, FilterState> {
final Map<FilterType, Set<String>> _selectedFilters = {};
Stream<FilterState> mapEventToState(FilterEvent event) async* {
if (event is ToggleFilter) {
_updateFilter(event.type, event.value, event.selected);
yield FilterUpdated(_selectedFilters);
}
}
void _updateFilter(FilterType type, String value, bool selected) {
_selectedFilters[type] ??= {};
if (selected) {
_selectedFilters[type]!.add(value);
} else {
_selectedFilters[type]!.remove(value);
}
}
}
// 在UI层
BlocBuilder<FilterBloc, FilterState>(
builder: (context, state) {
return Filters(
items: state.availableFilters,
selectedValues: state.selectedFilters,
onChanged: (values) {
context.read<FilterBloc>().add(FilterUpdated(values));
},
);
}
)
为过滤器添加选中动画:
dart复制AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: _selectedValues.contains(item.value)
? Icon(Icons.check, key: ValueKey('selected-${item.value}'))
: Icon(Icons.add, key: ValueKey('unselected-${item.value}')),
)
在实现过滤器组件时,我最深刻的体会是:没有绝对的最佳方案,只有最适合当前场景的选择。对于简单的个人项目,回调函数方案完全够用;而在大型商业应用中,可能需要结合状态管理方案实现更复杂的逻辑。关键是要理解每种方案的适用场景和trade-off,才能做出合理的设计决策。