flutter 仿 12306 城市选择

205 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

功能点

  • 顶部筛选功能,支持名称和拼音筛选
  • 城市分类,首字母分组
  • 右侧显示快捷滚动方式,常用、热门、字母排序等
  • 中间显示当前切换的字母,几秒后消失

功能点实现分析

数据源格式:

  • 数据需要处理成按26个字母进行分组的数据。
[{"code":"110100","areaType":"1","area":"北京市","parentCode":"110000","firstLetter":"B","pinyin":"beijing","hotFlag":"1"},{"code":"310100","areaType":"1","area":"上海市","parentCode":"310000","firstLetter":"S","pinyin":"shanghai","hotFlag":"1"},{"code":"320100","areaType":"1","area":"南京市","parentCode":"320000","firstLetter":"N","pinyin":"nanjing","hotFlag":"1"},{"code":"320500","areaType":"1","area":"苏州市","parentCode":"320000","firstLetter":"S","pinyin":"suzhou","hotFlag":"1"}
...
]

页面整体布局

  • 顶部一个搜索框,下面是列表,使用SingleChildScrollView,进行滚动,不使用ListView的原因是:ListView 有懒加载机制,超出屏幕外的会回收掉,点击右侧快速入口的时候,不能计算出字母的位置,页面滚动出错。
  • 使用stack 选中的时候,最顶层,显示快速入口的字母。

页面城市列表显示

使用ListView,将每个字母分成一个整块Column,内嵌ListView显示单个城市信息,注意设置shrinkWrapphysics 属性,否则会抛出异常

  // 每个字母和对应的城市
  Widget letterWithCity(Map item, int index) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 首字母 a,b,c
        Text('A'),
        // 每个字母下的城市
        ListView.builder(
          padding: EdgeInsets.only(bottom: 20.w, top: 10.w),
          shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          itemBuilder: (BuildContext context, int index) {
            return Text('城市名字');
          },
          itemCount: item['cityCodeList'].length,
        )
      ],
    );
  }

右侧快速入口列表

页面整体布局Row,设定右侧快速入口的宽度rWidth(个人根据自己设计定义宽度),左侧城市占用余下空间

  // 底层展示页面
  Row(
    children: [
      Expanded(
        child: SingleChildScrollView(
          controller: _scrollController,
          child: Column(
            children: [
              // 当前位置
              currentArea(),
              // 定位/最近访问
              if (visitedCityList.length > 0) visitedhis(),
              // 热门城市
              hotCiteWidget(),
              // 城市按字母展示
              ...letterList()
            ],
          ),
        ),
      ),
      Container(
        width: rWidth,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: quickLetter(),
        ),
      )
    ],
  ),

计算位置并滚动

原理:根据快速入口的个数,使用GlobalKey()生成一个letterKeyList,使用索引做关联,对每一块(热门,常用、26个字母)进行唯一标记,并于右侧快速入口建立关联关系,当点击字母K时,找到对应的GolbalKey,计算当前位置,页面快速滚动到该位置。

  // 右侧快速入口文字
  Widget rightTextItem(String str, int index) {
    return InkWell(
      onTap: () {
        double height;
        RenderBox box = letterKeyList[index]
            .currentContext
            ?.findRenderObject() as RenderBox;
        Offset offset = box.localToGlobal(Offset.zero);
        height = offset.dy;
        _scrollController.animateTo(
            _scrollController.offset +
                height -
                (appBarHeight + statusBarHeight),
            duration: Duration(milliseconds: 300),
            curve: Curves.ease);
       // 最上层字母显示
       ......
      },
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 3),
        child: Text(
          str,
          style: TextStyle(fontSize: 22.w, fontWeight: FontWeight.w500),
        ),
      ),
    );
  }