mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Allow detection of taps on TabBar (#23919)
This commit is contained in:
parent
a37099f3b1
commit
01694ab62d
1
AUTHORS
1
AUTHORS
@ -29,4 +29,5 @@ Lukasz Piliszczuk <lukasz@intheloup.io>
|
||||
Felix Schmidt <felix.free@gmx.de>
|
||||
Artur Rymarz <artur.rymarz@gmail.com>
|
||||
Stefan Mitev <mr.mitew@gmail.com>
|
||||
Jasper van Riet <jaspervanriet@gmail.com>
|
||||
Mattijs Fuijkschot <mattijs.fuijkschot@gmail.com>
|
||||
|
@ -548,6 +548,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
this.labelPadding,
|
||||
this.unselectedLabelColor,
|
||||
this.unselectedLabelStyle,
|
||||
this.onTap,
|
||||
}) : assert(tabs != null),
|
||||
assert(isScrollable != null),
|
||||
assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)),
|
||||
@ -660,6 +661,17 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
/// is null then the text style of the theme's body2 definition is used.
|
||||
final TextStyle unselectedLabelStyle;
|
||||
|
||||
/// An optional callback that's called when the [TabBar] is tapped.
|
||||
///
|
||||
/// The callback is applied to the index of the tab where the tap occurred.
|
||||
///
|
||||
/// This callback has no effect on the default handling of taps. It's for
|
||||
/// applications that want to do a little extra work when a tab is tapped,
|
||||
/// even if the tap doesn't change the TabController's index. TabBar [onTap]
|
||||
/// callbacks should not make changes to the TabController since that would
|
||||
/// interfere with the default tap handler.
|
||||
final ValueChanged<int> onTap;
|
||||
|
||||
/// A size whose height depends on if the tabs have both icons and text.
|
||||
///
|
||||
/// [AppBar] uses this this size to compute its own preferred size.
|
||||
@ -883,6 +895,9 @@ class _TabBarState extends State<TabBar> {
|
||||
void _handleTap(int index) {
|
||||
assert(index >= 0 && index < widget.tabs.length);
|
||||
_controller.animateTo(index);
|
||||
if (widget.onTap != null) {
|
||||
widget.onTap(index);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildStyledTab(Widget child, bool selected, Animation<double> animation) {
|
||||
|
@ -1760,6 +1760,87 @@ void main() {
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('can be notified of TabBar onTap behavior', (WidgetTester tester) async {
|
||||
int tabIndex = -1;
|
||||
|
||||
Widget buildFrame({
|
||||
TabController controller,
|
||||
List<String> tabs,
|
||||
}) {
|
||||
return boilerplate(
|
||||
child: Container(
|
||||
child: TabBar(
|
||||
controller: controller,
|
||||
tabs: tabs.map<Widget>((String tab) => Tab(text: tab)).toList(),
|
||||
onTap: (int index) {
|
||||
tabIndex = index;
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final List<String> tabs = <String>['A', 'B', 'C'];
|
||||
final TabController controller = TabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
initialIndex: tabs.indexOf('C'),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, controller: controller));
|
||||
expect(find.text('A'), findsOneWidget);
|
||||
expect(find.text('B'), findsOneWidget);
|
||||
expect(find.text('C'), findsOneWidget);
|
||||
expect(controller, isNotNull);
|
||||
expect(controller.index, 2);
|
||||
expect(tabIndex, -1); // no tap so far so tabIndex should reflect that
|
||||
|
||||
// Verify whether the [onTap] notification works when the [TabBar] animates.
|
||||
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, controller: controller));
|
||||
await tester.tap(find.text('B'));
|
||||
await tester.pump();
|
||||
expect(controller.indexIsChanging, true);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.index, 1);
|
||||
expect(controller.previousIndex, 2);
|
||||
expect(controller.indexIsChanging, false);
|
||||
expect(tabIndex, controller.index);
|
||||
|
||||
tabIndex = -1;
|
||||
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, controller: controller));
|
||||
await tester.tap(find.text('C'));
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.index, 2);
|
||||
expect(controller.previousIndex, 1);
|
||||
expect(tabIndex, controller.index);
|
||||
|
||||
tabIndex = -1;
|
||||
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, controller: controller));
|
||||
await tester.tap(find.text('A'));
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.index, 0);
|
||||
expect(controller.previousIndex, 2);
|
||||
expect(tabIndex, controller.index);
|
||||
|
||||
tabIndex = -1;
|
||||
|
||||
// Verify whether [onTap] is called even when the [TabController] does
|
||||
// not change.
|
||||
|
||||
final int currentControllerIndex = controller.index;
|
||||
await tester.pumpWidget(buildFrame(tabs: tabs, controller: controller));
|
||||
await tester.tap(find.text('A'));
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.index, currentControllerIndex); // controller has not changed
|
||||
expect(tabIndex, 0);
|
||||
});
|
||||
|
||||
test('illegal constructor combinations', () {
|
||||
expect(() => Tab(icon: nonconst(null)), throwsAssertionError);
|
||||
expect(() => Tab(icon: Container(), text: 'foo', child: Container()), throwsAssertionError);
|
||||
|
Loading…
Reference in New Issue
Block a user