筛选页面¶
1,左侧导航栏可以上下滑动,选中某一个的时候,右侧内容区域自动滚动到选中模块。 2,右侧内容区域,标识单选的是只能单选,添加选中样式,后面没有单选的,都是多选,添加选中和取消选中效果和功能。
import 'package:flutter/material.dart';
class FilterPage extends StatefulWidget {
@override
_FilterPageState createState() => _FilterPageState();
}
class _FilterPageState extends State<FilterPage> {
final ScrollController _scrollController = ScrollController();
final List<String> _sections = ['为你推荐', '学历要求', '薪资待遇(单选)'];
final Map<String, List<String>> _options = {
'为你推荐': ['SRE', 'PLC', '手游测试', 'QGIS', '运维', '地理信息'],
'学历要求': ['全部', '初中及以下', '中专/中技', '高中', '大专', '本科', '硕士', '博士'],
'薪资待遇(单选)': ['全部', '15K以下', '15-25K', '25-35K', '35-45K', '45K以上', '+ 自定义薪资'],
};
int _selectedNavIndex = 0;
Map<String, String?> _singleSelect = {};
Map<String, Set<String>> _multiSelect = {};
final Map<String, GlobalKey> _sectionKeys = {};
final Map<String, double> _sectionHeights = {};
@override
void initState() {
super.initState();
_sections.forEach((section) {
_singleSelect[section] = null;
_multiSelect[section] = {};
_sectionKeys[section] = GlobalKey();
});
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
WidgetsBinding.instance.addPostFrameCallback((_) {
_calculateSectionHeights();
});
}
//计算每一个分区的高度
void _calculateSectionHeights() {
for (String section in _sections) {
final key = _sectionKeys[section];
final context = key?.currentContext;
if (context != null) {
final box = context.findRenderObject() as RenderBox;
final height = box.size.height;
_sectionHeights[section] = height;
}
}
}
void _onScroll() {
double offset = _scrollController.offset;
double totalOffset = 0.0;
for (int i = 0; i < _sections.length; i++) {
totalOffset += _sectionHeights[_sections[i]] ?? 0.0;
if (offset < totalOffset) {
setState(() {
_selectedNavIndex = i;
});
break;
}
}
}
void _scrollToSection(int index) {
double offset = 0.0;
for (int i = 0; i < index; i++) {
offset += _sectionHeights[_sections[i]] ?? 0.0;
}
_scrollController.animateTo(offset,
duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('筛选'),
leading: IconButton(
icon: Icon(Icons.close),
onPressed: () {
// 关闭按钮的处理
},
),
),
body: Row(
children: [
// 左侧导航栏
NavigationRail(
selectedIndex: _selectedNavIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedNavIndex = index;
});
_scrollToSection(index);
},
labelType: NavigationRailLabelType.all,
destinations: _sections.map((section) {
return NavigationRailDestination(
icon: SizedBox.shrink(),
label: Text(section),
);
}).toList(),
),
VerticalDivider(thickness: 1, width: 1),
// 右侧内容区域
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _sections.length,
itemBuilder: (context, index) {
String section = _sections[index];
return Padding(
key: _sectionKeys[section],
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(section, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _options[section]!.map((option) {
bool isSelected = section == '薪资待遇(单选)'
? _singleSelect[section] == option
: _multiSelect[section]!.contains(option);
return ChoiceChip(
label: Text(option),
selected: isSelected,
onSelected: (bool selected) {
setState(() {
if (section == '薪资待遇(单选)') {
_singleSelect[section] = selected ? option : null;
} else {
if (selected) {
_multiSelect[section]!.add(option);
} else {
_multiSelect[section]!.remove(option);
}
}
});
},
);
}).toList(),
),
],
),
);
},
),
),
],
),
bottomNavigationBar: Padding(
padding: EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () {
// 清除按钮的处理
setState(() {
_singleSelect.forEach((key, value) {
_singleSelect[key] = null;
});
_multiSelect.forEach((key, value) {
_multiSelect[key] = {};
});
});
},
child: Text('清除'),
style: ElevatedButton.styleFrom(
primary: Colors.grey,
),
),
ElevatedButton(
onPressed: () {
// 确认按钮的处理
},
child: Text('确认'),
),
],
),
),
);
}
}
代码说明¶
左侧导航栏:¶
- 使用
NavigationRail
组件来实现。 onDestinationSelected
方法中调用_scrollToSection
方法,使右侧内容区域自动滚动到选中模块。
右侧内容区域:¶
- 使用
ListView.builder
来动态生成模块。 - 使用
Wrap
组件来布局标签。 - 使用
ChoiceChip
组件实现标签的选择功能。 - 单选和多选的逻辑分别处理,单选使用
_singleSelect
,多选使用_multiSelect
。
联动效果:¶
点击左侧导航栏,右侧内容栏自动滚动到对应位置¶
- 使用
GlobalKey
: - 为每个部分创建一个
GlobalKey
,并将其存储在_sectionKeys
字典中。 - 在
ListView.builder
中,将GlobalKey
分配给每个部分的Padding
组件。 - 计算每个部分的高度:
- 在
didChangeDependencies
方法中,使用WidgetsBinding.instance.addPostFrameCallback
来确保在布局完成后计算每个部分的高度。 - 在
_calculateSectionHeights
方法中,通过GlobalKey
获取每个部分的高度,并存储在_sectionHeights
字典中。 - 滚动到特定部分:
- 在
_scrollToSection
方法中,根据存储的高度计算滚动偏移量,并使用animateTo
方法滚动到目标位置。
滑动右侧内容栏时,左侧导航栏自动选中对应的模块¶
需要监听右侧内容栏的滚动事件,并根据当前滚动位置计算出当前显示的部分,然后更新左侧导航栏的选中状态。
通过以下步骤来实现这个功能:
- 监听滚动事件:在
initState
方法中,为右侧内容栏的_scrollController
添加滚动监听器_onScroll
,并在dispose
方法中移除监听器。 - 计算当前显示的部分:在
_onScroll
方法中,根据当前的滚动偏移量,计算出当前显示的部分,并更新_selectedNavIndex
。 - 更新左侧导航栏的选中状态:根据
_selectedNavIndex
的变化,更新左侧导航栏的选中状态。