• flutter 手写 TabBar


    前言:

    这几天在使用 flutter TabBar 的时候 我们的设计给我提了一个需求:

    如下 Tabbar  第一个元素 左对齐试了下TabBar 的配置,无法实现这个需求,他的 配置是针对所有元素的。而且 这个 TabBar 下面的 滑块在移动的时候 上面的文字会相应的抖动。

    看了下 TabBar 的源代码 他的实现是相对复杂的 下面的 滑块是 canvas 实现的。 有可能他要实现的功能比较丰富。

    自定义Tabbar 的基本布局

    下面是我页面的布局:这样实现起来 里面元素的 样式可以完全自己定义单个配置,想怎么显示都可以。这样就可以不用局限于 自带Tabbar的配置

    SingleChildScrollView 解析

    完成页面布局相对简单,主要实现底部 滑块的移动,以及 整 SingleChildScrollView 的居中移动是一个关键点

    ScrollController 中的几个关键概念:
    • controller.position.viewportDimension:  SingleChildScrollView 视口 的大小
    • position.maxScrollExtent :                     SingleChildScrollView 可以移动的最大范围
    • position.minScrollExtent  :                     SingleChildScrollView 可以移动最小范围 一般是0
    • Row 的长度就是所有元素的长度之和:也就是 position.maxScrollExtent + position.viewportDimension 

    Row 的长度之和 为什么是 SingleChildScrollView 最大可移动范围加 position.viewportDimension 的和

    SingleChildScrollView 可见区域始终是他的视口大小,不可见的也就是 Row的长度减去视口大小 也就是 maxScrollExtent 可拖动的最大区域
    

    实现 Tabbar

    下面是我实现的大致效果:第一个元素左对齐,最后一个元素右对齐,我这边是直接写死的,你们封装一下 在外边直接用就好了。

    代码如下:

    1. import 'dart:ui';
    2. import 'package:flutter/material.dart';
    3. import 'package:game/const/app_textStyle.dart';
    4. import 'package:game/utils/app_widget.dart';
    5. import 'package:game/wrap/extension/extension.dart';
    6. class PageTabBar extends StatefulWidget {
    7. const PageTabBar({Key? key}) : super(key: key);
    8. @override
    9. State createState() => _PageTabBarState();
    10. }
    11. class _PageTabBarState extends State<PageTabBar> {
    12. final ScrollController _controller = ScrollController();
    13. int _selectIndex = 0;
    14. double _width = 0;
    15. double _positionX = 0;
    16. final List<Map> _listMap = [
    17. {'width': 0, 'name': '一号', 'key': GlobalKey()},
    18. {'width': 0, 'name': '二二号技师', 'key': GlobalKey()},
    19. {'width': 0, 'name': '三三三号技师', 'key': GlobalKey()},
    20. {'width': 0, 'name': '四号技师', 'key': GlobalKey()},
    21. {'width': 0, 'name': '五五号技师', 'key': GlobalKey()},
    22. {'width': 0, 'name': '六六六号技师', 'key': GlobalKey()},
    23. {'width': 0, 'name': '七号技师', 'key': GlobalKey()},
    24. {'width': 0, 'name': '八八号技师', 'key': GlobalKey()},
    25. {'width': 0, 'name': '九', 'key': GlobalKey()},
    26. {'width': 0, 'name': '十号技师', 'key': GlobalKey()},
    27. ];
    28. @override
    29. void initState() {
    30. // TODO: implement initState
    31. super.initState();
    32. WidgetsBinding.instance.addPostFrameCallback((_) => _printSize());
    33. // _controller.addListener(() {
    34. // print('_controller.offset:${_controller.offset}');
    35. // });
    36. }
    37. @override
    38. void dispose() {
    39. // TODO: implement dispose
    40. _controller.dispose();
    41. super.dispose();
    42. }
    43. void _printSize() {
    44. for (Map element in _listMap) {
    45. final RenderBox box = element['key'].currentContext.findRenderObject();
    46. element['width'] = box.size.width;
    47. }
    48. _width = _listMap[0]['width'];
    49. _selectItem(0);
    50. }
    51. @override
    52. Widget build(BuildContext context) {
    53. return Scaffold(
    54. appBar: AppWidget.appBar(title: 'TabBar 测试页面'),
    55. body: Center(
    56. child: Container(
    57. height: 220.cale,
    58. width: 710.cale,
    59. color: Colors.deepOrangeAccent,
    60. child: SingleChildScrollView(
    61. physics: const BouncingScrollPhysics(
    62. parent: AlwaysScrollableScrollPhysics(),
    63. ),
    64. controller: _controller,
    65. scrollDirection: Axis.horizontal,
    66. child: Stack(
    67. children: [
    68. Row(
    69. children: _listMap
    70. .asMap()
    71. .map(
    72. (key, value) => MapEntry(
    73. key,
    74. AppWidget.inkWellEffectNone(
    75. onTap: () {
    76. _selectItem(key);
    77. },
    78. child: Container(
    79. padding: key == 0
    80. ? EdgeInsets.only(right: 25.cale)
    81. : key == _listMap.length - 1
    82. ? EdgeInsets.only(left: 25.cale)
    83. : EdgeInsets.symmetric(
    84. horizontal: 25.cale),
    85. key: value['key'],
    86. color: Colors.blue.withOpacity(0.1 * key),
    87. height: 180.cale,
    88. child: Center(
    89. child: Text(
    90. value['name'],
    91. style: _selectIndex == key
    92. ? AppTextStyle.textStyle_34_FD3949_Bold
    93. : AppTextStyle.textStyle_30_1A1A1A,
    94. ),
    95. ),
    96. ),
    97. ),
    98. ),
    99. )
    100. .values
    101. .toList(),
    102. ),
    103. AnimatedPositioned(
    104. bottom: 0.cale,
    105. left: _positionX,
    106. duration: const Duration(milliseconds: 250),
    107. child: AnimatedContainer(
    108. duration: const Duration(milliseconds: 250),
    109. width: _width,
    110. child: Container(
    111. height: 20.cale,
    112. margin: EdgeInsets.symmetric(horizontal: 25.cale),
    113. width: double.infinity,
    114. color: Colors.green,
    115. ),
    116. ),
    117. )
    118. ],
    119. ),
    120. ),
    121. ),
    122. ),
    123. );
    124. }
    125. void _selectItem(int index) {
    126. print('index:$index');
    127. final ScrollPosition position = _controller.position;
    128. setState(() {
    129. _selectIndex = index;
    130. _width = _listMap[index]['width'];
    131. });
    132. _positionX = 0;
    133. List.generate(index, (itemIndex) {
    134. _positionX += _listMap[itemIndex]['width'];
    135. });
    136. //当前展示的元素位置 中心点位置,用户确定 滚动位置
    137. double viewPosition = _positionX + _listMap[index]['width'] / 2;
    138. double movePosition = viewPosition - position.viewportDimension / 2;
    139. movePosition = clampDouble(
    140. movePosition, position.minScrollExtent, position.maxScrollExtent);
    141. _controller.animateTo(
    142. movePosition,
    143. duration: const Duration(milliseconds: 300),
    144. curve: Curves.easeOut,
    145. );
    146. }
    147. }

    可以按需求封装下就能上手使用了

  • 相关阅读:
    chapter 11 in C primer plus
    长安链fact示例合约statedb存储数据原文带有cmecv1.0原因分析
    618如何冲出重围?海尔智家:做好用户的数字化
    Java Redis多限流
    【白板推导系列笔记】线性分类-背景&感知机
    Java的Map中put,compute,computeIfAbsent,putIfAbsent与的区别
    行为学派 进化计算
    深入了解ln命令:创建硬链接和符号链接的实用指南
    Day30_路由的query参数
    攻防世界-adworld-fileinclude
  • 原文地址:https://blog.csdn.net/nicepainkiller/article/details/140432190