| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865 |
- import 'dart:math' as math;
- import 'dart:ui' show lerpDouble;
- import 'package:flutter/foundation.dart';
- import 'package:flutter/gestures.dart' show DragStartBehavior;
- import 'package:flutter/material.dart';
- import 'package:flutter/rendering.dart';
- const double _kTabHeight = 46.0;
- typedef _LayoutCallback = void Function(
- List<double> xOffsets, TextDirection textDirection, double width);
- class _TabLabelBarRenderer extends RenderFlex {
- _TabLabelBarRenderer({
- required Axis direction,
- required MainAxisSize mainAxisSize,
- required MainAxisAlignment mainAxisAlignment,
- required CrossAxisAlignment crossAxisAlignment,
- required TextDirection textDirection,
- required VerticalDirection verticalDirection,
- required this.onPerformLayout,
- }) : super(
- direction: direction,
- mainAxisSize: mainAxisSize,
- mainAxisAlignment: mainAxisAlignment,
- crossAxisAlignment: crossAxisAlignment,
- textDirection: textDirection,
- verticalDirection: verticalDirection,
- );
- _LayoutCallback onPerformLayout;
- @override
- void performLayout() {
- super.performLayout();
- // xOffsets will contain childCount+1 values, giving the offsets of the
- // leading edge of the first tab as the first value, of the leading edge of
- // the each subsequent tab as each subsequent value, and of the trailing
- // edge of the last tab as the last value.
- RenderBox? child = firstChild;
- final List<double> xOffsets = <double>[];
- while (child != null) {
- final FlexParentData childParentData =
- child.parentData! as FlexParentData;
- xOffsets.add(childParentData.offset.dx);
- assert(child.parentData == childParentData);
- child = childParentData.nextSibling;
- }
- assert(textDirection != null);
- switch (textDirection!) {
- case TextDirection.rtl:
- xOffsets.insert(0, size.width);
- break;
- case TextDirection.ltr:
- xOffsets.add(size.width);
- break;
- }
- onPerformLayout(xOffsets, textDirection!, size.width);
- }
- }
- // This class and its renderer class only exist to report the widths of the tabs
- // upon layout. The tab widths are only used at paint time (see _IndicatorPainter)
- // or in response to input.
- class _TabLabelBar extends Flex {
- _TabLabelBar({
- required List<Widget> children,
- required this.onPerformLayout,
- }) : super(
- children: children,
- direction: Axis.horizontal,
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.center,
- verticalDirection: VerticalDirection.down,
- );
- final _LayoutCallback onPerformLayout;
- @override
- RenderFlex createRenderObject(BuildContext context) {
- return _TabLabelBarRenderer(
- direction: direction,
- mainAxisAlignment: mainAxisAlignment,
- mainAxisSize: mainAxisSize,
- crossAxisAlignment: crossAxisAlignment,
- textDirection: getEffectiveTextDirection(context)!,
- verticalDirection: verticalDirection,
- onPerformLayout: onPerformLayout,
- );
- }
- @override
- void updateRenderObject(
- BuildContext context, _TabLabelBarRenderer renderObject) {
- super.updateRenderObject(context, renderObject);
- renderObject.onPerformLayout = onPerformLayout;
- }
- }
- class _IndicatorPainter extends CustomPainter {
- _IndicatorPainter({
- required this.controller,
- required this.tabKeys,
- required _IndicatorPainter? old,
- }) : super(repaint: controller.animation) {
- if (old != null) {
- saveTabOffsets(old._currentTabOffsets, old._currentTextDirection);
- }
- }
- final TabController controller;
- final List<GlobalKey> tabKeys;
- // _currentTabOffsets and _currentTextDirection are set each time TabBar
- // layout is completed. These values can be null when TabBar contains no
- // tabs, since there are nothing to lay out.
- List<double>? _currentTabOffsets;
- TextDirection? _currentTextDirection;
- BoxPainter? _painter;
- bool _needsPaint = false;
- void markNeedsPaint() {
- _needsPaint = true;
- }
- void dispose() {
- _painter?.dispose();
- }
- void saveTabOffsets(List<double>? tabOffsets, TextDirection? textDirection) {
- _currentTabOffsets = tabOffsets;
- _currentTextDirection = textDirection;
- }
- // _currentTabOffsets[index] is the offset of the start edge of the tab at index, and
- // _currentTabOffsets[_currentTabOffsets.length] is the end edge of the last tab.
- int get maxTabIndex => _currentTabOffsets!.length - 2;
- double centerOf(int tabIndex) {
- assert(_currentTabOffsets != null);
- assert(_currentTabOffsets!.isNotEmpty);
- assert(tabIndex >= 0);
- assert(tabIndex <= maxTabIndex);
- return (_currentTabOffsets![tabIndex] + _currentTabOffsets![tabIndex + 1]) /
- 2.0;
- }
- @override
- void paint(Canvas canvas, Size size) {
- _needsPaint = false;
- }
- @override
- bool shouldRepaint(_IndicatorPainter old) {
- return _needsPaint ||
- controller != old.controller ||
- tabKeys.length != old.tabKeys.length ||
- (!listEquals(_currentTabOffsets, old._currentTabOffsets)) ||
- _currentTextDirection != old._currentTextDirection;
- }
- }
- // This class, and TabBarScrollController, only exist to handle the case
- // where a scrollable TabBar has a non-zero initialIndex. In that case we can
- // only compute the scroll position's initial scroll offset (the "correct"
- // pixels value) after the TabBar viewport width and scroll limits are known.
- class _TabBarScrollPosition extends ScrollPositionWithSingleContext {
- _TabBarScrollPosition({
- required ScrollPhysics physics,
- required ScrollContext context,
- required ScrollPosition? oldPosition,
- required this.tabBar,
- }) : super(
- initialPixels: null,
- physics: physics,
- context: context,
- oldPosition: oldPosition,
- );
- final _FlutterFlowButtonTabBarState tabBar;
- bool _viewportDimensionWasNonZero = false;
- // Position should be adjusted at least once.
- bool _needsPixelsCorrection = true;
- @override
- bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
- bool result = true;
- if (!_viewportDimensionWasNonZero) {
- _viewportDimensionWasNonZero = viewportDimension != 0.0;
- }
- // If the viewport never had a non-zero dimension, we just want to jump
- // to the initial scroll position to avoid strange scrolling effects in
- // release mode: In release mode, the viewport temporarily may have a
- // dimension of zero before the actual dimension is calculated. In that
- // scenario, setting the actual dimension would cause a strange scroll
- // effect without this guard because the super call below would starts a
- // ballistic scroll activity.
- if (!_viewportDimensionWasNonZero || _needsPixelsCorrection) {
- _needsPixelsCorrection = false;
- correctPixels(tabBar._initialScrollOffset(
- viewportDimension, minScrollExtent, maxScrollExtent));
- result = false;
- }
- return super.applyContentDimensions(minScrollExtent, maxScrollExtent) &&
- result;
- }
- void markNeedsPixelsCorrection() {
- _needsPixelsCorrection = true;
- }
- }
- // This class, and TabBarScrollPosition, only exist to handle the case
- // where a scrollable TabBar has a non-zero initialIndex.
- class _TabBarScrollController extends ScrollController {
- _TabBarScrollController(this.tabBar);
- final _FlutterFlowButtonTabBarState tabBar;
- @override
- ScrollPosition createScrollPosition(ScrollPhysics physics,
- ScrollContext context, ScrollPosition? oldPosition) {
- return _TabBarScrollPosition(
- physics: physics,
- context: context,
- oldPosition: oldPosition,
- tabBar: tabBar,
- );
- }
- }
- /// A Flutterflow Design widget that displays a horizontal row of tabs.
- class FlutterFlowButtonTabBar extends StatefulWidget
- implements PreferredSizeWidget {
- /// The [tabs] argument must not be null and its length must match the [controller]'s
- /// [TabController.length].
- ///
- /// If a [TabController] is not provided, then there must be a
- /// [DefaultTabController] ancestor.
- ///
- const FlutterFlowButtonTabBar({
- Key? key,
- required this.tabs,
- this.controller,
- this.isScrollable = false,
- this.useToggleButtonStyle = false,
- this.dragStartBehavior = DragStartBehavior.start,
- this.onTap,
- this.backgroundColor,
- this.unselectedBackgroundColor,
- this.decoration,
- this.unselectedDecoration,
- this.labelStyle,
- this.unselectedLabelStyle,
- this.labelColor,
- this.unselectedLabelColor,
- this.borderWidth = 0,
- this.borderColor = Colors.transparent,
- this.unselectedBorderColor = Colors.transparent,
- this.physics = const BouncingScrollPhysics(),
- this.labelPadding = const EdgeInsets.symmetric(horizontal: 4),
- this.buttonMargin = const EdgeInsets.all(4),
- this.padding = EdgeInsets.zero,
- this.borderRadius = 8.0,
- this.elevation = 0,
- }) : super(key: key);
- /// Typically a list of two or more [Tab] widgets.
- ///
- /// The length of this list must match the [controller]'s [TabController.length]
- /// and the length of the [TabBarView.children] list.
- final List<Widget> tabs;
- /// This widget's selection and animation state.
- ///
- /// If [TabController] is not provided, then the value of [DefaultTabController.of]
- /// will be used.
- final TabController? controller;
- /// Whether this tab bar can be scrolled horizontally.
- ///
- /// If [isScrollable] is true, then each tab is as wide as needed for its label
- /// and the entire [FlutterFlowButtonTabBar] is scrollable. Otherwise each tab gets an equal
- /// share of the available space.
- final bool isScrollable;
- /// Whether the tab buttons should be styled as toggle buttons.
- final bool useToggleButtonStyle;
- /// The background [Color] of the button on its selected state.
- final Color? backgroundColor;
- /// The background [Color] of the button on its unselected state.
- final Color? unselectedBackgroundColor;
- /// The [BoxDecoration] of the button on its selected state.
- ///
- /// If [BoxDecoration] is not provided, [backgroundColor] is used.
- final BoxDecoration? decoration;
- /// The [BoxDecoration] of the button on its unselected state.
- ///
- /// If [BoxDecoration] is not provided, [unselectedBackgroundColor] is used.
- final BoxDecoration? unselectedDecoration;
- /// The [TextStyle] of the button's [Text] on its selected state. The color provided
- /// on the TextStyle will be used for the [Icon]'s color.
- final TextStyle? labelStyle;
- /// The color of selected tab labels.
- final Color? labelColor;
- /// The color of unselected tab labels.
- final Color? unselectedLabelColor;
- /// The [TextStyle] of the button's [Text] on its unselected state. The color provided
- /// on the TextStyle will be used for the [Icon]'s color.
- final TextStyle? unselectedLabelStyle;
- /// The with of solid [Border] for each button. If no value is provided, the border
- /// is not drawn.
- final double borderWidth;
- /// The [Color] of solid [Border] for each button.
- final Color? borderColor;
- /// The [Color] of solid [Border] for each button. If no value is provided, the value of
- /// [this.borderColor] is used.
- final Color? unselectedBorderColor;
- /// The [EdgeInsets] used for the [Padding] of the buttons' content.
- ///
- /// The default value is [EdgeInsets.symmetric(horizontal: 4)].
- final EdgeInsetsGeometry labelPadding;
- /// The [EdgeInsets] used for the [Margin] of the buttons.
- ///
- /// The default value is [EdgeInsets.all(4)].
- final EdgeInsetsGeometry buttonMargin;
- /// The amount of space by which to inset the tab bar.
- final EdgeInsetsGeometry? padding;
- /// The value of the [BorderRadius.circular] applied to each button.
- final double borderRadius;
- /// The value of the [elevation] applied to each button.
- final double elevation;
- final DragStartBehavior dragStartBehavior;
- final ValueChanged<int>? onTap;
- final ScrollPhysics? physics;
- /// A size whose height depends on if the tabs have both icons and text.
- ///
- /// [AppBar] uses this size to compute its own preferred size.
- @override
- Size get preferredSize {
- double maxHeight = _kTabHeight;
- for (final Widget item in tabs) {
- if (item is PreferredSizeWidget) {
- final double itemHeight = item.preferredSize.height;
- maxHeight = math.max(itemHeight, maxHeight);
- }
- }
- return Size.fromHeight(
- maxHeight + labelPadding.vertical + buttonMargin.vertical);
- }
- @override
- State<FlutterFlowButtonTabBar> createState() =>
- _FlutterFlowButtonTabBarState();
- }
- class _FlutterFlowButtonTabBarState extends State<FlutterFlowButtonTabBar>
- with TickerProviderStateMixin {
- ScrollController? _scrollController;
- TabController? _controller;
- _IndicatorPainter? _indicatorPainter;
- late AnimationController _animationController;
- int _currentIndex = 0;
- int _prevIndex = -1;
- late double _tabStripWidth;
- late List<GlobalKey> _tabKeys;
- final GlobalKey _tabsParentKey = GlobalKey();
- bool _debugHasScheduledValidTabsCountCheck = false;
- @override
- void initState() {
- super.initState();
- // If indicatorSize is TabIndicatorSize.label, _tabKeys[i] is used to find
- // the width of tab widget i. See _IndicatorPainter.indicatorRect().
- _tabKeys = widget.tabs.map((tab) => GlobalKey()).toList();
- /// The animation duration is 2/3 of the tab scroll animation duration in
- /// Material design (kTabScrollDuration).
- _animationController = AnimationController(
- vsync: this, duration: const Duration(milliseconds: 200));
- // so the buttons start in their "final" state (color)
- _animationController
- ..value = 1.0
- ..addListener(() {
- if (mounted) {
- setState(() {});
- }
- });
- }
- // If the TabBar is rebuilt with a new tab controller, the caller should
- // dispose the old one. In that case the old controller's animation will be
- // null and should not be accessed.
- bool get _controllerIsValid => _controller?.animation != null;
- void _updateTabController() {
- final TabController? newController =
- widget.controller ?? DefaultTabController.maybeOf(context);
- assert(() {
- if (newController == null) {
- throw FlutterError(
- 'No TabController for ${widget.runtimeType}.\n'
- 'When creating a ${widget.runtimeType}, you must either provide an explicit '
- 'TabController using the "controller" property, or you must ensure that there '
- 'is a DefaultTabController above the ${widget.runtimeType}.\n'
- 'In this case, there was neither an explicit controller nor a default controller.',
- );
- }
- return true;
- }());
- if (newController == _controller) {
- return;
- }
- if (_controllerIsValid) {
- _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
- _controller!.removeListener(_handleTabControllerTick);
- }
- _controller = newController;
- if (_controller != null) {
- _controller!.animation!.addListener(_handleTabControllerAnimationTick);
- _controller!.addListener(_handleTabControllerTick);
- _currentIndex = _controller!.index;
- }
- }
- void _initIndicatorPainter() {
- _indicatorPainter = !_controllerIsValid
- ? null
- : _IndicatorPainter(
- controller: _controller!,
- tabKeys: _tabKeys,
- old: _indicatorPainter,
- );
- }
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- assert(debugCheckHasMaterial(context));
- _updateTabController();
- _initIndicatorPainter();
- }
- @override
- void didUpdateWidget(FlutterFlowButtonTabBar oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (widget.controller != oldWidget.controller) {
- _updateTabController();
- _initIndicatorPainter();
- // Adjust scroll position.
- if (_scrollController != null) {
- final ScrollPosition position = _scrollController!.position;
- if (position is _TabBarScrollPosition) {
- position.markNeedsPixelsCorrection();
- }
- }
- }
- if (widget.tabs.length > _tabKeys.length) {
- final int delta = widget.tabs.length - _tabKeys.length;
- _tabKeys.addAll(List<GlobalKey>.generate(delta, (int n) => GlobalKey()));
- } else if (widget.tabs.length < _tabKeys.length) {
- _tabKeys.removeRange(widget.tabs.length, _tabKeys.length);
- }
- }
- @override
- void dispose() {
- _indicatorPainter!.dispose();
- if (_controllerIsValid) {
- _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
- _controller!.removeListener(_handleTabControllerTick);
- }
- _controller = null;
- // We don't own the _controller Animation, so it's not disposed here.
- super.dispose();
- }
- int get maxTabIndex => _indicatorPainter!.maxTabIndex;
- double _tabScrollOffset(
- int index, double viewportWidth, double minExtent, double maxExtent) {
- if (!widget.isScrollable) {
- return 0.0;
- }
- double tabCenter = _indicatorPainter!.centerOf(index);
- double paddingStart;
- switch (Directionality.of(context)) {
- case TextDirection.rtl:
- paddingStart = widget.padding?.resolve(TextDirection.rtl).right ?? 0;
- tabCenter = _tabStripWidth - tabCenter;
- break;
- case TextDirection.ltr:
- paddingStart = widget.padding?.resolve(TextDirection.ltr).left ?? 0;
- break;
- }
- return clampDouble(
- tabCenter + paddingStart - viewportWidth / 2.0, minExtent, maxExtent);
- }
- double _tabCenteredScrollOffset(int index) {
- final ScrollPosition position = _scrollController!.position;
- return _tabScrollOffset(index, position.viewportDimension,
- position.minScrollExtent, position.maxScrollExtent);
- }
- double _initialScrollOffset(
- double viewportWidth, double minExtent, double maxExtent) {
- return _tabScrollOffset(_currentIndex, viewportWidth, minExtent, maxExtent);
- }
- void _scrollToCurrentIndex() {
- final double offset = _tabCenteredScrollOffset(_currentIndex);
- _scrollController!
- .animateTo(offset, duration: kTabScrollDuration, curve: Curves.ease);
- }
- void _scrollToControllerValue() {
- final double? leadingPosition =
- _currentIndex > 0 ? _tabCenteredScrollOffset(_currentIndex - 1) : null;
- final double middlePosition = _tabCenteredScrollOffset(_currentIndex);
- final double? trailingPosition = _currentIndex < maxTabIndex
- ? _tabCenteredScrollOffset(_currentIndex + 1)
- : null;
- final double index = _controller!.index.toDouble();
- final double value = _controller!.animation!.value;
- final double offset;
- if (value == index - 1.0) {
- offset = leadingPosition ?? middlePosition;
- } else if (value == index + 1.0) {
- offset = trailingPosition ?? middlePosition;
- } else if (value == index) {
- offset = middlePosition;
- } else if (value < index) {
- offset = leadingPosition == null
- ? middlePosition
- : lerpDouble(middlePosition, leadingPosition, index - value)!;
- } else {
- offset = trailingPosition == null
- ? middlePosition
- : lerpDouble(middlePosition, trailingPosition, value - index)!;
- }
- _scrollController!.jumpTo(offset);
- }
- void _handleTabControllerAnimationTick() {
- assert(mounted);
- if (!_controller!.indexIsChanging && widget.isScrollable) {
- // Sync the TabBar's scroll position with the TabBarView's PageView.
- _currentIndex = _controller!.index;
- _scrollToControllerValue();
- }
- }
- void _handleTabControllerTick() {
- if (_controller!.index != _currentIndex) {
- _prevIndex = _currentIndex;
- _currentIndex = _controller!.index;
- _triggerAnimation();
- if (widget.isScrollable) {
- _scrollToCurrentIndex();
- }
- }
- setState(() {
- // Rebuild the tabs after a (potentially animated) index change
- // has completed.
- });
- }
- void _triggerAnimation() {
- // reset the animation so it's ready to go
- _animationController
- ..reset()
- ..forward();
- }
- // Called each time layout completes.
- void _saveTabOffsets(
- List<double> tabOffsets, TextDirection textDirection, double width) {
- _tabStripWidth = width;
- _indicatorPainter?.saveTabOffsets(tabOffsets, textDirection);
- }
- void _handleTap(int index) {
- assert(index >= 0 && index < widget.tabs.length);
- _controller?.animateTo(index);
- widget.onTap?.call(index);
- }
- Widget _buildStyledTab(Widget child, int index) {
- final tabBarTheme = TabBarTheme.of(context);
- final double animationValue;
- if (index == _currentIndex) {
- animationValue = _animationController.value;
- } else if (index == _prevIndex) {
- animationValue = 1 - _animationController.value;
- } else {
- animationValue = 0;
- }
- final TextStyle? textStyle = TextStyle.lerp(
- (widget.unselectedLabelStyle ??
- tabBarTheme.labelStyle ??
- DefaultTextStyle.of(context).style)
- .copyWith(
- color: widget.unselectedLabelColor,
- ),
- (widget.labelStyle ??
- tabBarTheme.labelStyle ??
- DefaultTextStyle.of(context).style)
- .copyWith(
- color: widget.labelColor,
- ),
- animationValue);
- final Color? textColor = Color.lerp(
- widget.unselectedLabelColor, widget.labelColor, animationValue);
- final Color? borderColor = Color.lerp(
- widget.unselectedBorderColor, widget.borderColor, animationValue);
- BoxDecoration? boxDecoration = BoxDecoration.lerp(
- BoxDecoration(
- color: widget.unselectedDecoration?.color ??
- widget.unselectedBackgroundColor ??
- Colors.transparent,
- boxShadow: widget.unselectedDecoration?.boxShadow,
- gradient: widget.unselectedDecoration?.gradient,
- borderRadius: widget.useToggleButtonStyle
- ? null
- : BorderRadius.circular(widget.borderRadius),
- ),
- BoxDecoration(
- color: widget.decoration?.color ??
- widget.backgroundColor ??
- Colors.transparent,
- boxShadow: widget.decoration?.boxShadow,
- gradient: widget.decoration?.gradient,
- borderRadius: widget.useToggleButtonStyle
- ? null
- : BorderRadius.circular(widget.borderRadius),
- ),
- animationValue);
- if (widget.useToggleButtonStyle &&
- widget.borderWidth > 0 &&
- boxDecoration != null) {
- if (index == 0) {
- boxDecoration = boxDecoration.copyWith(
- border: Border(
- right: BorderSide(
- color: widget.unselectedBorderColor ?? Colors.transparent,
- width: widget.borderWidth / 2,
- ),
- ),
- );
- } else if (index == widget.tabs.length - 1) {
- boxDecoration = boxDecoration.copyWith(
- border: Border(
- left: BorderSide(
- color: widget.unselectedBorderColor ?? Colors.transparent,
- width: widget.borderWidth / 2,
- ),
- ),
- );
- } else {
- boxDecoration = boxDecoration.copyWith(
- border: Border.symmetric(
- vertical: BorderSide(
- color: widget.unselectedBorderColor ?? Colors.transparent,
- width: widget.borderWidth / 2,
- ),
- ),
- );
- }
- }
- return Padding(
- key: _tabKeys[index],
- // padding for the buttons
- padding:
- widget.useToggleButtonStyle ? EdgeInsets.zero : widget.buttonMargin,
- child: TextButton(
- onPressed: () => _handleTap(index),
- style: ButtonStyle(
- elevation: MaterialStateProperty.all(
- widget.useToggleButtonStyle ? 0 : widget.elevation),
- /// give a pretty small minimum size
- minimumSize: MaterialStateProperty.all(const Size(10, 10)),
- padding: MaterialStateProperty.all(EdgeInsets.zero),
- textStyle: MaterialStateProperty.all(textStyle),
- foregroundColor: MaterialStateProperty.all(textColor),
- tapTargetSize: MaterialTapTargetSize.shrinkWrap,
- shape: MaterialStateProperty.all(
- widget.useToggleButtonStyle
- ? const RoundedRectangleBorder(
- side: BorderSide.none,
- borderRadius: BorderRadius.zero,
- )
- : RoundedRectangleBorder(
- side: (widget.borderWidth == 0)
- ? BorderSide.none
- : BorderSide(
- color: borderColor ?? Colors.transparent,
- width: widget.borderWidth,
- ),
- borderRadius: BorderRadius.circular(widget.borderRadius),
- ),
- ),
- ),
- child: Ink(
- decoration: boxDecoration,
- child: Container(
- padding: widget.labelPadding,
- alignment: Alignment.center,
- child: child,
- ),
- ),
- ),
- );
- }
- bool _debugScheduleCheckHasValidTabsCount() {
- if (_debugHasScheduledValidTabsCountCheck) {
- return true;
- }
- WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
- _debugHasScheduledValidTabsCountCheck = false;
- if (!mounted) {
- return;
- }
- assert(() {
- if (_controller!.length != widget.tabs.length) {
- throw FlutterError(
- "Controller's length property (${_controller!.length}) does not match the "
- "number of tabs (${widget.tabs.length}) present in TabBar's tabs property.",
- );
- }
- return true;
- }());
- });
- _debugHasScheduledValidTabsCountCheck = true;
- return true;
- }
- @override
- Widget build(BuildContext context) {
- assert(_debugScheduleCheckHasValidTabsCount());
- if (_controller!.length == 0) {
- return Container(
- height: _kTabHeight +
- widget.labelPadding.vertical +
- widget.buttonMargin.vertical,
- );
- }
- final List<Widget> wrappedTabs =
- List<Widget>.generate(widget.tabs.length, (int index) {
- return _buildStyledTab(widget.tabs[index], index);
- });
- final int tabCount = widget.tabs.length;
- // Add the tap handler to each tab. If the tab bar is not scrollable,
- // then give all of the tabs equal flexibility so that they each occupy
- // the same share of the tab bar's overall width.
- for (int index = 0; index < tabCount; index += 1) {
- if (!widget.isScrollable) {
- wrappedTabs[index] = Expanded(child: wrappedTabs[index]);
- }
- }
- Widget tabBar = AnimatedBuilder(
- animation: _animationController,
- key: _tabsParentKey,
- builder: (context, child) {
- Widget tabBarTemp = _TabLabelBar(
- onPerformLayout: _saveTabOffsets,
- children: wrappedTabs,
- );
- if (widget.useToggleButtonStyle) {
- tabBarTemp = Material(
- shape: widget.useToggleButtonStyle
- ? RoundedRectangleBorder(
- side: (widget.borderWidth == 0)
- ? BorderSide.none
- : BorderSide(
- color: widget.borderColor ?? Colors.transparent,
- width: widget.borderWidth,
- style: BorderStyle.solid,
- ),
- borderRadius: BorderRadius.circular(widget.borderRadius),
- )
- : null,
- elevation: widget.useToggleButtonStyle ? widget.elevation : 0,
- clipBehavior: Clip.antiAliasWithSaveLayer,
- child: tabBarTemp,
- );
- }
- return CustomPaint(
- painter: _indicatorPainter,
- child: tabBarTemp,
- );
- },
- );
- if (widget.isScrollable) {
- _scrollController ??= _TabBarScrollController(this);
- tabBar = SingleChildScrollView(
- dragStartBehavior: widget.dragStartBehavior,
- scrollDirection: Axis.horizontal,
- controller: _scrollController,
- padding: widget.padding,
- physics: widget.physics,
- child: tabBar,
- );
- } else if (widget.padding != null) {
- tabBar = Padding(
- padding: widget.padding!,
- child: tabBar,
- );
- }
- return tabBar;
- }
- }
|