跳转至

筛选页面

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

联动效果:

点击左侧导航栏,右侧内容栏自动滚动到对应位置

  1. 使用GlobalKey
  2. 为每个部分创建一个GlobalKey,并将其存储在_sectionKeys字典中。
  3. ListView.builder中,将GlobalKey分配给每个部分的Padding组件。
  4. 计算每个部分的高度
  5. didChangeDependencies 方法中,使用 WidgetsBinding.instance.addPostFrameCallback 来确保在布局完成后计算每个部分的高度。
  6. _calculateSectionHeights 方法中,通过 GlobalKey 获取每个部分的高度,并存储在 _sectionHeights 字典中。
  7. 滚动到特定部分
  8. _scrollToSection 方法中,根据存储的高度计算滚动偏移量,并使用 animateTo 方法滚动到目标位置。

滑动右侧内容栏时,左侧导航栏自动选中对应的模块

需要监听右侧内容栏的滚动事件,并根据当前滚动位置计算出当前显示的部分,然后更新左侧导航栏的选中状态。

通过以下步骤来实现这个功能:

  1. 监听滚动事件:在 initState 方法中,为右侧内容栏的 _scrollController 添加滚动监听器 _onScroll,并在 dispose 方法中移除监听器。
  2. 计算当前显示的部分:在 _onScroll 方法中,根据当前的滚动偏移量,计算出当前显示的部分,并更新 _selectedNavIndex
  3. 更新左侧导航栏的选中状态:根据 _selectedNavIndex 的变化,更新左侧导航栏的选中状态。