From 7ada46677bb8d5a8f9db3fc967f7be57247dfef4 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 14 Jun 2017 11:25:03 -0700 Subject: [PATCH] TabPageSelector colors and indicatorSize (#10665) --- packages/flutter/lib/src/material/tabs.dart | 71 +++++++++++---- .../test/material/page_selector_test.dart | 90 ++++++++++++++----- 2 files changed, 119 insertions(+), 42 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index eb7b9e7a946..796ca9ebc4f 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -961,12 +961,19 @@ class _TabBarViewState extends State { } } -/// Displays a single 12x12 circle with the specified border and background colors. +/// Displays a single circle with the specified border and background colors. /// /// Used by [TabPageSelector] to indicate the selected page. class TabPageSelectorIndicator extends StatelessWidget { /// Creates an indicator used by [TabPageSelector]. - const TabPageSelectorIndicator({ Key key, this.backgroundColor, this.borderColor }) : super(key: key); + /// + /// The [backgroundColor], [borderColor], and [size] parameters cannot be null. + const TabPageSelectorIndicator({ + Key key, + @required this.backgroundColor, + @required this.borderColor, + @required this.size, + }) : assert(backgroundColor != null), assert(borderColor != null), assert(size != null), super(key: key); /// The indicator circle's background color. final Color backgroundColor; @@ -974,11 +981,14 @@ class TabPageSelectorIndicator extends StatelessWidget { /// The indicator circle's border color. final Color borderColor; + /// The indicator circle's diameter. + final double size; + @override Widget build(BuildContext context) { return new Container( - width: 12.0, - height: 12.0, + width: size, + height: size, margin: const EdgeInsets.all(4.0), decoration: new BoxDecoration( color: backgroundColor, @@ -996,7 +1006,13 @@ class TabPageSelectorIndicator extends StatelessWidget { /// ancestor. class TabPageSelector extends StatelessWidget { /// Creates a compact widget that indicates which tab has been selected. - const TabPageSelector({ Key key, this.controller }) : super(key: key); + const TabPageSelector({ + Key key, + this.controller, + this.indicatorSize: 12.0, + this.color, + this.selectedColor, + }) : assert(indicatorSize != null && indicatorSize > 0.0), super(key: key); /// This widget's selection and animation state. /// @@ -1004,47 +1020,64 @@ class TabPageSelector extends StatelessWidget { /// will be used. final TabController controller; + /// The indicator circle's diameter (the default value is 12.0). + final double indicatorSize; + + /// The indicator cicle's fill color for unselected pages. + /// + /// If this parameter is null then the indicator is filled with [Colors.transparent]. + final Color color; + + /// The indicator cicle's fill color for selected pages and border color + /// for all indicator circles. + /// + /// If this parameter is null then the indicator is filled with the theme's + /// accent color, [ThemeData.accentColor]. + final Color selectedColor; + Widget _buildTabIndicator( int tabIndex, TabController tabController, - ColorTween selectedColor, - ColorTween previousColor, + ColorTween selectedColorTween, + ColorTween previousColorTween, ) { Color background; if (tabController.indexIsChanging) { // The selection's animation is animating from previousValue to value. final double t = 1.0 - _indexChangeProgress(tabController); if (tabController.index == tabIndex) - background = selectedColor.lerp(t); + background = selectedColorTween.lerp(t); else if (tabController.previousIndex == tabIndex) - background = previousColor.lerp(t); + background = previousColorTween.lerp(t); else - background = selectedColor.begin; + background = selectedColorTween.begin; } else { // The selection's offset reflects how far the TabBarView has /// been dragged to the left (-1.0 to 0.0) or the right (0.0 to 1.0). final double offset = tabController.offset; if (tabController.index == tabIndex) { - background = selectedColor.lerp(1.0 - offset.abs()); + background = selectedColorTween.lerp(1.0 - offset.abs()); } else if (tabController.index == tabIndex - 1 && offset > 0.0) { - background = selectedColor.lerp(offset); + background = selectedColorTween.lerp(offset); } else if (tabController.index == tabIndex + 1 && offset < 0.0) { - background = selectedColor.lerp(-offset); + background = selectedColorTween.lerp(-offset); } else { - background = selectedColor.begin; + background = selectedColorTween.begin; } } return new TabPageSelectorIndicator( backgroundColor: background, - borderColor: selectedColor.end, + borderColor: selectedColorTween.end, + size: indicatorSize, ); } @override Widget build(BuildContext context) { - final Color color = Theme.of(context).accentColor; - final ColorTween selectedColor = new ColorTween(begin: Colors.transparent, end: color); - final ColorTween previousColor = new ColorTween(begin: color, end: Colors.transparent); + final Color fixColor = color ?? Colors.transparent; + final Color fixSelectedColor = selectedColor ?? Theme.of(context).accentColor; + final ColorTween selectedColorTween = new ColorTween(begin: fixColor, end: fixSelectedColor); + final ColorTween previousColorTween = new ColorTween(begin: fixSelectedColor, end: fixColor); final TabController tabController = controller ?? DefaultTabController.of(context); assert(() { if (tabController == null) { @@ -1070,7 +1103,7 @@ class TabPageSelector extends StatelessWidget { child: new Row( mainAxisSize: MainAxisSize.min, children: new List.generate(tabController.length, (int tabIndex) { - return _buildTabIndicator(tabIndex, tabController, selectedColor, previousColor); + return _buildTabIndicator(tabIndex, tabController, selectedColorTween, previousColorTween); }).toList(), ), ); diff --git a/packages/flutter/test/material/page_selector_test.dart b/packages/flutter/test/material/page_selector_test.dart index 814b7a89a28..ca1a65e7c44 100644 --- a/packages/flutter/test/material/page_selector_test.dart +++ b/packages/flutter/test/material/page_selector_test.dart @@ -5,12 +5,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; -const Color selectedColor = const Color(0xFF00FF00); -const Color unselectedColor = Colors.transparent; +const Color kSelectedColor = const Color(0xFF00FF00); +const Color kUnselectedColor = Colors.transparent; -Widget buildFrame(TabController tabController) { +Widget buildFrame(TabController tabController, { Color color, Color selectedColor, double indicatorSize: 12.0 }) { return new Theme( - data: new ThemeData(accentColor: selectedColor), + data: new ThemeData(accentColor: kSelectedColor), child: new SizedBox.expand( child: new Center( child: new SizedBox( @@ -18,7 +18,12 @@ Widget buildFrame(TabController tabController) { height: 400.0, child: new Column( children: [ - new TabPageSelector(controller: tabController), + new TabPageSelector( + controller: tabController, + color: color, + selectedColor: selectedColor, + indicatorSize: indicatorSize, + ), new Flexible( child: new TabBarView( controller: tabController, @@ -56,17 +61,17 @@ void main() { await tester.pumpWidget(buildFrame(tabController)); expect(tabController.index, 0); - expect(indicatorColors(tester), const [selectedColor, unselectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kSelectedColor, kUnselectedColor, kUnselectedColor]); tabController.index = 1; await tester.pump(); expect(tabController.index, 1); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); tabController.index = 2; await tester.pump(); expect(tabController.index, 2); - expect(indicatorColors(tester), const [unselectedColor, unselectedColor, selectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kUnselectedColor, kSelectedColor]); }); testWidgets('PageSelector responds correctly to TabController.animateTo()', (WidgetTester tester) async { @@ -77,7 +82,7 @@ void main() { await tester.pumpWidget(buildFrame(tabController)); expect(tabController.index, 0); - expect(indicatorColors(tester), const [selectedColor, unselectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kSelectedColor, kUnselectedColor, kUnselectedColor]); tabController.animateTo(1, duration: const Duration(milliseconds: 200)); await tester.pump(); @@ -87,14 +92,14 @@ void main() { await tester.pump(const Duration(milliseconds: 10)); List colors = indicatorColors(tester); expect(colors[0].alpha, greaterThan(colors[1].alpha)); - expect(colors[2], unselectedColor); + expect(colors[2], kUnselectedColor); await tester.pump(const Duration(milliseconds: 175)); colors = indicatorColors(tester); expect(colors[0].alpha, lessThan(colors[1].alpha)); - expect(colors[2], unselectedColor); + expect(colors[2], kUnselectedColor); await tester.pumpAndSettle(); expect(tabController.index, 1); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); tabController.animateTo(2, duration: const Duration(milliseconds: 200)); await tester.pump(); @@ -102,14 +107,14 @@ void main() { await tester.pump(const Duration(milliseconds: 10)); colors = indicatorColors(tester); expect(colors[1].alpha, greaterThan(colors[2].alpha)); - expect(colors[0], unselectedColor); + expect(colors[0], kUnselectedColor); await tester.pump(const Duration(milliseconds: 175)); colors = indicatorColors(tester); expect(colors[1].alpha, lessThan(colors[2].alpha)); - expect(colors[0], unselectedColor); + expect(colors[0], kUnselectedColor); await tester.pumpAndSettle(); expect(tabController.index, 2); - expect(indicatorColors(tester), const [unselectedColor, unselectedColor, selectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kUnselectedColor, kSelectedColor]); }); testWidgets('PageSelector responds correctly to TabBarView drags', (WidgetTester tester) async { @@ -121,7 +126,7 @@ void main() { await tester.pumpWidget(buildFrame(tabController)); expect(tabController.index, 1); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0)); @@ -131,13 +136,13 @@ void main() { await tester.pumpAndSettle(); List colors = indicatorColors(tester); expect(colors[1].alpha, greaterThan(colors[2].alpha)); - expect(colors[0], unselectedColor); + expect(colors[0], kUnselectedColor); // Drag back to where we started. await gesture.moveBy(const Offset(100.0, 0.0)); await tester.pumpAndSettle(); colors = indicatorColors(tester); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); // Drag to the left moving the selection towards indicator 0. Indicator 0's // opacity should increase and Indicator 1's opacity should decrease. @@ -145,30 +150,69 @@ void main() { await tester.pumpAndSettle(); colors = indicatorColors(tester); expect(colors[1].alpha, greaterThan(colors[0].alpha)); - expect(colors[2], unselectedColor); + expect(colors[2], kUnselectedColor); // Drag back to where we started. await gesture.moveBy(const Offset(-100.0, 0.0)); await tester.pumpAndSettle(); colors = indicatorColors(tester); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); // Completing the gesture doesn't change anything await gesture.up(); await tester.pumpAndSettle(); colors = indicatorColors(tester); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); // Fling to the left, selects indicator 2 await tester.fling(find.byType(TabBarView), const Offset(-100.0, 0.0), 1000.0); await tester.pumpAndSettle(); - expect(indicatorColors(tester), const [unselectedColor, unselectedColor, selectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kUnselectedColor, kSelectedColor]); // Fling to the right, selects indicator 1 await tester.fling(find.byType(TabBarView), const Offset(100.0, 0.0), 1000.0); await tester.pumpAndSettle(); - expect(indicatorColors(tester), const [unselectedColor, selectedColor, unselectedColor]); + expect(indicatorColors(tester), const [kUnselectedColor, kSelectedColor, kUnselectedColor]); }); + testWidgets('PageSelector indicatorColors', (WidgetTester tester) async { + const Color kRed = const Color(0xFFFF0000); + const Color kBlue = const Color(0xFF0000FF); + + final TabController tabController = new TabController( + vsync: const TestVSync(), + initialIndex: 1, + length: 3, + ); + await tester.pumpWidget(buildFrame(tabController, color: kRed, selectedColor: kBlue)); + + expect(tabController.index, 1); + expect(indicatorColors(tester), const [kRed, kBlue, kRed]); + + tabController.index = 0; + await tester.pumpAndSettle(); + expect(indicatorColors(tester), const [kBlue, kRed, kRed]); + }); + + testWidgets('PageSelector indicatorSize', (WidgetTester tester) async { + final TabController tabController = new TabController( + vsync: const TestVSync(), + initialIndex: 1, + length: 3, + ); + await tester.pumpWidget(buildFrame(tabController, indicatorSize: 16.0)); + + final Iterable indicatorElements = find.descendant( + of: find.byType(TabPageSelector), + matching: find.byType(TabPageSelectorIndicator), + ).evaluate(); + + // Indicators get an 8 pixel margin, 16 + 8 = 24. + for (Element indicatorElement in indicatorElements) + expect(indicatorElement.size, const Size(24.0, 24.0)); + + expect(tester.getSize(find.byType(TabPageSelector)).height, 24.0); + }); + }