先看效果

Tabbar_01.mp4 (178.12KB)

实现代码

  1. class OptionsTab extends StatefulWidget {
  2. const OptionsTab({
  3. Key key,
  4. }) : super(key: key);
  5. @override
  6. _OptionsTabState createState() => _OptionsTabState();
  7. }
  8. class _OptionsTabState extends State<OptionsTab> {
  9. ScrollController scrollController;
  10. List optionList;
  11. int _currentSelectedIndex = 0;
  12. @override
  13. void initState() {
  14. optionList = [
  15. '全部',
  16. '通知',
  17. '作业',
  18. '打卡',
  19. '通知',
  20. '作业',
  21. '打卡',
  22. '通知',
  23. '作业',
  24. '打卡',
  25. '通知',
  26. '作业',
  27. '打卡',
  28. ];
  29. scrollController = ScrollController();
  30. super.initState();
  31. }
  32. @override
  33. Widget build(BuildContext context) {
  34. return Container(
  35. padding: EdgeInsets.symmetric(vertical: 5),
  36. height: 30,
  37. color: Colors.white,
  38. child: ListView.builder(
  39. scrollDirection: Axis.horizontal,
  40. controller: scrollController,
  41. itemBuilder: (BuildContext context, int index) {
  42. bool selected = _currentSelectedIndex == index;
  43. return GestureDetector(
  44. behavior: HitTestBehavior.translucent,
  45. onTap: () {
  46. if (_currentSelectedIndex == index) {
  47. return;
  48. }
  49. ScrollPosition position = scrollController.position;
  50. print(
  51. 'position index:$index viewportDimension:${position.viewportDimension} minScrollExtent:${position.minScrollExtent} maxScrollExtent${position.maxScrollExtent}');
  52. /// 计算当前选中的 Tab 的 offset
  53. double currentOffset = 50 * index.toDouble();
  54. /// 计算居中的 offset
  55. double centerOffset = currentOffset + 25;
  56. /// 计算最终滚动的 offset 距离
  57. double absOffset = (centerOffset - position.viewportDimension / 2)
  58. .clamp(position.minScrollExtent, position.maxScrollExtent)
  59. .toDouble();
  60. print(
  61. 'currentOffset:$currentOffset centerOffset:$centerOffset absOffset:$absOffset');
  62. /// 通过 controller 滚动到对于的距离
  63. scrollController.animateTo(absOffset,
  64. duration: kTabScrollDuration, curve: Curves.ease);
  65. _currentSelectedIndex = index;
  66. setState(() {});
  67. },
  68. child: Container(
  69. margin: EdgeInsets.symmetric(horizontal: 5),
  70. decoration: BoxDecoration(
  71. color: selected ? Colors.white : Colors.grey[200],
  72. border: Border.all(
  73. color: selected ? Colors.blue : Colors.grey[300],
  74. ),
  75. borderRadius: BorderRadius.circular(4)),
  76. child: Text(
  77. '${optionList[index]}',
  78. style: TextStyle(
  79. color: selected ? Colors.blue : Colors.grey[500],
  80. fontSize: 10,
  81. ),
  82. ),
  83. alignment: Alignment.center,
  84. height: 30,
  85. width: 50,
  86. ),
  87. );
  88. },
  89. itemCount: optionList?.length ?? 0,
  90. ),
  91. );
  92. }
  93. }

使用上面的小算法就可以实现 listview 滚动到对应的 item

  1. ScrollPosition position = scrollController.position;
  2. print(
  3. 'position index:$index viewportDimension:${position.viewportDimension} minScrollExtent:${position.minScrollExtent} maxScrollExtent${position.maxScrollExtent}');
  4. /// 计算当前选中的 Tab 的 offset
  5. double currentOffset = 50 * index.toDouble();
  6. /// 计算居中的 offset
  7. double centerOffset = currentOffset + 25;
  8. /// 计算最终滚动的 offset 距离
  9. double absOffset = (centerOffset - position.viewportDimension / 2)
  10. .clamp(position.minScrollExtent, position.maxScrollExtent)
  11. .toDouble();
  12. print(
  13. 'currentOffset:$currentOffset centerOffset:$centerOffset absOffset:$absOffset');
  14. /// 通过 controller 滚动到对于的距离
  15. scrollController.animateTo(absOffset,
  16. duration: kTabScrollDuration, curve: Curves.ease);

参考链接