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>
|
Felix Schmidt <felix.free@gmx.de>
|
||||||
Artur Rymarz <artur.rymarz@gmail.com>
|
Artur Rymarz <artur.rymarz@gmail.com>
|
||||||
Stefan Mitev <mr.mitew@gmail.com>
|
Stefan Mitev <mr.mitew@gmail.com>
|
||||||
|
Jasper van Riet <jaspervanriet@gmail.com>
|
||||||
Mattijs Fuijkschot <mattijs.fuijkschot@gmail.com>
|
Mattijs Fuijkschot <mattijs.fuijkschot@gmail.com>
|
||||||
|
@ -548,6 +548,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
|
|||||||
this.labelPadding,
|
this.labelPadding,
|
||||||
this.unselectedLabelColor,
|
this.unselectedLabelColor,
|
||||||
this.unselectedLabelStyle,
|
this.unselectedLabelStyle,
|
||||||
|
this.onTap,
|
||||||
}) : assert(tabs != null),
|
}) : assert(tabs != null),
|
||||||
assert(isScrollable != null),
|
assert(isScrollable != null),
|
||||||
assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)),
|
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.
|
/// is null then the text style of the theme's body2 definition is used.
|
||||||
final TextStyle unselectedLabelStyle;
|
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.
|
/// 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.
|
/// [AppBar] uses this this size to compute its own preferred size.
|
||||||
@ -883,6 +895,9 @@ class _TabBarState extends State<TabBar> {
|
|||||||
void _handleTap(int index) {
|
void _handleTap(int index) {
|
||||||
assert(index >= 0 && index < widget.tabs.length);
|
assert(index >= 0 && index < widget.tabs.length);
|
||||||
_controller.animateTo(index);
|
_controller.animateTo(index);
|
||||||
|
if (widget.onTap != null) {
|
||||||
|
widget.onTap(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStyledTab(Widget child, bool selected, Animation<double> animation) {
|
Widget _buildStyledTab(Widget child, bool selected, Animation<double> animation) {
|
||||||
|
@ -1760,6 +1760,87 @@ void main() {
|
|||||||
semantics.dispose();
|
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', () {
|
test('illegal constructor combinations', () {
|
||||||
expect(() => Tab(icon: nonconst(null)), throwsAssertionError);
|
expect(() => Tab(icon: nonconst(null)), throwsAssertionError);
|
||||||
expect(() => Tab(icon: Container(), text: 'foo', child: Container()), throwsAssertionError);
|
expect(() => Tab(icon: Container(), text: 'foo', child: Container()), throwsAssertionError);
|
||||||
|
Loading…
Reference in New Issue
Block a user