Flutter学习-17- 微信项目学习-索引条联动
- 我们上一篇实现了索引条的显示和点击状态变化,接下来我们要实现索引条的气泡展示和通讯录的联动。
1. 计算出点击或拖拽的文字
我们思路是通过计算出索引条的每一个字母所在的位置返回对应的字母展示出来。
我们通过上下文拿到当前小部件的盒子,RenderBox
相当于索引条的大小,我们通过globalToLocal
把外部的坐标转换当前控件的坐标系统。类似我们把子视图的点由在全局的坐标点转换为父视图的坐标
,我们就不用转换了。
onVerticalDragUpdate: (DragUpdateDetails details){
setState(() {
RenderBox box = context.findRenderObject() as RenderBox ;
print('$box.globalToLocal(details.localPosition).dy');
print('手势移动的位置:$details');
});
},
拿到y值,globalToLocal
当前位置我部件的原点(小部件左上角)的距离(x,y)。通过每个字符的高度,根据偏移
//拖拽
onVerticalDragUpdate: (DragUpdateDetails details){
setState(() {
RenderBox box = context.findRenderObject() as RenderBox ;
double y = box.globalToLocal(details.localPosition).dy;
//item高度
var itemHeight = screenHeight(context)/2 / INDEX_WORDS.length;
int index = y ~/ itemHeight;
print('选中了第$index个');
});
},
这里如果超出了屏幕
数组也会报错
我们也要设定下index
的取值范围
我们使用clamp
设定范围为0到INDEX_WORDS.length - 1
得到对应的下标,最后在取出数组中的str
。我们把方法抽出来
//获取选中的Item的字符!!
String getIndex(BuildContext context, Offset globalPosition) {
//拿到点前小部件的盒子
RenderBox box = context.findRenderObject() as RenderBox ;
//拿到y值,globalToLocal当前位置我部件的原点(小部件左上角)的距离(x,y)
double y = box.globalToLocal(globalPosition).dy;
//算出字符高度
var itemHeight = screenHeight(context) / 2 / INDEX_WORDS.length;
//算出第几个item
int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
return INDEX_WORDS[index];
}
2. 索引条的回调
我们在索引条中回调点击的返回的字符,我们需要高速外界的ListView
,所以我们要一个回调
,我们定义一个回调方法。
class CloumnIndexBar extends StatefulWidget {
final void Function(String str) indexBarCallBack;
CloumnIndexBar({required this.indexBarCallBack});
@override
_CloumnIndexBarState createState() => _CloumnIndexBarState();
}
我们在点击和移动的时候把这个字符传递出去
//拖拽
onVerticalDragUpdate: (DragUpdateDetails details){
setState(() {
var str = getIndex(context, details.globalPosition);
widget.indexBarCallBack(str);
print(str);
});
},
//点击
onVerticalDragDown: (DragDownDetails details){
setState(() {
var str = getIndex(context, details.globalPosition);
widget.indexBarCallBack(str);
print('手势进入的位置:$details');
_backgroundColor = Color.fromRGBO(1, 1, 1, 0.5);
_textColor = Colors.white;
});
},
我们在创建的时候得到回调
listView
滚动需要控制器,所以我们需要开始的时候进行创建,这里直接定义
final ScrollController _scrollController = ScrollController();
我们使用_scrollController
的animateTo
进行滚动,这里先写个300测试下
child:Stack(
children: [
ListView.builder(itemBuilder: _itemBuilder,
itemCount: _headerData.length+_listDatas.length,
controller: _scrollController,
),
CloumnIndexBar(indexBarCallBack: (String str){
_scrollController.animateTo(300, duration: Duration(microseconds: 100), curve: Curves.easeIn);
print('选中了$str');
},)
],
)
因此我们可以算出来我们字母对应的位置。
3. 计算对应的偏移量
我们之前定义的cell的高度和头部的高度
double _cellHeight = 54.5;
double _groupHeight = 30.0;
之后我们定义一个字典
,字典中每个字母存放对应的偏移量
final Map _groupOffsetMap = {
INDEX_WORDS[0]: 0.0,
INDEX_WORDS[1]: 0.0,
};
这里前2个
是固定的,所以不用偏移。我们在计算的时候不能放到itembulid
里面,滑动的时候才会加载,所以我们在initState
中数据处理完成后进行计算。
var _groupOffset = _cellHeight * _headerData.length;//默认位置
//进过循环计算,将每一个头的位置算出来。放入字典
for (int i = 0; i < _listDatas.length; i++) {
if (i < 1) {
//第一个cell一定有头!
_groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
//保存完了再加_groupOffset
_groupOffset += _cellHeight + _groupHeight;
} else if (_listDatas[i].indexLetter == _listDatas[i - 1].indexLetter) {
//不同存,只需要加Cell的高度
_groupOffset += _cellHeight;
} else {
_groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
//保存完了再加_groupOffset
_groupOffset += _cellHeight + _groupHeight;
}
}
我们在之前的回调中显示,为了防止不存在的我们加个判断
if (_groupOffsetMap[str] != null) {
_scrollController.animateTo(_groupOffsetMap[str],
duration: Duration(microseconds: 100),
curve: Curves.easeIn);
}
print('选中了$str');
4. 指示器显示
指示器左边的气泡我们可以把它和指示器当成一个整体
,那么我们可以使用row
进行布局。
我们调整下索引条的宽度和整体的宽度,这样就
居中
了
我们继续设置下左边的
指示器
,主要是气泡和文字那么我们可以使用stack
进行包裹
我们调整下位置,居中
我们知道flutter中的坐标是
1到-1
,中间是0,那么我们可以使用alignment
来表示范围
这里还要往下一点应该是y的范围
(-1.1,1.1)
我们定义3个变量,
根据index算出-1.1在1.1的位置
double _indexBarOffsetY = 0.0; // -1.1到1.1之间的偏移量 中心点的是0
bool _indexBarHidden = true; // 是否隐藏
String _indexBarText = 'A'; // 当前正在显示的字母
我们点击或者拖拽的时候显示的字母判断是否隐藏
我们把这个之前返回的字符串改为index
Widget build(BuildContext context) {
final List<Widget> _words = [];
for(int i = 0 ;i<INDEX_WORDS.length;i++){
_words.add(Expanded(child: Text(INDEX_WORDS[i],style: TextStyle(fontSize: 10,color: _textColor),)));
}
return Positioned(
top: screenHeight(context)/8,
width: 120,
right: 0,
height: screenHeight(context)/2,
child:
Container(
child:
Row(
children: [
Container(
alignment: Alignment(0,_indicatorOffsetY),
width: 100,
child: _indicatorHidden ? null : Stack(
alignment: Alignment(-0.2,0),
children: [
Image(
image: AssetImage('images/气泡.png'),
width: 60,
),
Text(_indicatorText,
style: TextStyle(fontSize: 35, color: Colors.white),
)
],
),
),
GestureDetector(
//拖拽
onVerticalDragUpdate: (DragUpdateDetails details){
setState(() {
int index = getIndex(context, details.globalPosition);
widget.indexBarCallBack(INDEX_WORDS[index]);
setState(() {
_indicatorOffsetY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
});
});
},
//点击
onVerticalDragDown: (DragDownDetails details){
setState(() {
int index = getIndex(context, details.globalPosition);
widget.indexBarCallBack(INDEX_WORDS[index]);
print('手势进入的位置:$details');
_indicatorOffsetY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
_backgroundColor = Color.fromRGBO(1, 1, 1, 0.5);
_textColor = Colors.white;
});
},
//离开
onVerticalDragEnd:(DragEndDetails details){
setState(() {
_backgroundColor = Color.fromRGBO(1, 1, 1, 0);
_textColor = Colors.black;
_indicatorHidden = true;
});
},
child:
Container(
width: 20,
color:_backgroundColor,
child:
Column(
children: _words,
),
),
)
],
),
)
);
}
最终的效果:
转载自:https://juejin.cn/post/7030666250095165470