Commit 1e415169 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Fix PageSelector, page selector gallery demo (#9448)

parent 4631717f
......@@ -12,7 +12,7 @@ class _PageSelector extends StatelessWidget {
void _handleArrowButtonPress(BuildContext context, int delta) {
final TabController controller = DefaultTabController.of(context);
if (!controller.indexIsChanging)
controller.animateTo(controller.index + delta);
controller.animateTo((controller.index + delta).clamp(0, icons.length - 1));
}
@override
......
......@@ -912,6 +912,33 @@ class _TabBarViewState extends State<TabBarView> {
}
}
/// Displays a single 12x12 circle with the specified border and background colors.
///
/// Used by [TabPageSelector] to indicate the selected page.
class TabPageSelectorIndicator extends StatelessWidget {
const TabPageSelectorIndicator({ Key key, this.backgroundColor, this.borderColor }) : super(key: key);
/// The indicator circle's background color.
final Color backgroundColor;
/// The indicator circle's border color.
final Color borderColor;
@override
Widget build(BuildContext context) {
return new Container(
width: 12.0,
height: 12.0,
margin: const EdgeInsets.all(4.0),
decoration: new BoxDecoration(
backgroundColor: backgroundColor,
border: new Border.all(color: borderColor),
shape: BoxShape.circle
),
);
}
}
/// Displays a row of small circular indicators, one per tab. The selected
/// tab's indicator is highlighted. Often used in conjuction with a [TabBarView].
///
......@@ -936,24 +963,30 @@ class TabPageSelector extends StatelessWidget {
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(_indexChangeProgress(tabController));
background = selectedColor.lerp(t);
else if (tabController.previousIndex == tabIndex)
background = previousColor.lerp(_indexChangeProgress(tabController));
background = previousColor.lerp(t);
else
background = selectedColor.begin;
} else {
background = tabController.index == tabIndex ? selectedColor.end : selectedColor.begin;
// 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());
} else if (tabController.index == tabIndex - 1 && offset > 0.0) {
background = selectedColor.lerp(offset);
} else if (tabController.index == tabIndex + 1 && offset < 0.0) {
background = selectedColor.lerp(-offset);
} else {
background = selectedColor.begin;
}
}
return new Container(
width: 12.0,
height: 12.0,
margin: const EdgeInsets.all(4.0),
decoration: new BoxDecoration(
backgroundColor: background,
border: new Border.all(color: selectedColor.end),
shape: BoxShape.circle
)
return new TabPageSelectorIndicator(
backgroundColor: background,
borderColor: selectedColor.end,
);
}
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
const Color selectedColor = const Color(0xFF00FF00);
const Color unselectedColor = Colors.transparent;
Widget buildFrame(TabController tabController) {
return new Theme(
data: new ThemeData(accentColor: selectedColor),
child: new SizedBox.expand(
child: new Center(
child: new SizedBox(
width: 400.0,
height: 400.0,
child: new Column(
children: <Widget>[
new TabPageSelector(controller: tabController),
new Flexible(
child: new TabBarView(
controller: tabController,
children: <Widget>[
const Center(child: const Text('0')),
const Center(child: const Text('1')),
const Center(child: const Text('2')),
],
),
),
],
),
),
),
),
);
}
List<Color> indicatorColors(WidgetTester tester) {
final Iterable<TabPageSelectorIndicator> indicators = tester.widgetList(
find.descendant(
of: find.byType(TabPageSelector),
matching: find.byType(TabPageSelectorIndicator)
)
);
return indicators.map((TabPageSelectorIndicator indicator) => indicator.backgroundColor).toList();
}
void main() {
testWidgets('PageSelector responds correctly to setting the TabController index', (WidgetTester tester) async {
final TabController tabController = new TabController(
vsync: const TestVSync(),
length: 3,
);
await tester.pumpWidget(buildFrame(tabController));
expect(tabController.index, 0);
expect(indicatorColors(tester), const <Color>[selectedColor, unselectedColor, unselectedColor]);
tabController.index = 1;
await tester.pump();
expect(tabController.index, 1);
expect(indicatorColors(tester), const <Color>[unselectedColor, selectedColor, unselectedColor]);
tabController.index = 2;
await tester.pump();
expect(tabController.index, 2);
expect(indicatorColors(tester), const <Color>[unselectedColor, unselectedColor, selectedColor]);
});
testWidgets('PageSelector responds correctly to TabController.animateTo()', (WidgetTester tester) async {
final TabController tabController = new TabController(
vsync: const TestVSync(),
length: 3,
);
await tester.pumpWidget(buildFrame(tabController));
expect(tabController.index, 0);
expect(indicatorColors(tester), const <Color>[selectedColor, unselectedColor, unselectedColor]);
tabController.animateTo(1, duration: const Duration(milliseconds: 200));
await tester.pump();
// Verify that indicator 0's color is becoming increasingly transparent,
/// and indicator 1's color is becoming increasingly opaque during the
// 200ms animation. Indicator 2 remains transparent throughout.
await tester.pump(const Duration(milliseconds: 10));
List<Color> colors = indicatorColors(tester);
expect(colors[0].alpha, greaterThan(colors[1].alpha));
expect(colors[2], unselectedColor);
await tester.pump(const Duration(milliseconds: 175));
colors = indicatorColors(tester);
expect(colors[0].alpha, lessThan(colors[1].alpha));
expect(colors[2], unselectedColor);
await tester.pumpAndSettle();
expect(tabController.index, 1);
expect(indicatorColors(tester), const <Color>[unselectedColor, selectedColor, unselectedColor]);
tabController.animateTo(2, duration: const Duration(milliseconds: 200));
await tester.pump();
// Same animation test as above for indicators 1 and 2.
await tester.pump(const Duration(milliseconds: 10));
colors = indicatorColors(tester);
expect(colors[1].alpha, greaterThan(colors[2].alpha));
expect(colors[0], unselectedColor);
await tester.pump(const Duration(milliseconds: 175));
colors = indicatorColors(tester);
expect(colors[1].alpha, lessThan(colors[2].alpha));
expect(colors[0], unselectedColor);
await tester.pumpAndSettle();
expect(tabController.index, 2);
expect(indicatorColors(tester), const <Color>[unselectedColor, unselectedColor, selectedColor]);
});
testWidgets('PageSelector responds correctly to TabBarView drags', (WidgetTester tester) async {
final TabController tabController = new TabController(
vsync: const TestVSync(),
initialIndex: 1,
length: 3,
);
await tester.pumpWidget(buildFrame(tabController));
expect(tabController.index, 1);
expect(indicatorColors(tester), const <Color>[unselectedColor, selectedColor, unselectedColor]);
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0));
// Drag to the left moving the selection towards indicator 2. Indicator 2's
// opacity should increase and Indicator 1's opacity should decrease.
await gesture.moveBy(const Offset(-100.0, 0.0));
await tester.pumpAndSettle();
List<Color> colors = indicatorColors(tester);
expect(colors[1].alpha, greaterThan(colors[2].alpha));
expect(colors[0], unselectedColor);
// 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]);
// Drag to the left moving the selection towards indicator 0. Indicator 0's
// opacity should increase and Indicator 1's opacity should decrease.
await gesture.moveBy(const Offset(100.0, 0.0));
await tester.pumpAndSettle();
colors = indicatorColors(tester);
expect(colors[1].alpha, greaterThan(colors[0].alpha));
expect(colors[2], unselectedColor);
// 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]);
// Completing the gesture doesn't change anything
await gesture.up();
await tester.pumpAndSettle();
colors = indicatorColors(tester);
expect(indicatorColors(tester), const <Color>[unselectedColor, selectedColor, unselectedColor]);
// 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]);
// 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]);
});
}
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