flutter 通讯录
工程仓库
国内的话需要梯子打开
https://www.dropbox.com/sh/u9s38957amql3yc/AABc3ncZd8L2gMtMAy83FKL0a?dl=0
如果不能下载的话请私信我
样式长这样
需求分析:
1、滑动会影响到右边的导航栏。
2、右边的导航栏具有点击和拖动的手势,位置点到的位置会影响到左边的list。
3、当list的导航栏的索引没有对应的list会选择最近的list。
4、当最后的list不占满的话,选倒数第二个list,若倒数第二个也不占满,以此类推上一个list。
需求没有说做停留索引,所以我没做了。
整体做法
页面
页面的话整体是个stack 。
占整个的是一个singleChildScrollview ,分两部分,一部分是头Container ,下面那部分是ListView 嵌套ListView :目的是为了数据好处理。
嵌套一个导航栏:整体是Row 左边是指示器,位置是根据右边的选择来做判定,右边是一个嵌套了GestureDetector 的ListView 。GestureDetector 具有点击、滑动、取消点击的回调。
数据
创建一个长度为27的数组来存储高度(# + A~Z)。数据可能 B没有,那该位置就是上一个A的位置。当滑动的时候,根据offset 来判断当前的位置indexLocation 为右边的导航栏显示更新。
核心代码
整体的scrollview + 模拟数据
// 存储导航列表的高度位置瞄点
List<double> scrollHeightList = [];
// item高度
double itemHeight = 60;
// 导航item高度
double sessionHeight = 38.5;
// 当前选择item的位置
int indexLocation = -1;
// 最后占满屏幕的高度
double lastHeight = 0;
// 最后占满屏幕的位置
int lastIndex = 0;
// 标签页高度
double firstItemHeight = 0;
List<TeacherChooseList> teacherList = [];
// 数据选择的字典
Map chooseIndex = {'first': -1, 'second': -1};
在数据的创建时就要把高度给存在scrollHeightList
void createData() {
List abcList = ['#','A','B','C','D','E','F'];
abcList.forEach((element) {
List<TeacherChooseModel> teacherItem = [];
for(int i = 0; i < 5; i++) {
TeacherChooseModel model = TeacherChooseModel('$element $i 中文 yingwen', false, '');
teacherItem.add(model);
}
final TeacherChooseModel model = TeacherChooseModel('$element ddd', false, '一年级1班101');
teacherItem.add(model);
final TeacherChooseList chooseList = TeacherChooseList(element, teacherItem);
teacherList.add(chooseList);
});
List<TeacherChooseList> dataList = teacherList;
double textWidth = '文本文本文本文本文本文本文本文本文本文本'.paintWidthWithTextStyle(
const TextStyle(
fontSize: 12
));
double topItemHeight = 0;
if(textWidth > (MediaQuery.of(widget.context).size.width - 30)) {
topItemHeight = 33.5;
}else {
topItemHeight = 17;
}
firstItemHeight = topItemHeight + 52.5;
double totalHeight = firstItemHeight;
scrollHeightList.add(totalHeight);
int wordIndex = 0;
lastHeight += sessionHeight +
itemHeight * dataList[dataList.length - 1].teacherItem.length;
for(int i = 0; i < indexWord.length; i++) {
for(int j = 1; j < dataList.length; j++) {
if(dataList[j - 1].index == indexWord[i]) {
if (j + 1 == (dataList.length)) {
int dataIndex = j;
print(MediaQuery.of(widget.context).size.height
- firstItemHeight
- MediaQuery.of(widget.context).padding.top
- 64);
while (lastHeight < (MediaQuery.of(widget.context).size.height
- firstItemHeight
- MediaQuery.of(widget.context).padding.top
- 64)) {
if(dataIndex >= 0) {
lastHeight += sessionHeight +
itemHeight * dataList[dataIndex].teacherItem.length;
}
dataIndex -= 1;
if(lastIndex == 0) {
lastIndex = wordIndex;
}
}
}
totalHeight += sessionHeight + itemHeight*dataList[j - 1].teacherItem.length;
break;
// }
}
}
scrollHeightList.add(totalHeight);
wordIndex += 1;
}
}
scrollview 的滑动监听
void _scrollListener() {
final double offsetY = _scrollController.offset;
setState(() {
for(int i = 0; i < scrollHeightList.length; i ++) {
if(offsetY < scrollHeightList[i]) {
indexLocation = i - 1;
break;
}else {
for (int i = 0; i < indexWord.length; i++) {
if(indexWord[i] == teacherList.last.index) {
indexLocation = i;
break;
}
}
}
}
});
}
导航栏
需要传递的数据
List indexWord = ['#','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
class TeacherChooseIndex extends StatefulWidget {
final void Function(int index) indexBarCallBack;
double totalHeight;
int chooseIndex = -1;
double firstItemHeight;
TeacherChooseIndex({this.indexBarCallBack, this.totalHeight, this.chooseIndex, this.firstItemHeight});
@override
TeacherChooseIndexState createState() => TeacherChooseIndexState();
}
内部的数据
class TeacherChooseIndexState extends State<TeacherChooseIndex> {
double _totalHeight = 0;
double _indicatorY = 0.0;
bool _indicatorHidden = true;
String _indicatorText = 'A';
double dragHeight = 0;
double _firstItemHeight = 0;
@override
void initState() {
super.initState();
_totalHeight = widget.totalHeight;
dragHeight = 25*_totalHeight/27;
_firstItemHeight = widget.firstItemHeight;
}
滑动手势获取当前的index位置
int getIndexItem(BuildContext context,Offset globalPosition){
//拿到当前盒子
final RenderBox box = context.findRenderObject() as RenderBox;
//拿到y值,当前位置到部件原点(部件左上角)的距离(x,y)
var y = box.globalToLocal(globalPosition).dy;
//算出字符高度
final itemHeight = dragHeight/27;
int index = y ~/itemHeight.clamp(0, indexWord.length-1) - 1;//~取整,设置取整范围clamp
if(index > 26) {
index = 26;
}
if (index < 0) {
index = 0;
}
print('${indexWord[index]}');
return index;
}
手势的操作
GestureDetector(
onVerticalDragDown: (DragDownDetails details){
final int index = getIndexItem(context, details.globalPosition);
widget.indexBarCallBack(index);
setState(() {
widget.chooseIndex = index;
_indicatorY = index * dragHeight/27;
_indicatorText = indexWord[index];
_indicatorHidden = false;//是否隐藏指示器
});
},
onVerticalDragCancel: () {
setState(() {
_indicatorHidden = true; //是否隐藏指示器
});
},
onVerticalDragEnd:(DragEndDetails details){
setState(() {
_indicatorHidden = true;//是否隐藏指示器
});
},
onVerticalDragUpdate: (DragUpdateDetails details){
final int index = getIndexItem(context, details.globalPosition);
widget.indexBarCallBack(index);
setState(() {
widget.chooseIndex = index;
_indicatorY = index * dragHeight/27;
_indicatorText = indexWord[index];
_indicatorHidden = false;//是否隐藏指示器
});
},
child: Container(
width:dragHeight/27,
// color: AppColor.black,
child: _getListView(context)
),
),
|