开启掘金成长之旅!这是我参与「掘金日新计划 · 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
显示单个城市信息,注意设置shrinkWrap
和physics
属性,否则会抛出异常
// 每个字母和对应的城市
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),
),
),
);
}