Flutter —— 索引条
索引条
这里功能太多所以重新创建一个文件来写索引条。 索引条是叠在Listview上面的,那么朋友圈页面里面的body就应该使用stack。然后添加索引条。
body: Stack(
children: [
Container(
child: ListView.builder(
itemBuilder: _itemForRow,
itemCount: datas.length + _headerData.length,
),
color: weChatThemColor,
), //列表
IndexBar(), // 索引条
],
),
添加索引条数据
const INDEX_WORDS = [ '🔍', '☆', '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' ];
创建一个保存Widget的list
final List<Widget> words = [];
在initState中添加Widget
for (int i = 0; i < INDEX_WORDS.length;i++) {
words.add(
Expanded(child: Text(INDEX_WORDS[i],style: TextStyle(fontSize: 10,color: Colors.grey),),)
);
}
然后在build里面大致写一下索引条的界面,这样索引条就能显示出来了。
Positioned(
right: 0.0,
top: screenHeight(context)/8,
height: screenHeight(context)/2,
width: 30,
child: Column(
children: words,
),
);
索引条还需要捕捉到选中的是哪一个,所以先定义两个属性。
Color _bkColor = Color.fromRGBO(1, 1, 1, 0.0);
Color _textColor = Colors.black;
然后将Column用GestureDetector包住。
将之前在initState中创建的text的文字颜色改为_textColor。将Column用Container包住之后颜色改为_bkColor。
在GestureDetector监听onVerticalDragDown以及onVerticalDragEnd,然后setState改变背景色和文字颜色。
onVerticalDragDown: (DragDownDetails details){
setState(() {
_bkColor = const Color.fromRGBO(1, 1, 1, 0.5);
_textColor = Colors.white;
});
},
onVerticalDragEnd: (DragEndDetails details){
setState(() {
_bkColor = const Color.fromRGBO(1, 1, 1, 0.0);
_textColor = Colors.black;
});
},
这时候发现背景色变化来,但是文字颜色没有改变,这是因为文字颜色在initState里面。所以与界面相关的就不应该放在数据相关的地方,否则就容易出现bug,所以这里将words和循环放到build里面。
然后在GestureDetector监听onVerticalDragUpdate,使用context.findRenderObject找到最近的一个部件,然后用globalToLocal算出当前点击的地方距离部件的原点的y值,然后算出字符高度,算出是第几个item,然后得到点击的字母。
onVerticalDragUpdate: (DragUpdateDetails details){
//拿到当前小部件
RenderBox box = context.findRenderObject() as RenderBox;
//拿到y值,globalToLocal当前位置距离部件的原点的距离
double y = box.globalToLocal(details.globalPosition).dy;
// 算出字符高度
var itemHeight = screenHeight(context) / 2 / INDEX_WORDS.length;
// 算出第几个item
int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
print('${INDEX_WORDS[index]}');
},
将里面的步骤封装成一个方法
String getIndex (BuildContext context,Offset globalPosition) {
//拿到当前小部件
RenderBox box = context.findRenderObject() as RenderBox;
//拿到y值,globalToLocal当前位置距离部件的原点的距离
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];
}
onVerticalDragUpdate里面调用
onVerticalDragUpdate: (DragUpdateDetails details) {
getIndex(context, details.globalPosition);
},
这里需要把数据返回到通讯录页面,那么这里写一个回调。
final void Function(String str)? indexBarCallBack;
IndexBar({this.indexBarCallBack});
在onVerticalDragUpdate里面调用indexBarCallBack。
onVerticalDragUpdate: (DragUpdateDetails details) {
if (widget.indexBarCallBack != null) {
widget.indexBarCallBack!(
getIndex(context, details.globalPosition));
}
},
在外面使用IndexBar就耀传入这个indexBarCallBack
IndexBar(indexBarCallBack: (String str){
}),
接下来要根据点击的字母进行滚动,那么就需要计算每个字母的滚动距离。 这里需要先声明一个ScrollController
ScrollController _scrollController = ScrollController();
将其设为_scrollController的controller。
controller: _scrollController,
之后声明map,_cellHeight以及_groupHeight
final Map _groupOffsetMap = {};
final double _cellHeight = 54.5;
double _groupHeight = 30;
然后在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 ) {
_groupOffset += _cellHeight;
} else {
_groupOffsetMap.addAll({_listDatas[i].indexLetter:_groupOffset});
_groupOffset += _cellHeight + _groupHeight;
}
}
然后就可以在IndexBar回调中滚动了。
IndexBar(indexBarCallBack: (String str){
if (_groupOffsetMap[str] != null) {
_scrollController.animateTo(
_groupOffsetMap[str], duration: Duration(microseconds: 100),
curve: Curves.easeIn);
}
}),
接下来要做左边的气泡,那么这里先将Indexbar的索引条包起来。
然后在Container里面添加图片以及文字。
Container(
alignment: Alignment(0,-1.1),
width: 100,
color: Colors.red,
child: Stack(
alignment: Alignment(-0.2, 0),
children: [
Image(
image: AssetImage('images/气泡.png'),
width: 60,
),
Text(
'A',
style: TextStyle(
fontSize: 35,
color: Colors.white,
),
),
],
),
),
接下来需要根据点击的位置变换气泡文字,以及在释放时隐藏气泡,所以这里声明三个属性。
double _indicatorY = 0.0;
String _indicatorText = "A";
bool _indicatorHidden = true;
修改getIndex成只返回index,然后在onVerticalDragDown和onVerticalDragUpdate里面修改_indicatorY和_indicatorText,并且把_indicatorHidden设为false,然后在onVerticalDragEnd里面把_indicatorHidden设为true。
onVerticalDragDown: (DragDownDetails details) {
int index = getIndex(context, details.globalPosition);
if (widget.indexBarCallBack != null) {
widget.indexBarCallBack!(
INDEX_WORDS[index]);
}
setState(() {
_indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
_bkColor = const Color.fromRGBO(1, 1, 1, 0.5);
_textColor = Colors.white;
});
},
onVerticalDragUpdate: (DragUpdateDetails details) {
int index = getIndex(context, details.globalPosition);
if (widget.indexBarCallBack != null) {
widget.indexBarCallBack!(
INDEX_WORDS[index]);
}
setState(() {
_indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
});
},
onVerticalDragEnd: (DragEndDetails details) {
setState(() {
_bkColor = const Color.fromRGBO(1, 1, 1, 0.0);
_textColor = Colors.black;
_indicatorHidden = true;
});
},
然后修改气泡这边,如果_indicatorHidden为true就返回null,否则就返回气泡,气泡的位置跟着_indicatorY变化而变化。
Container(
alignment: Alignment(0,_indicatorY),
width: 100,
color: Colors.red,
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,
),
),
],
),
),
转载自:https://juejin.cn/post/7030605935991062558