前言
说实话,由于项目时间紧迫,秉承着C+V大法的理念,就想着在网上找个能用的简单点的索引列表代码改改直接来用,谁知道没一个靠谱的,靠人不如靠自己-.-
注意
这套代码开发方式采用的是Getx的Bloc设计方式开发的,如果您使用的不是Getx,末尾也提供了常规代码方式。
实现步骤
城市/通讯录/列表
”数据一、创建Model:pinyin.dart
// 管理拼音
class CountryModel {
// 详细数据
var info;
// 拼音首字母
late var pin;
CountryModel({
this.info,
this.pin,
});
}
// 管理列表
class SortList {
var label;
var children;
SortList({
this.label,
this.children,
});
}
二、安装需要的插件
lpinyin
帮助我们将后端数据转为拼音
scrollable_positioned_list
帮助我们定位具体的item位置
lpinyin: ^2.0.3
scrollable_positioned_list: ^0.3.8
三、代码层面
state.dart
class IndexNavigationListState {
late int currentIndex;
IndexNavigationListState() {
currentIndex = 0;
}
}
logic.dart
数据采用的是本地的一个Map数据,具体大家换成自己后台数据就好了
import 'package:get/get.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'state.dart';
import 'dart:convert';
import '../../config/pinyin.dart';
import '../../model/model.dart';
import 'package:lpinyin/lpinyin.dart';
class IndexNavigationListLogic extends GetxController {
final IndexNavigationListState state = IndexNavigationListState();
final ItemScrollController scrollController = ItemScrollController();
List<CountryModel> CountryList = [];
List<SortList> dataList = [
SortList(label: '*', children: ['热门城市', '收藏城市'])
];
List indexList = [];
// 右侧的索引列表如果您想循环全部的单词,就使用这个值代替UI层中的logic.dataList,目前是根据列表有什么数据,生成什么样的右侧索引
final List<String> az = [
"*",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"G",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
];
void onInit() {
// TODO: implement onInit
super.onInit();
getData();
}
getData() async {
var _resultList = json.decode(schoolData);
_resultList['schools'].forEach((element) {
String now = '#';
String nowPin = PinyinHelper.getFirstWordPinyin(element['province_name']);
if (nowPin.length > 0) {
now = indexList.contains(nowPin.substring(0, 1).toUpperCase())
? '#'
: nowPin.substring(0, 1);
}
CountryModel vo =
CountryModel(info: element['province_name'], pin: now.toUpperCase());
CountryList.add(vo);
});
// 排序
CountryList.sort((CountryModel a, CountryModel b) {
if (a.pin == '#') {
return 1;
}
if (b.pin == '#') {
return 0;
}
return a.pin.compareTo(b.pin);
});
// 去重排序
List _arr = [];
var s = Set();
for (var i = 0; i < CountryList.length; i++) {
_arr.add(CountryList[i].pin);
}
s.addAll(_arr);
// 转为Model类型列表
s.toList().forEach((element) {
element = SortList(label: element, children: []);
dataList.add(element);
});
// 生成UI列表显示数据
for (var element in dataList) {
for (var item in CountryList) {
if (element.label == item.pin) {
element.children.add(item.info);
}
}
}
}
// 改变索引下标
void changeIndex(index) {
state.currentIndex = index;
update();
}
}
view.dart
代码中的颜色:AppColor.themeColor是我的全局的颜色,大家自己替换掉就好了
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'logic.dart';
class IndexNavigationListPage extends StatelessWidget {
IndexNavigationListPage({Key? key}) : super(key: key);
final logic = Get.put(IndexNavigationListLogic());
final state = Get.find<IndexNavigationListLogic>().state;
Widget build(BuildContext context) {
return GetBuilder<IndexNavigationListLogic>(
builder: (logic) {
return Expanded(
child: Stack(
children: [
Listener(
onPointerUp: (e) {
// 拉动列表,取消右侧索引
logic.changeIndex(-1);
},
child: ScrollablePositionedList.builder(
physics: const BouncingScrollPhysics(),
itemScrollController: logic.scrollController,
padding: const EdgeInsets.all(0),
itemBuilder: (context, index) {
return ListItem(logic: logic, index: index);
},
itemCount: logic.dataList.length,
),
),
Positioned(
width: 40.w,
height: 540.h,
right: 0,
child: Listener(
// 手指按下触发
onPointerDown: (PointerDownEvent e) {
//打印手指按下的位置(相对于屏幕)
int i = e.localPosition.dy ~/ 21;
logic.changeIndex(i);
logic.scrollController.jumpTo(index: i);
},
// 手指滑动时触发
onPointerMove: (PointerMoveEvent e) {
//用户手指滑动时,更新偏移
int i = e.localPosition.dy ~/ 21;
if (i >= 0 && i <= logic.az.length - 1) {
if (i != logic.state.currentIndex) {
logic.changeIndex(i);
}
}
},
child: ListView.builder(
padding: const EdgeInsets.all(0),
itemCount: logic.dataList.length,
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: 20.h,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(20),
color: logic.state.currentIndex == index
? const Color(0xffF3F4F7)
: null,
shape: BoxShape.circle),
child: logic.dataList[index].label == '*'
? Container(
width: 8.w,
height: 8.h,
decoration: BoxDecoration(
color: logic.state.currentIndex == index
? const Color(0xffFFCF57)
: const Color(0xff9299A0),
borderRadius: BorderRadius.circular(20)),
)
: Text(
logic.dataList[index].label,
style: TextStyle(
fontFamily: 'sansBold',
color: logic.state.currentIndex == index
? AppColor.themeColor
: const Color(0xff9299A0),
),
),
),
);
},
),
),
)
],
));
},
);
}
}
class ListItem extends StatelessWidget {
var logic;
var index;
ListItem({super.key, required this.logic, this.index});
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsetsDirectional.only(start: 10),
color: Colors.grey.shade300,
height: 30,
width: double.infinity,
alignment: Alignment.centerLeft,
child: Text(logic.dataList[index].label),
),
Container(
padding: const EdgeInsetsDirectional.only(start: 15),
child: ListView.builder(
// 禁用滑动事件
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: const EdgeInsets.all(0),
itemBuilder: (context, cityIndex) {
return InkWell(
child: Container(
height: 40,
child: Column(
children: [
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child:
Text(logic.dataList[index].children[cityIndex]),
),
),
const Divider(
height: 0.5,
)
],
),
),
onTap: () {},
);
},
itemCount: logic.dataList[index].children.length),
)
],
);
}
}
OK,大功告成,完美实现索引列表功能!有问题,给我留言~
常规代码写法
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:lpinyin/lpinyin.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../config/const.dart';
import '../config/pinyin.dart';
import '../model/model.dart';
class IndexNavigationList extends StatefulWidget {
final bool isShowRegional;
const IndexNavigationList({super.key, this.isShowRegional = true});
State<IndexNavigationList> createState() => _IndexNavigationListState();
}
class _IndexNavigationListState extends State<IndexNavigationList> {
int currentIndex = 0;
final ItemScrollController scrollController = ItemScrollController();
List<CountryModel> CountryList = [];
late List<SortList> dataList = [];
List indexList = [];
final List<String> az = [
"*",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"G",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
];
void initState() {
// TODO: implement initState
super.initState();
if (widget.isShowRegional) {
dataList.add(SortList(label: '*', children: [
'Europe (35)',
'APAC (15)',
'Global (115)',
'Nordic (4)',
'SEA-Oceania(8)'
]));
}
getData();
}
getData() async {
var _resultList = json.decode(schoolData);
_resultList['schools'].forEach((element) {
String now = '#';
String nowPin = PinyinHelper.getFirstWordPinyin(element['province_name']);
if (nowPin.length > 0) {
now = indexList.contains(nowPin.substring(0, 1).toUpperCase())
? '#'
: nowPin.substring(0, 1);
}
CountryModel vo =
CountryModel(info: element['province_name'], pin: now.toUpperCase());
CountryList.add(vo);
});
// 排序
CountryList.sort((CountryModel a, CountryModel b) {
if (a.pin == '#') {
return 1;
}
if (b.pin == '#') {
return 0;
}
return a.pin.compareTo(b.pin);
});
// 去重排序
List _arr = [];
var s = Set();
for (var i = 0; i < CountryList.length; i++) {
_arr.add(CountryList[i].pin);
}
s.addAll(_arr);
// 转为Model类型列表
s.toList().forEach((element) {
element = SortList(label: element, children: []);
dataList.add(element);
});
// 生成UI列表显示数据
for (var element in dataList) {
for (var item in CountryList) {
if (element.label == item.pin) {
element.children.add(item.info);
}
}
}
}
// 改变索引下标
void changeIndex(index) {
currentIndex = index;
setState(() {});
}
Widget build(BuildContext context) {
return Expanded(
child: Stack(
children: [
GestureDetector(
onVerticalDragCancel: () => changeIndex(-1),
child: ScrollablePositionedList.builder(
physics: const BouncingScrollPhysics(),
itemScrollController: scrollController,
padding:
EdgeInsets.only(top: 0, bottom: 30.h, left: 30.w, right: 30.w),
itemBuilder: (context, index) {
return ListItem(logic: {
'dataList': dataList,
}, index: index, isShowRegional: widget.isShowRegional);
},
itemCount: dataList.length,
),
),
Positioned(
width: 40.w,
height: 540.h,
right: 0,
child: Listener(
// 手指按下触发
onPointerDown: (PointerDownEvent e) {
//打印手指按下的位置(相对于屏幕)
int i = e.localPosition.dy ~/ 21;
changeIndex(i);
scrollController.jumpTo(index: i);
},
// 手指滑动时触发
onPointerMove: (PointerMoveEvent e) {
//用户手指滑动时,更新偏移
int i = e.localPosition.dy ~/ 21;
if (i >= 0 && i <= dataList.length - 1) {
if (i != currentIndex) {
changeIndex(i);
}
}
},
child: ListView.builder(
padding: const EdgeInsets.all(0),
itemCount: dataList.length,
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: 20.h,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(20),
color: currentIndex == index
? const Color(0xffF3F4F7)
: null,
shape: BoxShape.circle),
child: dataList[index].label == '*'
? Container(
width: 8.w,
height: 8.h,
decoration: BoxDecoration(
color: currentIndex == index
? const Color(0xffFFCF57)
: const Color(0xff9299A0),
borderRadius: BorderRadius.circular(20)),
)
: Text(
dataList[index].label,
style: TextStyle(
fontFamily: 'sansBold',
color: currentIndex == index
? AppColor.themeColor
: const Color(0xff9299A0),
),
),
),
);
},
),
),
)
],
));
}
}
class ListItem extends StatelessWidget {
var logic;
var index;
bool isShowRegional;
ListItem(
{super.key, required this.logic, this.index, this.isShowRegional = true});
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.only(
top: index != 0 ? 38.h : 0, left: 12.w, bottom: 10.h),
width: double.infinity,
alignment: Alignment.centerLeft,
child: Text(
logic['dataList'][index].label == '*'
? 'Regional Plans'
: logic['dataList'][index].label,
style: TextStyle(
fontFamily: 'sansBold',
color: const Color(0xff768089),
fontSize: 14.sp),
)),
Container(
child: ListView.builder(
// 禁用滑动事件
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: const EdgeInsets.all(0),
itemBuilder: (context, cityIndex) {
return InkWell(
child: Container(
height: 40.h,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 33.w,
height: 33.h,
margin: EdgeInsets.only(right: 20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: const Color(0xffE9EBF0),
),
),
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child: Text(
logic['dataList'][index].children[cityIndex],
style: TextStyle(
fontSize: 15.sp,
color: const Color(0xff15141F),
fontFamily: 'sansBold'),
),
),
),
],
),
),
onTap: () {},
);
},
itemCount: logic['dataList'][index].children.length),
)
],
);
}
}