Fix focus switching when changing tabs in CupertinoTabScaffold (#14431)
* Fix focus switching when changing tabs in CupertinoTabScaffold * review * Test focuses are saved within tabs * review
This commit is contained in:
parent
681192a042
commit
62addedc11
@ -134,7 +134,7 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> stacked = <Widget>[];
|
||||
|
||||
Widget content = new _TabView(
|
||||
Widget content = new _TabSwitchingView(
|
||||
currentTabIndex: _currentPage,
|
||||
tabNumber: widget.tabBar.items.length,
|
||||
tabBuilder: widget.tabBuilder,
|
||||
@ -197,10 +197,10 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An widget laying out multiple tabs with only one active tab being built
|
||||
/// A widget laying out multiple tabs with only one active tab being built
|
||||
/// at a time and on stage. Off stage tabs' animations are stopped.
|
||||
class _TabView extends StatefulWidget {
|
||||
const _TabView({
|
||||
class _TabSwitchingView extends StatefulWidget {
|
||||
const _TabSwitchingView({
|
||||
@required this.currentTabIndex,
|
||||
@required this.tabNumber,
|
||||
@required this.tabBuilder,
|
||||
@ -213,16 +213,45 @@ class _TabView extends StatefulWidget {
|
||||
final IndexedWidgetBuilder tabBuilder;
|
||||
|
||||
@override
|
||||
_TabViewState createState() => new _TabViewState();
|
||||
_TabSwitchingViewState createState() => new _TabSwitchingViewState();
|
||||
}
|
||||
|
||||
class _TabViewState extends State<_TabView> {
|
||||
class _TabSwitchingViewState extends State<_TabSwitchingView> {
|
||||
List<Widget> tabs;
|
||||
List<FocusScopeNode> tabFocusNodes;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
tabs = new List<Widget>(widget.tabNumber);
|
||||
tabFocusNodes = new List<FocusScopeNode>.generate(
|
||||
widget.tabNumber,
|
||||
(int index) => new FocusScopeNode(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_focusActiveTab();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_TabSwitchingView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_focusActiveTab();
|
||||
}
|
||||
|
||||
void _focusActiveTab() {
|
||||
FocusScope.of(context).setFirstFocus(tabFocusNodes[widget.currentTabIndex]);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (FocusScopeNode focusScopeNode in tabFocusNodes) {
|
||||
focusScopeNode.detach();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -232,14 +261,18 @@ class _TabViewState extends State<_TabView> {
|
||||
children: new List<Widget>.generate(widget.tabNumber, (int index) {
|
||||
final bool active = index == widget.currentTabIndex;
|
||||
|
||||
if (active || tabs[index] != null)
|
||||
if (active || tabs[index] != null) {
|
||||
tabs[index] = widget.tabBuilder(context, index);
|
||||
}
|
||||
|
||||
return new Offstage(
|
||||
offstage: !active,
|
||||
child: new TickerMode(
|
||||
enabled: active,
|
||||
child: tabs[index] ?? new Container(),
|
||||
child: new FocusScope(
|
||||
node: tabFocusNodes[index],
|
||||
child: tabs[index] ?? new Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../painting/mocks_for_image_cache.dart';
|
||||
@ -34,7 +35,7 @@ void main() {
|
||||
onPaint: () { tabsPainted.add(index); }
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -88,11 +89,11 @@ void main() {
|
||||
settings: settings,
|
||||
builder: (BuildContext context) {
|
||||
return new CupertinoTabScaffold(
|
||||
tabBar: _buildTabBar(),
|
||||
tabBuilder: (BuildContext context, int index) {
|
||||
tabsBuilt.add(index);
|
||||
return new Text('Page ${index + 1}');
|
||||
}
|
||||
tabBar: _buildTabBar(),
|
||||
tabBuilder: (BuildContext context, int index) {
|
||||
tabsBuilt.add(index);
|
||||
return new Text('Page ${index + 1}');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -119,6 +120,121 @@ void main() {
|
||||
expect(find.text('Page 1'), findsOneWidget);
|
||||
expect(find.text('Page 2', skipOffstage: false), isOffstage);
|
||||
});
|
||||
|
||||
testWidgets('Last tab gets focus', (WidgetTester tester) async {
|
||||
// 2 nodes for 2 tabs
|
||||
final List<FocusNode> focusNodes = <FocusNode>[new FocusNode(), new FocusNode()];
|
||||
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return new CupertinoPageRoute<Null>(
|
||||
settings: settings,
|
||||
builder: (BuildContext context) {
|
||||
return new CupertinoTabScaffold(
|
||||
tabBar: _buildTabBar(),
|
||||
tabBuilder: (BuildContext context, int index) {
|
||||
return new TextField(
|
||||
focusNode: focusNodes[index],
|
||||
autofocus: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(focusNodes[0].hasFocus, isTrue);
|
||||
|
||||
await tester.tap(find.text('Tab 2'));
|
||||
await tester.pump();
|
||||
|
||||
expect(focusNodes[0].hasFocus, isFalse);
|
||||
expect(focusNodes[1].hasFocus, isTrue);
|
||||
|
||||
await tester.tap(find.text('Tab 1'));
|
||||
await tester.pump();
|
||||
|
||||
expect(focusNodes[0].hasFocus, isTrue);
|
||||
expect(focusNodes[1].hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('Do not affect focus order in the route', (WidgetTester tester) async {
|
||||
final List<FocusNode> focusNodes = <FocusNode>[
|
||||
new FocusNode(), new FocusNode(), new FocusNode(), new FocusNode(),
|
||||
];
|
||||
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return new CupertinoPageRoute<Null>(
|
||||
settings: settings,
|
||||
builder: (BuildContext context) {
|
||||
return new Material(
|
||||
child: new CupertinoTabScaffold(
|
||||
tabBar: _buildTabBar(),
|
||||
tabBuilder: (BuildContext context, int index) {
|
||||
return new Column(
|
||||
children: <Widget>[
|
||||
new TextField(
|
||||
focusNode: focusNodes[index * 2],
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'TextField 1',
|
||||
),
|
||||
),
|
||||
new TextField(
|
||||
focusNode: focusNodes[index * 2 + 1],
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'TextField 2',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
focusNodes.any((FocusNode node) => node.hasFocus),
|
||||
isFalse,
|
||||
);
|
||||
|
||||
await tester.tap(find.widgetWithText(TextField, 'TextField 2'));
|
||||
|
||||
expect(
|
||||
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
|
||||
1,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Tab 2'));
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.widgetWithText(TextField, 'TextField 1'));
|
||||
|
||||
expect(
|
||||
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
|
||||
2,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Tab 1'));
|
||||
await tester.pump();
|
||||
|
||||
// Upon going back to tab 1, the item it tab 1 that previously had the focus
|
||||
// (TextField 2) gets it back.
|
||||
expect(
|
||||
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
|
||||
1,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
CupertinoTabBar _buildTabBar() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user