Links

12.索引条:手势及clamp函数

前言

在进行类似联系人页面的开发过程中,我们经常会遇到侧边栏索引条。在iOS中我们只需要简单设置即可使用系统提供的控件。
这里我们使用 Flutter 自定义一个索引条,来熟悉一些常用的知识点。

一、 一个索引条包含什么?

  • 数据源
    • 索引数组
  • 点击手势
  • 滑动手势
  • 事件回调

二、 开始布局

索引条展示的数据是可变的,所以这里我们使用有状态的Widget进行布局。这里以 Container + Column 进行基础封装,看看效果。
import 'package:flutter/material.dart';
class IndexBar extends StatefulWidget {
IndexBar({
required this.dataSource,
});
final List<String> dataSource;
final double _indexItemHeight = 22;
@override
_IndexBarState createState() => _IndexBarState();
}
class _IndexBarState extends State<IndexBar> {
List<Widget> _indexsWidgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < widget.dataSource.length; i++) {
_indexsWidgets.add(Container(
height: widget._indexItemHeight,
child: Text(
widget.dataSource[i],
style: const TextStyle(color: Colors.grey),
),
));
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: (details) {
RenderBox box = context.findRenderObject() as RenderBox;
Offset point = box.globalToLocal(details.globalPosition);
double y = point.dy;
// 在当前 Widget 内的 Offset
print(y);
},
child: Container(
width: 22,
color: const Color.fromRGBO(1, 1, 1, 0.2),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: _indexsWidgets,
),
),
);
}
}
基础使用,传入索引数组:
@override
Widget build(BuildContext context) {
return Scaffold(
...
body: Stack(
children: [
Container(...),
Align(
alignment: Alignment.centerRight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IndexBar(dataSource: _indexs)
],
),
),
],
)
);
}
1
这里我们完成了基础布局,和数据源的传入,下面我们继续完善。

三、 点击事件

2
这里我们需要定位到具体点击了哪个 index 所以 onTap 不能够满足,这里通过注释我们可以发现 onTapUp 回调带有更多信息。我们使用这个进行点击事件的处理
typedef GestureTapUpCallback = void Function(TapUpDetails details);
3
现在我们能够获取点击坐标了,接下来进行计算获得具体 index。
4
flutter: L
flutter: N
flutter: E
flutter: B
flutter: J
flutter: L
flutter: J
flutter: K
flutter: N
flutter: J
flutter: F

四、 滑动事件

和点击事件类似,这里我们添加一下滑动选择事件:
onVerticalDragUpdate: (details) {
RenderBox box = context.findRenderObject() as RenderBox;
Offset point = box.globalToLocal(details.globalPosition);
print('onVerticalDragUpdate${widget.dataSource[_currentIndex(point)]}');
},
onTapUp: (details) {
RenderBox box = context.findRenderObject() as RenderBox;
Offset point = box.globalToLocal(details.globalPosition);
print('onTapUp${widget.dataSource[_currentIndex(point)]}');
},
测试有效:
flutter: onVerticalDragUpdateF
flutter: onVerticalDragUpdateF
flutter: onVerticalDragUpdateH
flutter: onVerticalDragUpdateH
flutter: onVerticalDragUpdateH

五、 事件回调

内部的事件处理完成了,我们来来添加一下外部的回调:

定义回调类型

typedef IndexBarSelectCallBack = void Function(int index, String title);
class IndexBar extends StatefulWidget {
IndexBar({
required this.dataSource,
required this.callBack
});
final List<String> dataSource;
final double _indexItemHeight = 22;
final IndexBarSelectCallBack callBack;
@override
_IndexBarState createState() => _IndexBarState();
}

调整事件处理及回调

onVerticalDragUpdate: (details) {
RenderBox box = context.findRenderObject() as RenderBox;
Offset point = box.globalToLocal(details.globalPosition);
int index = _currentIndex(point);
String title = widget.dataSource[index];
widget.callBack(index, title);
},
onTapUp: (details) {
RenderBox box = context.findRenderObject() as RenderBox;
Offset point = box.globalToLocal(details.globalPosition);
int index = _currentIndex(point);
String title = widget.dataSource[index];
widget.callBack(index, title);
},

外部传入回调

5

六、 Clamp 函数

在滑动偏移过大超出范围的时候会发生越界,出现下面的警告
RangeError (index): Invalid value: Not in inclusive range 0..10: 14
这里安利一个很好用的函数 clamp 来对方法 _currentIndex(Offset point) 进行一下优化:
int _currentIndex(Offset point) {
return (point.dy ~/ widget._indexItemHeight).clamp(0, widget.dataSource.length - 1);
}
这样就对返回的值的范围作出了限制。

七、 最终代码

https://github.com/RyukieSama/FlutterStudy/blob/master/fake_wechat/lib/widgets/contacts/index_bar.dart