Commit 7ada4667 authored by Hans Muller's avatar Hans Muller Committed by GitHub

TabPageSelector colors and indicatorSize (#10665)

parent ede575a9
......@@ -961,12 +961,19 @@ class _TabBarViewState extends State<TabBarView> {
}
}
/// 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<Widget>.generate(tabController.length, (int tabIndex) {
return _buildTabIndicator(tabIndex, tabController, selectedColor, previousColor);
return _buildTabIndicator(tabIndex, tabController, selectedColorTween, previousColorTween);
}).toList(),
),
);
......
......@@ -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: <Widget>[
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 <Color>[selectedColor, unselectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[kSelectedColor, kUnselectedColor, kUnselectedColor]);
tabController.index = 1;
await tester.pump();
expect(tabController.index, 1);
expect(indicatorColors(tester), const <Color>[unselectedColor, selectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[kUnselectedColor, kSelectedColor, kUnselectedColor]);
tabController.index = 2;
await tester.pump();
expect(tabController.index, 2);
expect(indicatorColors(tester), const <Color>[unselectedColor, unselectedColor, selectedColor]);
expect(indicatorColors(tester), const <Color>[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 <Color>[selectedColor, unselectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[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<Color> 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 <Color>[unselectedColor, selectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[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 <Color>[unselectedColor, unselectedColor, selectedColor]);
expect(indicatorColors(tester), const <Color>[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 <Color>[unselectedColor, selectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[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<Color> 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 <Color>[unselectedColor, selectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[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 <Color>[unselectedColor, selectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[kUnselectedColor, kSelectedColor, kUnselectedColor]);
// Completing the gesture doesn't change anything
await gesture.up();
await tester.pumpAndSettle();
colors = indicatorColors(tester);
expect(indicatorColors(tester), const <Color>[unselectedColor, selectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[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 <Color>[unselectedColor, unselectedColor, selectedColor]);
expect(indicatorColors(tester), const <Color>[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 <Color>[unselectedColor, selectedColor, unselectedColor]);
expect(indicatorColors(tester), const <Color>[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 <Color>[kRed, kBlue, kRed]);
tabController.index = 0;
await tester.pumpAndSettle();
expect(indicatorColors(tester), const <Color>[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<Element> 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);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment