Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
02d5c759
Unverified
Commit
02d5c759
authored
Mar 23, 2023
by
Pierre-Louis
Committed by
GitHub
Mar 23, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add support for secondary tab bar (#122756)
Add support for secondary tab bar
parent
ce68d979
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
801 additions
and
130 deletions
+801
-130
tabs_template.dart
dev/tools/gen_defaults/lib/tabs_template.dart
+61
-2
tab_bar.0.dart
examples/api/lib/material/tabs/tab_bar.0.dart
+9
-11
tab_bar.1.dart
examples/api/lib/material/tabs/tab_bar.1.dart
+18
-15
tab_bar.2.dart
examples/api/lib/material/tabs/tab_bar.2.dart
+117
-0
tab_bar.0_test.dart
examples/api/test/material/tabs/tab_bar.0_test.dart
+51
-0
tab_bar.1_test.dart
examples/api/test/material/tabs/tab_bar.1_test.dart
+51
-0
tab_bar.2_test.dart
examples/api/test/material/tabs/tab_bar.2_test.dart
+71
-0
tabs.dart
packages/flutter/lib/src/material/tabs.dart
+164
-25
tab_bar_theme_test.dart
packages/flutter/test/material/tab_bar_theme_test.dart
+149
-65
tabs_test.dart
packages/flutter/test/material/tabs_test.dart
+110
-12
No files found.
dev/tools/gen_defaults/lib/tabs_template.dart
View file @
02d5c759
...
@@ -12,8 +12,8 @@ class TabsTemplate extends TokenTemplate {
...
@@ -12,8 +12,8 @@ class TabsTemplate extends TokenTemplate {
@override
@override
String
generate
()
=>
'''
String
generate
()
=>
'''
class _
${blockName}
DefaultsM3 extends TabBarTheme {
class _
${blockName}
Primary
DefaultsM3 extends TabBarTheme {
_
${blockName}
DefaultsM3(this.context)
_
${blockName}
Primary
DefaultsM3(this.context)
: super(indicatorSize: TabBarIndicatorSize.label);
: super(indicatorSize: TabBarIndicatorSize.label);
final BuildContext context;
final BuildContext context;
...
@@ -69,5 +69,64 @@ class _${blockName}DefaultsM3 extends TabBarTheme {
...
@@ -69,5 +69,64 @@ class _${blockName}DefaultsM3 extends TabBarTheme {
@override
@override
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
}
}
class _
${blockName}
SecondaryDefaultsM3 extends TabBarTheme {
_
${blockName}
SecondaryDefaultsM3(this.context)
: super(indicatorSize: TabBarIndicatorSize.tab);
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
Color? get dividerColor =>
${componentColor("md.comp.secondary-navigation-tab.divider")}
;
@override
Color? get indicatorColor =>
${componentColor("md.comp.primary-navigation-tab.active-indicator")}
;
@override
Color? get labelColor =>
${componentColor("md.comp.secondary-navigation-tab.active.label-text")}
;
@override
TextStyle? get labelStyle =>
${textStyle("md.comp.secondary-navigation-tab.label-text")}
;
@override
Color? get unselectedLabelColor =>
${componentColor("md.comp.secondary-navigation-tab.inactive.label-text")}
;
@override
TextStyle? get unselectedLabelStyle =>
${textStyle("md.comp.secondary-navigation-tab.label-text")}
;
@override
MaterialStateProperty<Color?> get overlayColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.hovered)) {
return
${componentColor('md.comp.secondary-navigation-tab.hover.state-layer')}
;
}
if (states.contains(MaterialState.focused)) {
return
${componentColor('md.comp.secondary-navigation-tab.focus.state-layer')}
;
}
if (states.contains(MaterialState.pressed)) {
return
${componentColor('md.comp.secondary-navigation-tab.pressed.state-layer')}
;
}
return null;
}
if (states.contains(MaterialState.hovered)) {
return
${componentColor('md.comp.secondary-navigation-tab.hover.state-layer')}
;
}
if (states.contains(MaterialState.focused)) {
return
${componentColor('md.comp.secondary-navigation-tab.focus.state-layer')}
;
}
if (states.contains(MaterialState.pressed)) {
return
${componentColor('md.comp.secondary-navigation-tab.pressed.state-layer')}
;
}
return null;
});
}
@override
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
}
'''
;
'''
;
}
}
examples/api/lib/material/tabs/tab_bar.0.dart
View file @
02d5c759
...
@@ -6,24 +6,22 @@
...
@@ -6,24 +6,22 @@
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
void
main
(
)
=>
runApp
(
const
My
App
());
void
main
(
)
=>
runApp
(
const
TabBar
App
());
class
MyApp
extends
StatelessWidget
{
class
TabBarApp
extends
StatelessWidget
{
const
MyApp
({
super
.
key
});
const
TabBarApp
({
super
.
key
});
static
const
String
_title
=
'Flutter Code Sample'
;
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
const
MaterialApp
(
return
MaterialApp
(
t
itle:
_title
,
t
heme:
ThemeData
(
useMaterial3:
true
)
,
home:
MyStatelessWidget
(),
home:
const
TabBarExample
(),
);
);
}
}
}
}
class
MyStatelessWidget
extends
StatelessWidget
{
class
TabBarExample
extends
StatelessWidget
{
const
MyStatelessWidget
({
super
.
key
});
const
TabBarExample
({
super
.
key
});
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
...
@@ -32,7 +30,7 @@ class MyStatelessWidget extends StatelessWidget {
...
@@ -32,7 +30,7 @@ class MyStatelessWidget extends StatelessWidget {
length:
3
,
length:
3
,
child:
Scaffold
(
child:
Scaffold
(
appBar:
AppBar
(
appBar:
AppBar
(
title:
const
Text
(
'TabBar
Widget
'
),
title:
const
Text
(
'TabBar
Sample
'
),
bottom:
const
TabBar
(
bottom:
const
TabBar
(
tabs:
<
Widget
>[
tabs:
<
Widget
>[
Tab
(
Tab
(
...
...
examples/api/lib/material/tabs/tab_bar.1.dart
View file @
02d5c759
...
@@ -6,34 +6,31 @@
...
@@ -6,34 +6,31 @@
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
void
main
(
)
=>
runApp
(
const
My
App
());
void
main
(
)
=>
runApp
(
const
TabBar
App
());
class
MyApp
extends
StatelessWidget
{
class
TabBarApp
extends
StatelessWidget
{
const
MyApp
({
super
.
key
});
const
TabBarApp
({
super
.
key
});
static
const
String
_title
=
'Flutter Code Sample'
;
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
const
MaterialApp
(
return
MaterialApp
(
t
itle:
_title
,
t
heme:
ThemeData
(
useMaterial3:
true
)
,
home:
MyStatefulWidget
(),
home:
const
TabBarExample
(),
);
);
}
}
}
}
class
MyStatefulWidget
extends
StatefulWidget
{
class
TabBarExample
extends
StatefulWidget
{
const
MyStatefulWidget
({
super
.
key
});
const
TabBarExample
({
super
.
key
});
@override
@override
State
<
MyStatefulWidget
>
createState
()
=>
_MyStatefulWidget
State
();
State
<
TabBarExample
>
createState
()
=>
_TabBarExample
State
();
}
}
/// [AnimationController]s can be created with `vsync: this` because of
/// [AnimationController]s can be created with `vsync: this` because of
/// [TickerProviderStateMixin].
/// [TickerProviderStateMixin].
class
_MyStatefulWidgetState
extends
State
<
MyStatefulWidget
>
class
_TabBarExampleState
extends
State
<
TabBarExample
>
with
TickerProviderStateMixin
{
with
TickerProviderStateMixin
{
late
final
TabController
_tabController
;
late
TabController
_tabController
;
@override
@override
void
initState
()
{
void
initState
()
{
...
@@ -41,11 +38,17 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget>
...
@@ -41,11 +38,17 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget>
_tabController
=
TabController
(
length:
3
,
vsync:
this
);
_tabController
=
TabController
(
length:
3
,
vsync:
this
);
}
}
@override
void
dispose
()
{
_tabController
.
dispose
();
super
.
dispose
();
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
return
Scaffold
(
appBar:
AppBar
(
appBar:
AppBar
(
title:
const
Text
(
'TabBar
Widget
'
),
title:
const
Text
(
'TabBar
Sample
'
),
bottom:
TabBar
(
bottom:
TabBar
(
controller:
_tabController
,
controller:
_tabController
,
tabs:
const
<
Widget
>[
tabs:
const
<
Widget
>[
...
...
examples/api/lib/material/tabs/tab_bar.2.dart
0 → 100644
View file @
02d5c759
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for [TabBar].
import
'package:flutter/material.dart'
;
void
main
(
)
=>
runApp
(
const
TabBarApp
());
class
TabBarApp
extends
StatelessWidget
{
const
TabBarApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
theme:
ThemeData
(
useMaterial3:
true
),
home:
const
TabBarExample
(),
);
}
}
class
TabBarExample
extends
StatelessWidget
{
const
TabBarExample
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
DefaultTabController
(
initialIndex:
1
,
length:
3
,
child:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'Primary and secondary TabBar'
),
bottom:
const
TabBar
(
dividerColor:
Colors
.
transparent
,
tabs:
<
Widget
>[
Tab
(
text:
'Flights'
,
icon:
Icon
(
Icons
.
flight
),
),
Tab
(
text:
'Trips'
,
icon:
Icon
(
Icons
.
luggage
),
),
Tab
(
text:
'Explore'
,
icon:
Icon
(
Icons
.
explore
),
),
],
),
),
body:
const
TabBarView
(
children:
<
Widget
>[
NestedTabBar
(
'Flights'
),
NestedTabBar
(
'Trips'
),
NestedTabBar
(
'Explore'
),
],
),
),
);
}
}
class
NestedTabBar
extends
StatefulWidget
{
const
NestedTabBar
(
this
.
outerTab
,
{
super
.
key
});
final
String
outerTab
;
@override
State
<
NestedTabBar
>
createState
()
=>
_NestedTabBarState
();
}
class
_NestedTabBarState
extends
State
<
NestedTabBar
>
with
TickerProviderStateMixin
{
late
final
TabController
_tabController
;
@override
void
initState
()
{
super
.
initState
();
_tabController
=
TabController
(
length:
2
,
vsync:
this
);
}
@override
void
dispose
()
{
_tabController
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Column
(
children:
<
Widget
>[
TabBar
.
secondary
(
controller:
_tabController
,
tabs:
const
<
Widget
>[
Tab
(
text:
'Overview'
),
Tab
(
text:
'Specifications'
),
],
),
Expanded
(
child:
TabBarView
(
controller:
_tabController
,
children:
<
Widget
>[
Card
(
margin:
const
EdgeInsets
.
all
(
16.0
),
child:
Center
(
child:
Text
(
'
${widget.outerTab}
: Overview tab'
)),
),
Card
(
margin:
const
EdgeInsets
.
all
(
16.0
),
child:
Center
(
child:
Text
(
'
${widget.outerTab}
: Specifications tab'
)),
),
],
),
),
],
);
}
}
examples/api/test/material/tabs/tab_bar.0_test.dart
0 → 100644
View file @
02d5c759
// Copyright 2014 The Flutter 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/material.dart'
;
import
'package:flutter_api_samples/material/tabs/tab_bar.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Switch tabs in the TabBar'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
TabBarApp
(),
);
final
TabBar
tabBar
=
tester
.
widget
<
TabBar
>(
find
.
byType
(
TabBar
));
expect
(
tabBar
.
tabs
.
length
,
3
);
final
Finder
tab1
=
find
.
widgetWithIcon
(
Tab
,
Icons
.
cloud_outlined
);
final
Finder
tab2
=
find
.
widgetWithIcon
(
Tab
,
Icons
.
beach_access_sharp
);
final
Finder
tab3
=
find
.
widgetWithIcon
(
Tab
,
Icons
.
brightness_5_sharp
);
const
String
tabBarViewText1
=
"It's cloudy here"
;
const
String
tabBarViewText2
=
"It's rainy here"
;
const
String
tabBarViewText3
=
"It's sunny here"
;
expect
(
find
.
text
(
tabBarViewText1
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText2
),
findsOneWidget
);
expect
(
find
.
text
(
tabBarViewText3
),
findsNothing
);
await
tester
.
tap
(
tab1
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tabBarViewText1
),
findsOneWidget
);
expect
(
find
.
text
(
tabBarViewText2
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText3
),
findsNothing
);
await
tester
.
tap
(
tab2
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tabBarViewText1
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText2
),
findsOneWidget
);
expect
(
find
.
text
(
tabBarViewText3
),
findsNothing
);
await
tester
.
tap
(
tab3
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tabBarViewText1
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText2
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText3
),
findsOneWidget
);
});
}
examples/api/test/material/tabs/tab_bar.1_test.dart
0 → 100644
View file @
02d5c759
// Copyright 2014 The Flutter 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/material.dart'
;
import
'package:flutter_api_samples/material/tabs/tab_bar.1.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Switch tabs in the TabBar'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
TabBarApp
(),
);
final
TabBar
tabBar
=
tester
.
widget
<
TabBar
>(
find
.
byType
(
TabBar
));
expect
(
tabBar
.
tabs
.
length
,
3
);
final
Finder
tab1
=
find
.
widgetWithIcon
(
Tab
,
Icons
.
cloud_outlined
);
final
Finder
tab2
=
find
.
widgetWithIcon
(
Tab
,
Icons
.
beach_access_sharp
);
final
Finder
tab3
=
find
.
widgetWithIcon
(
Tab
,
Icons
.
brightness_5_sharp
);
const
String
tabBarViewText1
=
"It's cloudy here"
;
const
String
tabBarViewText2
=
"It's rainy here"
;
const
String
tabBarViewText3
=
"It's sunny here"
;
expect
(
find
.
text
(
tabBarViewText1
),
findsOneWidget
);
expect
(
find
.
text
(
tabBarViewText2
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText3
),
findsNothing
);
await
tester
.
tap
(
tab1
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tabBarViewText1
),
findsOneWidget
);
expect
(
find
.
text
(
tabBarViewText2
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText3
),
findsNothing
);
await
tester
.
tap
(
tab2
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tabBarViewText1
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText2
),
findsOneWidget
);
expect
(
find
.
text
(
tabBarViewText3
),
findsNothing
);
await
tester
.
tap
(
tab3
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tabBarViewText1
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText2
),
findsNothing
);
expect
(
find
.
text
(
tabBarViewText3
),
findsOneWidget
);
});
}
examples/api/test/material/tabs/tab_bar.2_test.dart
0 → 100644
View file @
02d5c759
// Copyright 2014 The Flutter 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/material.dart'
;
import
'package:flutter_api_samples/material/tabs/tab_bar.2.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Switch tabs in the TabBar'
,
(
WidgetTester
tester
)
async
{
const
String
primaryTabLabel1
=
'Flights'
;
const
String
primaryTabLabel2
=
'Trips'
;
const
String
primaryTabLabel3
=
'Explore'
;
const
String
secondaryTabLabel1
=
'Overview'
;
const
String
secondaryTabLabel2
=
'Specifications'
;
await
tester
.
pumpWidget
(
const
example
.
TabBarApp
(),
);
final
TabBar
primaryTabBar
=
tester
.
widget
<
TabBar
>(
find
.
byType
(
TabBar
).
last
);
expect
(
primaryTabBar
.
tabs
.
length
,
3
);
final
TabBar
secondaryTabBar
=
tester
.
widget
<
TabBar
>(
find
.
byType
(
TabBar
).
first
);
expect
(
secondaryTabBar
.
tabs
.
length
,
2
);
final
Finder
primaryTab1
=
find
.
widgetWithText
(
Tab
,
primaryTabLabel1
);
final
Finder
primaryTab2
=
find
.
widgetWithText
(
Tab
,
primaryTabLabel2
);
final
Finder
primaryTab3
=
find
.
widgetWithText
(
Tab
,
primaryTabLabel3
);
final
Finder
secondaryTab2
=
find
.
widgetWithText
(
Tab
,
secondaryTabLabel2
);
String
tabBarViewText
=
'
$primaryTabLabel2
:
$secondaryTabLabel1
tab'
;
expect
(
find
.
text
(
tabBarViewText
),
findsOneWidget
);
await
tester
.
tap
(
primaryTab1
);
await
tester
.
pumpAndSettle
();
tabBarViewText
=
'
$primaryTabLabel1
:
$secondaryTabLabel1
tab'
;
expect
(
find
.
text
(
tabBarViewText
),
findsOneWidget
);
await
tester
.
tap
(
secondaryTab2
);
await
tester
.
pumpAndSettle
();
tabBarViewText
=
'
$primaryTabLabel1
:
$secondaryTabLabel2
tab'
;
expect
(
find
.
text
(
tabBarViewText
),
findsOneWidget
);
await
tester
.
tap
(
primaryTab2
);
await
tester
.
pumpAndSettle
();
tabBarViewText
=
'
$primaryTabLabel2
:
$secondaryTabLabel1
tab'
;
expect
(
find
.
text
(
tabBarViewText
),
findsOneWidget
);
await
tester
.
tap
(
secondaryTab2
);
await
tester
.
pumpAndSettle
();
tabBarViewText
=
'
$primaryTabLabel2
:
$secondaryTabLabel2
tab'
;
expect
(
find
.
text
(
tabBarViewText
),
findsOneWidget
);
await
tester
.
tap
(
primaryTab3
);
await
tester
.
pumpAndSettle
();
tabBarViewText
=
'
$primaryTabLabel3
:
$secondaryTabLabel1
tab'
;
expect
(
find
.
text
(
tabBarViewText
),
findsOneWidget
);
await
tester
.
tap
(
secondaryTab2
);
await
tester
.
pumpAndSettle
();
tabBarViewText
=
'
$primaryTabLabel3
:
$secondaryTabLabel2
tab'
;
expect
(
find
.
text
(
tabBarViewText
),
findsOneWidget
);
});
}
packages/flutter/lib/src/material/tabs.dart
View file @
02d5c759
...
@@ -167,24 +167,27 @@ class _TabStyle extends AnimatedWidget {
...
@@ -167,24 +167,27 @@ class _TabStyle extends AnimatedWidget {
const
_TabStyle
({
const
_TabStyle
({
required
Animation
<
double
>
animation
,
required
Animation
<
double
>
animation
,
required
this
.
isSelected
,
required
this
.
isSelected
,
required
this
.
isPrimary
,
required
this
.
labelColor
,
required
this
.
labelColor
,
required
this
.
unselectedLabelColor
,
required
this
.
unselectedLabelColor
,
required
this
.
labelStyle
,
required
this
.
labelStyle
,
required
this
.
unselectedLabelStyle
,
required
this
.
unselectedLabelStyle
,
required
this
.
defaults
,
required
this
.
child
,
required
this
.
child
,
})
:
super
(
listenable:
animation
);
})
:
super
(
listenable:
animation
);
final
TextStyle
?
labelStyle
;
final
TextStyle
?
labelStyle
;
final
TextStyle
?
unselectedLabelStyle
;
final
TextStyle
?
unselectedLabelStyle
;
final
bool
isSelected
;
final
bool
isSelected
;
final
bool
isPrimary
;
final
Color
?
labelColor
;
final
Color
?
labelColor
;
final
Color
?
unselectedLabelColor
;
final
Color
?
unselectedLabelColor
;
final
TabBarTheme
defaults
;
final
Widget
child
;
final
Widget
child
;
MaterialStateColor
_resolveWithLabelColor
(
BuildContext
context
)
{
MaterialStateColor
_resolveWithLabelColor
(
BuildContext
context
)
{
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
defaults
=
themeData
.
useMaterial3
?
_TabsDefaultsM3
(
context
)
:
_TabsDefaultsM2
(
context
);
final
Animation
<
double
>
animation
=
listenable
as
Animation
<
double
>;
final
Animation
<
double
>
animation
=
listenable
as
Animation
<
double
>;
// labelStyle.color (and tabBarTheme.labelStyle.color) is not considered
// labelStyle.color (and tabBarTheme.labelStyle.color) is not considered
...
@@ -219,9 +222,7 @@ class _TabStyle extends AnimatedWidget {
...
@@ -219,9 +222,7 @@ class _TabStyle extends AnimatedWidget {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
defaults
=
themeData
.
useMaterial3
?
_TabsDefaultsM3
(
context
)
:
_TabsDefaultsM2
(
context
);
final
Animation
<
double
>
animation
=
listenable
as
Animation
<
double
>;
final
Animation
<
double
>
animation
=
listenable
as
Animation
<
double
>;
final
Set
<
MaterialState
>
states
=
isSelected
final
Set
<
MaterialState
>
states
=
isSelected
...
@@ -604,7 +605,10 @@ class _TabBarScrollController extends ScrollController {
...
@@ -604,7 +605,10 @@ class _TabBarScrollController extends ScrollController {
}
}
}
}
/// A Material Design widget that displays a horizontal row of tabs.
/// A Material Design primary tab bar.
///
/// Primary tabs are placed at the top of the content pane under a top app bar.
/// They display the main content destinations.
///
///
/// Typically created as the [AppBar.bottom] part of an [AppBar] and in
/// Typically created as the [AppBar.bottom] part of an [AppBar] and in
/// conjunction with a [TabBarView].
/// conjunction with a [TabBarView].
...
@@ -635,12 +639,23 @@ class _TabBarScrollController extends ScrollController {
...
@@ -635,12 +639,23 @@ class _TabBarScrollController extends ScrollController {
/// ** See code in examples/api/lib/material/tabs/tab_bar.1.dart **
/// ** See code in examples/api/lib/material/tabs/tab_bar.1.dart **
/// {@end-tool}
/// {@end-tool}
///
///
/// {@tool dartpad}
/// This sample showcases nested Material 3 [TabBar]s. It consists of a primary
/// [TabBar] with nested a secondary [TabBar]. The primary [TabBar] uses a
/// [DefaultTabController] while the secondary [TabBar] uses a [TabController].
///
/// ** See code in examples/api/lib/material/tabs/tab_bar.2.dart **
/// {@end-tool}
///
/// See also:
/// See also:
///
///
/// * [TabBar.secondary], for a secondary tab bar.
/// * [TabBarView], which displays page views that correspond to each tab.
/// * [TabBarView], which displays page views that correspond to each tab.
/// * [TabController], which coordinates tab selection between a [TabBar] and a [TabBarView].
/// * [TabController], which coordinates tab selection between a [TabBar] and a [TabBarView].
/// * https://m3.material.io/components/tab-bar/overview, the Material 3
/// tab bar specification.
class
TabBar
extends
StatefulWidget
implements
PreferredSizeWidget
{
class
TabBar
extends
StatefulWidget
implements
PreferredSizeWidget
{
/// Creates a Material Design tab bar.
/// Creates a Material Design
primary
tab bar.
///
///
/// The [tabs] argument must not be null and its length must match the [controller]'s
/// The [tabs] argument must not be null and its length must match the [controller]'s
/// [TabController.length].
/// [TabController.length].
...
@@ -680,7 +695,57 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
...
@@ -680,7 +695,57 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
this
.
physics
,
this
.
physics
,
this
.
splashFactory
,
this
.
splashFactory
,
this
.
splashBorderRadius
,
this
.
splashBorderRadius
,
})
:
assert
(
indicator
!=
null
||
(
indicatorWeight
>
0.0
));
})
:
_isPrimary
=
true
,
assert
(
indicator
!=
null
||
(
indicatorWeight
>
0.0
));
/// Creates a Material Design secondary tab bar.
///
/// Secondary tabs are used within a content area to further separate related
/// content and establish hierarchy.
///
/// {@tool dartpad}
/// This sample showcases nested Material 3 [TabBar]s. It consists of a primary
/// [TabBar] with nested a secondary [TabBar]. The primary [TabBar] uses a
/// [DefaultTabController] while the secondary [TabBar] uses a [TabController].
///
/// ** See code in examples/api/lib/material/tabs/tab_bar.2.dart **
/// {@end-tool}
///
/// See also:
///
/// * [TabBar], for a primary tab bar.
/// * [TabBarView], which displays page views that correspond to each tab.
/// * [TabController], which coordinates tab selection between a [TabBar] and a [TabBarView].
/// * https://m3.material.io/components/tab-bar/overview, the Material 3
/// tab bar specification.
const
TabBar
.
secondary
({
super
.
key
,
required
this
.
tabs
,
this
.
controller
,
this
.
isScrollable
=
false
,
this
.
padding
,
this
.
indicatorColor
,
this
.
automaticIndicatorColorAdjustment
=
true
,
this
.
indicatorWeight
=
2.0
,
this
.
indicatorPadding
=
EdgeInsets
.
zero
,
this
.
indicator
,
this
.
indicatorSize
,
this
.
dividerColor
,
this
.
labelColor
,
this
.
labelStyle
,
this
.
labelPadding
,
this
.
unselectedLabelColor
,
this
.
unselectedLabelStyle
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
overlayColor
,
this
.
mouseCursor
,
this
.
enableFeedback
,
this
.
onTap
,
this
.
physics
,
this
.
splashFactory
,
this
.
splashBorderRadius
,
})
:
_isPrimary
=
false
,
assert
(
indicator
!=
null
||
(
indicatorWeight
>
0.0
));
/// Typically a list of two or more [Tab] widgets.
/// Typically a list of two or more [Tab] widgets.
///
///
...
@@ -993,6 +1058,11 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
...
@@ -993,6 +1058,11 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
return
false
;
return
false
;
}
}
/// Whether this tab bar is a primary tab bar.
///
/// Otherwise, it is a secondary tab bar.
final
bool
_isPrimary
;
@override
@override
State
<
TabBar
>
createState
()
=>
_TabBarState
();
State
<
TabBar
>
createState
()
=>
_TabBarState
();
}
}
...
@@ -1016,10 +1086,19 @@ class _TabBarState extends State<TabBar> {
...
@@ -1016,10 +1086,19 @@ class _TabBarState extends State<TabBar> {
_labelPaddings
=
List
<
EdgeInsetsGeometry
>.
filled
(
widget
.
tabs
.
length
,
EdgeInsets
.
zero
,
growable:
true
);
_labelPaddings
=
List
<
EdgeInsetsGeometry
>.
filled
(
widget
.
tabs
.
length
,
EdgeInsets
.
zero
,
growable:
true
);
}
}
TabBarTheme
get
_defaults
{
if
(
Theme
.
of
(
context
).
useMaterial3
)
{
return
widget
.
_isPrimary
?
_TabsPrimaryDefaultsM3
(
context
)
:
_TabsSecondaryDefaultsM3
(
context
);
}
else
{
return
_TabsDefaultsM2
(
context
);
}
}
Decoration
_getIndicator
()
{
Decoration
_getIndicator
()
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
defaults
=
theme
.
useMaterial3
?
_TabsDefaultsM3
(
context
)
:
_TabsDefaultsM2
(
context
);
if
(
widget
.
indicator
!=
null
)
{
if
(
widget
.
indicator
!=
null
)
{
return
widget
.
indicator
!;
return
widget
.
indicator
!;
...
@@ -1030,7 +1109,7 @@ class _TabBarState extends State<TabBar> {
...
@@ -1030,7 +1109,7 @@ class _TabBarState extends State<TabBar> {
Color
color
=
widget
.
indicatorColor
Color
color
=
widget
.
indicatorColor
??
(
theme
.
useMaterial3
??
(
theme
.
useMaterial3
?
tabBarTheme
.
indicatorColor
??
defaults
.
indicatorColor
!
?
tabBarTheme
.
indicatorColor
??
_
defaults
.
indicatorColor
!
:
Theme
.
of
(
context
).
indicatorColor
);
:
Theme
.
of
(
context
).
indicatorColor
);
// ThemeData tries to avoid this by having indicatorColor avoid being the
// ThemeData tries to avoid this by having indicatorColor avoid being the
// primaryColor. However, it's possible that the tab bar is on a
// primaryColor. However, it's possible that the tab bar is on a
...
@@ -1046,12 +1125,13 @@ class _TabBarState extends State<TabBar> {
...
@@ -1046,12 +1125,13 @@ class _TabBarState extends State<TabBar> {
// TODO(xu-baolin): Remove automatic adjustment to white color indicator
// TODO(xu-baolin): Remove automatic adjustment to white color indicator
// with a better long-term solution.
// with a better long-term solution.
// https://github.com/flutter/flutter/pull/68171#pullrequestreview-517753917
// https://github.com/flutter/flutter/pull/68171#pullrequestreview-517753917
if
(
widget
.
automaticIndicatorColorAdjustment
&&
color
.
value
==
Material
.
maybeOf
(
context
)?.
color
?.
value
)
{
if
(
widget
.
automaticIndicatorColorAdjustment
&&
color
.
value
==
Material
.
maybeOf
(
context
)?.
color
?.
value
)
{
color
=
Colors
.
white
;
color
=
Colors
.
white
;
}
}
return
UnderlineTabIndicator
(
return
UnderlineTabIndicator
(
borderRadius:
theme
.
useMaterial3
borderRadius:
theme
.
useMaterial3
&&
widget
.
_isPrimary
// TODO(tahatesser): Make sure this value matches Material 3 Tabs spec
// TODO(tahatesser): Make sure this value matches Material 3 Tabs spec
// when `preferredSize`and `indicatorWeight` are updated to support Material 3
// when `preferredSize`and `indicatorWeight` are updated to support Material 3
// https://m3.material.io/components/tabs/specs#149a189f-9039-4195-99da-15c205d20e30,
// https://m3.material.io/components/tabs/specs#149a189f-9039-4195-99da-15c205d20e30,
...
@@ -1107,16 +1187,15 @@ class _TabBarState extends State<TabBar> {
...
@@ -1107,16 +1187,15 @@ class _TabBarState extends State<TabBar> {
void
_initIndicatorPainter
()
{
void
_initIndicatorPainter
()
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
defaults
=
theme
.
useMaterial3
?
_TabsDefaultsM3
(
context
)
:
_TabsDefaultsM2
(
context
);
_indicatorPainter
=
!
_controllerIsValid
?
null
:
_IndicatorPainter
(
_indicatorPainter
=
!
_controllerIsValid
?
null
:
_IndicatorPainter
(
controller:
_controller
!,
controller:
_controller
!,
indicator:
_getIndicator
(),
indicator:
_getIndicator
(),
indicatorSize:
widget
.
indicatorSize
??
tabBarTheme
.
indicatorSize
??
defaults
.
indicatorSize
!,
indicatorSize:
widget
.
indicatorSize
??
tabBarTheme
.
indicatorSize
??
_
defaults
.
indicatorSize
!,
indicatorPadding:
widget
.
indicatorPadding
,
indicatorPadding:
widget
.
indicatorPadding
,
tabKeys:
_tabKeys
,
tabKeys:
_tabKeys
,
old:
_indicatorPainter
,
old:
_indicatorPainter
,
dividerColor:
theme
.
useMaterial3
?
widget
.
dividerColor
??
tabBarTheme
.
dividerColor
??
defaults
.
dividerColor
:
null
,
dividerColor:
theme
.
useMaterial3
?
widget
.
dividerColor
??
tabBarTheme
.
dividerColor
??
_
defaults
.
dividerColor
:
null
,
labelPaddings:
_labelPaddings
,
labelPaddings:
_labelPaddings
,
);
);
}
}
...
@@ -1262,14 +1341,16 @@ class _TabBarState extends State<TabBar> {
...
@@ -1262,14 +1341,16 @@ class _TabBarState extends State<TabBar> {
widget
.
onTap
?.
call
(
index
);
widget
.
onTap
?.
call
(
index
);
}
}
Widget
_buildStyledTab
(
Widget
child
,
bool
isSelected
,
Animation
<
double
>
animation
)
{
Widget
_buildStyledTab
(
Widget
child
,
bool
isSelected
,
Animation
<
double
>
animation
,
TabBarTheme
defaults
)
{
return
_TabStyle
(
return
_TabStyle
(
animation:
animation
,
animation:
animation
,
isSelected:
isSelected
,
isSelected:
isSelected
,
isPrimary:
widget
.
_isPrimary
,
labelColor:
widget
.
labelColor
,
labelColor:
widget
.
labelColor
,
unselectedLabelColor:
widget
.
unselectedLabelColor
,
unselectedLabelColor:
widget
.
unselectedLabelColor
,
labelStyle:
widget
.
labelStyle
,
labelStyle:
widget
.
labelStyle
,
unselectedLabelStyle:
widget
.
unselectedLabelStyle
,
unselectedLabelStyle:
widget
.
unselectedLabelStyle
,
defaults:
defaults
,
child:
child
,
child:
child
,
);
);
}
}
...
@@ -1309,9 +1390,7 @@ class _TabBarState extends State<TabBar> {
...
@@ -1309,9 +1390,7 @@ class _TabBarState extends State<TabBar> {
);
);
}
}
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
tabBarTheme
=
TabBarTheme
.
of
(
context
);
final
TabBarTheme
defaults
=
theme
.
useMaterial3
?
_TabsDefaultsM3
(
context
)
:
_TabsDefaultsM2
(
context
);
final
List
<
Widget
>
wrappedTabs
=
List
<
Widget
>.
generate
(
widget
.
tabs
.
length
,
(
int
index
)
{
final
List
<
Widget
>
wrappedTabs
=
List
<
Widget
>.
generate
(
widget
.
tabs
.
length
,
(
int
index
)
{
const
double
verticalAdjustment
=
(
_kTextAndIconTabHeight
-
_kTabHeight
)/
2.0
;
const
double
verticalAdjustment
=
(
_kTextAndIconTabHeight
-
_kTabHeight
)/
2.0
;
...
@@ -1353,22 +1432,22 @@ class _TabBarState extends State<TabBar> {
...
@@ -1353,22 +1432,22 @@ class _TabBarState extends State<TabBar> {
// The user tapped on a tab, the tab controller's animation is running.
// The user tapped on a tab, the tab controller's animation is running.
assert
(
_currentIndex
!=
previousIndex
);
assert
(
_currentIndex
!=
previousIndex
);
final
Animation
<
double
>
animation
=
_ChangeAnimation
(
_controller
!);
final
Animation
<
double
>
animation
=
_ChangeAnimation
(
_controller
!);
wrappedTabs
[
_currentIndex
!]
=
_buildStyledTab
(
wrappedTabs
[
_currentIndex
!],
true
,
animation
);
wrappedTabs
[
_currentIndex
!]
=
_buildStyledTab
(
wrappedTabs
[
_currentIndex
!],
true
,
animation
,
_defaults
);
wrappedTabs
[
previousIndex
]
=
_buildStyledTab
(
wrappedTabs
[
previousIndex
],
false
,
animation
);
wrappedTabs
[
previousIndex
]
=
_buildStyledTab
(
wrappedTabs
[
previousIndex
],
false
,
animation
,
_defaults
);
}
else
{
}
else
{
// The user is dragging the TabBarView's PageView left or right.
// The user is dragging the TabBarView's PageView left or right.
final
int
tabIndex
=
_currentIndex
!;
final
int
tabIndex
=
_currentIndex
!;
final
Animation
<
double
>
centerAnimation
=
_DragAnimation
(
_controller
!,
tabIndex
);
final
Animation
<
double
>
centerAnimation
=
_DragAnimation
(
_controller
!,
tabIndex
);
wrappedTabs
[
tabIndex
]
=
_buildStyledTab
(
wrappedTabs
[
tabIndex
],
true
,
centerAnimation
);
wrappedTabs
[
tabIndex
]
=
_buildStyledTab
(
wrappedTabs
[
tabIndex
],
true
,
centerAnimation
,
_defaults
);
if
(
_currentIndex
!
>
0
)
{
if
(
_currentIndex
!
>
0
)
{
final
int
tabIndex
=
_currentIndex
!
-
1
;
final
int
tabIndex
=
_currentIndex
!
-
1
;
final
Animation
<
double
>
previousAnimation
=
ReverseAnimation
(
_DragAnimation
(
_controller
!,
tabIndex
));
final
Animation
<
double
>
previousAnimation
=
ReverseAnimation
(
_DragAnimation
(
_controller
!,
tabIndex
));
wrappedTabs
[
tabIndex
]
=
_buildStyledTab
(
wrappedTabs
[
tabIndex
],
false
,
previousAnimation
);
wrappedTabs
[
tabIndex
]
=
_buildStyledTab
(
wrappedTabs
[
tabIndex
],
false
,
previousAnimation
,
_defaults
);
}
}
if
(
_currentIndex
!
<
widget
.
tabs
.
length
-
1
)
{
if
(
_currentIndex
!
<
widget
.
tabs
.
length
-
1
)
{
final
int
tabIndex
=
_currentIndex
!
+
1
;
final
int
tabIndex
=
_currentIndex
!
+
1
;
final
Animation
<
double
>
nextAnimation
=
ReverseAnimation
(
_DragAnimation
(
_controller
!,
tabIndex
));
final
Animation
<
double
>
nextAnimation
=
ReverseAnimation
(
_DragAnimation
(
_controller
!,
tabIndex
));
wrappedTabs
[
tabIndex
]
=
_buildStyledTab
(
wrappedTabs
[
tabIndex
],
false
,
nextAnimation
);
wrappedTabs
[
tabIndex
]
=
_buildStyledTab
(
wrappedTabs
[
tabIndex
],
false
,
nextAnimation
,
_defaults
);
}
}
}
}
}
}
...
@@ -1389,7 +1468,7 @@ class _TabBarState extends State<TabBar> {
...
@@ -1389,7 +1468,7 @@ class _TabBarState extends State<TabBar> {
final
MaterialStateProperty
<
Color
?>
defaultOverlay
=
MaterialStateProperty
.
resolveWith
<
Color
?>(
final
MaterialStateProperty
<
Color
?>
defaultOverlay
=
MaterialStateProperty
.
resolveWith
<
Color
?>(
(
Set
<
MaterialState
>
states
)
{
(
Set
<
MaterialState
>
states
)
{
final
Set
<
MaterialState
>
effectiveStates
=
selectedState
..
addAll
(
states
);
final
Set
<
MaterialState
>
effectiveStates
=
selectedState
..
addAll
(
states
);
return
defaults
.
overlayColor
?.
resolve
(
effectiveStates
);
return
_
defaults
.
overlayColor
?.
resolve
(
effectiveStates
);
},
},
);
);
wrappedTabs
[
index
]
=
InkWell
(
wrappedTabs
[
index
]
=
InkWell
(
...
@@ -1397,7 +1476,7 @@ class _TabBarState extends State<TabBar> {
...
@@ -1397,7 +1476,7 @@ class _TabBarState extends State<TabBar> {
onTap:
()
{
_handleTap
(
index
);
},
onTap:
()
{
_handleTap
(
index
);
},
enableFeedback:
widget
.
enableFeedback
??
true
,
enableFeedback:
widget
.
enableFeedback
??
true
,
overlayColor:
widget
.
overlayColor
??
tabBarTheme
.
overlayColor
??
defaultOverlay
,
overlayColor:
widget
.
overlayColor
??
tabBarTheme
.
overlayColor
??
defaultOverlay
,
splashFactory:
widget
.
splashFactory
??
tabBarTheme
.
splashFactory
??
defaults
.
splashFactory
,
splashFactory:
widget
.
splashFactory
??
tabBarTheme
.
splashFactory
??
_
defaults
.
splashFactory
,
borderRadius:
widget
.
splashBorderRadius
,
borderRadius:
widget
.
splashBorderRadius
,
child:
Padding
(
child:
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
widget
.
indicatorWeight
),
padding:
EdgeInsets
.
only
(
bottom:
widget
.
indicatorWeight
),
...
@@ -1422,10 +1501,12 @@ class _TabBarState extends State<TabBar> {
...
@@ -1422,10 +1501,12 @@ class _TabBarState extends State<TabBar> {
child:
_TabStyle
(
child:
_TabStyle
(
animation:
kAlwaysDismissedAnimation
,
animation:
kAlwaysDismissedAnimation
,
isSelected:
false
,
isSelected:
false
,
isPrimary:
widget
.
_isPrimary
,
labelColor:
widget
.
labelColor
,
labelColor:
widget
.
labelColor
,
unselectedLabelColor:
widget
.
unselectedLabelColor
,
unselectedLabelColor:
widget
.
unselectedLabelColor
,
labelStyle:
widget
.
labelStyle
,
labelStyle:
widget
.
labelStyle
,
unselectedLabelStyle:
widget
.
unselectedLabelStyle
,
unselectedLabelStyle:
widget
.
unselectedLabelStyle
,
defaults:
_defaults
,
child:
_TabLabelBar
(
child:
_TabLabelBar
(
onPerformLayout:
_saveTabOffsets
,
onPerformLayout:
_saveTabOffsets
,
children:
wrappedTabs
,
children:
wrappedTabs
,
...
@@ -1979,8 +2060,8 @@ class _TabsDefaultsM2 extends TabBarTheme {
...
@@ -1979,8 +2060,8 @@ class _TabsDefaultsM2 extends TabBarTheme {
// Token database version: v0_162
// Token database version: v0_162
class
_TabsDefaultsM3
extends
TabBarTheme
{
class
_Tabs
Primary
DefaultsM3
extends
TabBarTheme
{
_TabsDefaultsM3
(
this
.
context
)
_Tabs
Primary
DefaultsM3
(
this
.
context
)
:
super
(
indicatorSize:
TabBarIndicatorSize
.
label
);
:
super
(
indicatorSize:
TabBarIndicatorSize
.
label
);
final
BuildContext
context
;
final
BuildContext
context
;
...
@@ -2037,4 +2118,62 @@ class _TabsDefaultsM3 extends TabBarTheme {
...
@@ -2037,4 +2118,62 @@ class _TabsDefaultsM3 extends TabBarTheme {
InteractiveInkFeatureFactory
?
get
splashFactory
=>
Theme
.
of
(
context
).
splashFactory
;
InteractiveInkFeatureFactory
?
get
splashFactory
=>
Theme
.
of
(
context
).
splashFactory
;
}
}
class
_TabsSecondaryDefaultsM3
extends
TabBarTheme
{
_TabsSecondaryDefaultsM3
(
this
.
context
)
:
super
(
indicatorSize:
TabBarIndicatorSize
.
tab
);
final
BuildContext
context
;
late
final
ColorScheme
_colors
=
Theme
.
of
(
context
).
colorScheme
;
late
final
TextTheme
_textTheme
=
Theme
.
of
(
context
).
textTheme
;
@override
Color
?
get
dividerColor
=>
_colors
.
surfaceVariant
;
@override
Color
?
get
indicatorColor
=>
_colors
.
primary
;
@override
Color
?
get
labelColor
=>
_colors
.
onSurface
;
@override
TextStyle
?
get
labelStyle
=>
_textTheme
.
titleSmall
;
@override
Color
?
get
unselectedLabelColor
=>
_colors
.
onSurfaceVariant
;
@override
TextStyle
?
get
unselectedLabelStyle
=>
_textTheme
.
titleSmall
;
@override
MaterialStateProperty
<
Color
?>
get
overlayColor
{
return
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
selected
))
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
return
null
;
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
return
null
;
});
}
@override
InteractiveInkFeatureFactory
?
get
splashFactory
=>
Theme
.
of
(
context
).
splashFactory
;
}
// END GENERATED TOKEN PROPERTIES - Tabs
// END GENERATED TOKEN PROPERTIES - Tabs
packages/flutter/test/material/tab_bar_theme_test.dart
View file @
02d5c759
...
@@ -31,14 +31,30 @@ final List<SizedBox> _sizedTabs = <SizedBox>[
...
@@ -31,14 +31,30 @@ final List<SizedBox> _sizedTabs = <SizedBox>[
SizedBox
(
key:
UniqueKey
(),
width:
100.0
,
height:
50.0
),
SizedBox
(
key:
UniqueKey
(),
width:
100.0
,
height:
50.0
),
];
];
Widget
_withTheme
(
Widget
buildTabBar
(
{
TabBarTheme
?
theme
,
{
TabBarTheme
?
tabBarTheme
,
bool
secondaryTabBar
=
false
,
List
<
Widget
>
tabs
=
_tabs
,
List
<
Widget
>
tabs
=
_tabs
,
bool
isScrollable
=
false
,
bool
isScrollable
=
false
,
bool
useMaterial3
=
false
,
bool
useMaterial3
=
false
,
})
{
})
{
if
(
secondaryTabBar
)
{
return
MaterialApp
(
return
MaterialApp
(
theme:
ThemeData
(
tabBarTheme:
theme
,
useMaterial3:
useMaterial3
),
theme:
ThemeData
(
tabBarTheme:
tabBarTheme
,
useMaterial3:
useMaterial3
),
home:
Scaffold
(
body:
RepaintBoundary
(
key:
_painterKey
,
child:
TabBar
.
secondary
(
tabs:
tabs
,
isScrollable:
isScrollable
,
controller:
TabController
(
length:
tabs
.
length
,
vsync:
const
TestVSync
()),
),
),
),
);
}
return
MaterialApp
(
theme:
ThemeData
(
tabBarTheme:
tabBarTheme
,
useMaterial3:
useMaterial3
),
home:
Scaffold
(
home:
Scaffold
(
body:
RepaintBoundary
(
body:
RepaintBoundary
(
key:
_painterKey
,
key:
_painterKey
,
...
@@ -52,12 +68,17 @@ Widget _withTheme(
...
@@ -52,12 +68,17 @@ Widget _withTheme(
);
);
}
}
RenderParagraph
_iconRenderObject
(
WidgetTester
tester
,
IconData
icon
)
{
RenderParagraph
_getIcon
(
WidgetTester
tester
,
IconData
icon
)
{
return
tester
.
renderObject
<
RenderParagraph
>(
return
tester
.
renderObject
<
RenderParagraph
>(
find
.
descendant
(
of:
find
.
byIcon
(
icon
),
matching:
find
.
byType
(
RichText
)),
find
.
descendant
(
of:
find
.
byIcon
(
icon
),
matching:
find
.
byType
(
RichText
)),
);
);
}
}
RenderParagraph
_getText
(
WidgetTester
tester
,
String
text
)
{
return
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
text
));
}
void
main
(
)
{
void
main
(
)
{
test
(
'TabBarTheme copyWith, ==, hashCode, defaults'
,
()
{
test
(
'TabBarTheme copyWith, ==, hashCode, defaults'
,
()
{
expect
(
const
TabBarTheme
(),
const
TabBarTheme
().
copyWith
());
expect
(
const
TabBarTheme
(),
const
TabBarTheme
().
copyWith
());
...
@@ -82,60 +103,113 @@ void main() {
...
@@ -82,60 +103,113 @@ void main() {
expect
(
identical
(
TabBarTheme
.
lerp
(
theme
,
theme
,
0.5
),
theme
),
true
);
expect
(
identical
(
TabBarTheme
.
lerp
(
theme
,
theme
,
0.5
),
theme
),
true
);
});
});
testWidgets
(
'Tab bar defaults'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar defaults
(primary)
'
,
(
WidgetTester
tester
)
async
{
//
tests for the default label color and label styles when tabBarTheme and tabBar do not provide any
//
Test default label color and label styles.
await
tester
.
pumpWidget
(
_withTheme
(
null
,
useMaterial3:
true
));
await
tester
.
pumpWidget
(
buildTabBar
(
useMaterial3:
true
));
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
RenderParagraph
selected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab1Text
)
);
final
RenderParagraph
selected
Label
=
_getText
(
tester
,
_tab1Text
);
expect
(
selected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
theme
.
textTheme
.
titleSmall
!.
fontFamily
));
expect
(
selected
Label
.
text
.
style
!.
fontFamily
,
equals
(
theme
.
textTheme
.
titleSmall
!.
fontFamily
));
expect
(
selected
RenderObject
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
selected
Label
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
selected
RenderObject
.
text
.
style
!.
color
,
equals
(
theme
.
colorScheme
.
primary
));
expect
(
selected
Label
.
text
.
style
!.
color
,
equals
(
theme
.
colorScheme
.
primary
));
final
RenderParagraph
unselected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab2Text
)
);
final
RenderParagraph
unselected
Label
=
_getText
(
tester
,
_tab2Text
);
expect
(
unselected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
theme
.
textTheme
.
titleSmall
!.
fontFamily
));
expect
(
unselected
Label
.
text
.
style
!.
fontFamily
,
equals
(
theme
.
textTheme
.
titleSmall
!.
fontFamily
));
expect
(
unselected
RenderObject
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
unselected
Label
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
unselected
RenderObject
.
text
.
style
!.
color
,
equals
(
theme
.
colorScheme
.
onSurfaceVariant
));
expect
(
unselected
Label
.
text
.
style
!.
color
,
equals
(
theme
.
colorScheme
.
onSurfaceVariant
));
//
tests for the default value of labelPadding when tabBarTheme and tabBar do not provide one
//
Test default labelPadding.
await
tester
.
pumpWidget
(
_withTheme
(
null
,
tabs:
_sizedTabs
,
isScrollable:
true
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabs:
_sizedTabs
,
isScrollable:
true
));
const
double
indicatorWeight
=
2.0
;
const
double
indicatorWeight
=
2.0
;
final
Rect
tabBar
=
tester
.
getRect
(
find
.
byType
(
TabBar
));
final
Rect
tabBar
=
tester
.
getRect
(
find
.
byType
(
TabBar
));
final
Rect
tabOneRect
=
tester
.
getRect
(
find
.
byKey
(
_sizedTabs
[
0
].
key
!));
final
Rect
tabOneRect
=
tester
.
getRect
(
find
.
byKey
(
_sizedTabs
[
0
].
key
!));
final
Rect
tabTwoRect
=
tester
.
getRect
(
find
.
byKey
(
_sizedTabs
[
1
].
key
!));
final
Rect
tabTwoRect
=
tester
.
getRect
(
find
.
byKey
(
_sizedTabs
[
1
].
key
!));
//
verify coordinates of tabOne
//
Verify tabOne coordinates.
expect
(
tabOneRect
.
left
,
equals
(
kTabLabelPadding
.
left
));
expect
(
tabOneRect
.
left
,
equals
(
kTabLabelPadding
.
left
));
expect
(
tabOneRect
.
top
,
equals
(
kTabLabelPadding
.
top
));
expect
(
tabOneRect
.
top
,
equals
(
kTabLabelPadding
.
top
));
expect
(
tabOneRect
.
bottom
,
equals
(
tabBar
.
bottom
-
kTabLabelPadding
.
bottom
-
indicatorWeight
));
expect
(
tabOneRect
.
bottom
,
equals
(
tabBar
.
bottom
-
kTabLabelPadding
.
bottom
-
indicatorWeight
));
//
verify coordinates of tabTwo
//
Verify tabTwo coordinates.
expect
(
tabTwoRect
.
right
,
equals
(
tabBar
.
width
-
kTabLabelPadding
.
right
));
expect
(
tabTwoRect
.
right
,
equals
(
tabBar
.
width
-
kTabLabelPadding
.
right
));
expect
(
tabTwoRect
.
top
,
equals
(
kTabLabelPadding
.
top
));
expect
(
tabTwoRect
.
top
,
equals
(
kTabLabelPadding
.
top
));
expect
(
tabTwoRect
.
bottom
,
equals
(
tabBar
.
bottom
-
kTabLabelPadding
.
bottom
-
indicatorWeight
));
expect
(
tabTwoRect
.
bottom
,
equals
(
tabBar
.
bottom
-
kTabLabelPadding
.
bottom
-
indicatorWeight
));
//
verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo
//
Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
expect
(
tabOneRect
.
right
,
equals
(
tabTwoRect
.
left
-
kTabLabelPadding
.
left
-
kTabLabelPadding
.
right
));
expect
(
tabOneRect
.
right
,
equals
(
tabTwoRect
.
left
-
kTabLabelPadding
.
left
-
kTabLabelPadding
.
right
));
// Verify divider color and indicator color.
final
RenderBox
tabBarBox
=
tester
.
firstRenderObject
<
RenderBox
>(
find
.
byType
(
TabBar
));
final
RenderBox
tabBarBox
=
tester
.
firstRenderObject
<
RenderBox
>(
find
.
byType
(
TabBar
));
expect
(
expect
(
tabBarBox
,
tabBarBox
,
paints
paints
..
line
(
color:
theme
.
colorScheme
.
surfaceVariant
)
..
line
(
color:
theme
.
colorScheme
.
surfaceVariant
)
// Indicator is a rrect in the primary tab bar.
..
rrect
(
color:
theme
.
colorScheme
.
primary
),
..
rrect
(
color:
theme
.
colorScheme
.
primary
),
);
);
});
});
testWidgets
(
'Tab bar defaults (secondary)'
,
(
WidgetTester
tester
)
async
{
// Test default label color and label styles.
await
tester
.
pumpWidget
(
buildTabBar
(
secondaryTabBar:
true
,
useMaterial3:
true
));
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
RenderParagraph
selectedLabel
=
_getText
(
tester
,
_tab1Text
);
expect
(
selectedLabel
.
text
.
style
!.
fontFamily
,
equals
(
theme
.
textTheme
.
titleSmall
!.
fontFamily
));
expect
(
selectedLabel
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
selectedLabel
.
text
.
style
!.
color
,
equals
(
theme
.
colorScheme
.
onSurface
));
final
RenderParagraph
unselectedLabel
=
_getText
(
tester
,
_tab2Text
);
expect
(
unselectedLabel
.
text
.
style
!.
fontFamily
,
equals
(
theme
.
textTheme
.
titleSmall
!.
fontFamily
));
expect
(
unselectedLabel
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
unselectedLabel
.
text
.
style
!.
color
,
equals
(
theme
.
colorScheme
.
onSurfaceVariant
));
// Test default labelPadding.
await
tester
.
pumpWidget
(
buildTabBar
(
secondaryTabBar:
true
,
tabs:
_sizedTabs
,
isScrollable:
true
,
useMaterial3:
true
,
));
const
double
indicatorWeight
=
2.0
;
final
Rect
tabBar
=
tester
.
getRect
(
find
.
byType
(
TabBar
));
final
Rect
tabOneRect
=
tester
.
getRect
(
find
.
byKey
(
_sizedTabs
[
0
].
key
!));
final
Rect
tabTwoRect
=
tester
.
getRect
(
find
.
byKey
(
_sizedTabs
[
1
].
key
!));
// Verify tabOne coordinates.
expect
(
tabOneRect
.
left
,
equals
(
kTabLabelPadding
.
left
));
expect
(
tabOneRect
.
top
,
equals
(
kTabLabelPadding
.
top
));
expect
(
tabOneRect
.
bottom
,
equals
(
tabBar
.
bottom
-
kTabLabelPadding
.
bottom
-
indicatorWeight
));
// Verify tabTwo coordinates.
expect
(
tabTwoRect
.
right
,
equals
(
tabBar
.
width
-
kTabLabelPadding
.
right
));
expect
(
tabTwoRect
.
top
,
equals
(
kTabLabelPadding
.
top
));
expect
(
tabTwoRect
.
bottom
,
equals
(
tabBar
.
bottom
-
kTabLabelPadding
.
bottom
-
indicatorWeight
));
// Verify tabOne and tabTwo is separated by right padding of tabOne and left padding of tabTwo.
expect
(
tabOneRect
.
right
,
equals
(
tabTwoRect
.
left
-
kTabLabelPadding
.
left
-
kTabLabelPadding
.
right
));
// Verify divider color and indicator color.
final
RenderBox
tabBarBox
=
tester
.
firstRenderObject
<
RenderBox
>(
find
.
byType
(
TabBar
));
expect
(
tabBarBox
,
paints
..
line
(
color:
theme
.
colorScheme
.
surfaceVariant
)
// Indicator is a line in the secondary tab bar.
..
line
(
color:
theme
.
colorScheme
.
primary
),
);
});
testWidgets
(
'Tab bar theme overrides label color (selected)'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar theme overrides label color (selected)'
,
(
WidgetTester
tester
)
async
{
const
Color
labelColor
=
Colors
.
black
;
const
Color
labelColor
=
Colors
.
black
;
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
labelColor:
labelColor
);
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
labelColor:
labelColor
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
final
RenderParagraph
t
extRenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab1Text
)
);
final
RenderParagraph
t
abLabel
=
_getText
(
tester
,
_tab1Text
);
expect
(
t
extRenderObject
.
text
.
style
!.
color
,
equals
(
labelColor
));
expect
(
t
abLabel
.
text
.
style
!.
color
,
equals
(
labelColor
));
final
RenderParagraph
iconRenderObject
=
_iconRenderObject
(
tester
,
Icons
.
looks_one
);
final
RenderParagraph
tabIcon
=
_getIcon
(
tester
,
Icons
.
looks_one
);
expect
(
iconRenderObject
.
text
.
style
!.
color
,
equals
(
labelColor
));
expect
(
tabIcon
.
text
.
style
!.
color
,
equals
(
labelColor
));
});
});
testWidgets
(
'Tab bar theme overrides label padding'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar theme overrides label padding'
,
(
WidgetTester
tester
)
async
{
...
@@ -151,8 +225,8 @@ void main() {
...
@@ -151,8 +225,8 @@ void main() {
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
labelPadding:
labelPadding
);
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
labelPadding:
labelPadding
);
await
tester
.
pumpWidget
(
_withTheme
(
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme
,
tabBarTheme
:
tabBarTheme
,
tabs:
_sizedTabs
,
tabs:
_sizedTabs
,
isScrollable:
true
,
isScrollable:
true
,
));
));
...
@@ -183,12 +257,12 @@ void main() {
...
@@ -183,12 +257,12 @@ void main() {
unselectedLabelStyle:
unselectedLabelStyle
,
unselectedLabelStyle:
unselectedLabelStyle
,
);
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
final
RenderParagraph
selected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab1Text
)
);
final
RenderParagraph
selected
Label
=
_getText
(
tester
,
_tab1Text
);
expect
(
selected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
labelStyle
.
fontFamily
));
expect
(
selected
Label
.
text
.
style
!.
fontFamily
,
equals
(
labelStyle
.
fontFamily
));
final
RenderParagraph
unselected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab2Text
)
);
final
RenderParagraph
unselected
Label
=
_getText
(
tester
,
_tab2Text
);
expect
(
unselected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
unselectedLabelStyle
.
fontFamily
));
expect
(
unselected
Label
.
text
.
style
!.
fontFamily
,
equals
(
unselectedLabelStyle
.
fontFamily
));
});
});
testWidgets
(
'Tab bar theme with just label style specified'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar theme with just label style specified'
,
(
WidgetTester
tester
)
async
{
...
@@ -198,14 +272,14 @@ void main() {
...
@@ -198,14 +272,14 @@ void main() {
labelStyle:
labelStyle
,
labelStyle:
labelStyle
,
);
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
final
RenderParagraph
selected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab1Text
)
);
final
RenderParagraph
selected
Label
=
_getText
(
tester
,
_tab1Text
);
expect
(
selected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
labelStyle
.
fontFamily
));
expect
(
selected
Label
.
text
.
style
!.
fontFamily
,
equals
(
labelStyle
.
fontFamily
));
final
RenderParagraph
unselected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab2Text
)
);
final
RenderParagraph
unselected
Label
=
_getText
(
tester
,
_tab2Text
);
expect
(
unselected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
'Roboto'
));
expect
(
unselected
Label
.
text
.
style
!.
fontFamily
,
equals
(
'Roboto'
));
expect
(
unselected
RenderObject
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
unselected
Label
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
unselected
RenderObject
.
text
.
style
!.
color
,
equals
(
Colors
.
white
.
withAlpha
(
0xB2
)));
expect
(
unselected
Label
.
text
.
style
!.
color
,
equals
(
Colors
.
white
.
withAlpha
(
0xB2
)));
});
});
testWidgets
(
'Tab bar label styles override theme label styles'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar label styles override theme label styles'
,
(
WidgetTester
tester
)
async
{
...
@@ -221,7 +295,8 @@ void main() {
...
@@ -221,7 +295,8 @@ void main() {
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
MaterialApp
(
MaterialApp
(
theme:
ThemeData
(
tabBarTheme:
tabBarTheme
),
theme:
ThemeData
(
tabBarTheme:
tabBarTheme
),
home:
Scaffold
(
body:
TabBar
(
home:
Scaffold
(
body:
TabBar
(
tabs:
_tabs
,
tabs:
_tabs
,
controller:
TabController
(
length:
_tabs
.
length
,
vsync:
const
TestVSync
()),
controller:
TabController
(
length:
_tabs
.
length
,
vsync:
const
TestVSync
()),
labelStyle:
labelStyle
,
labelStyle:
labelStyle
,
...
@@ -231,10 +306,10 @@ void main() {
...
@@ -231,10 +306,10 @@ void main() {
),
),
);
);
final
RenderParagraph
selected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab1Text
)
);
final
RenderParagraph
selected
Label
=
_getText
(
tester
,
_tab1Text
);
expect
(
selected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
labelStyle
.
fontFamily
));
expect
(
selected
Label
.
text
.
style
!.
fontFamily
,
equals
(
labelStyle
.
fontFamily
));
final
RenderParagraph
unselected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab2Text
)
);
final
RenderParagraph
unselected
Label
=
_getText
(
tester
,
_tab2Text
);
expect
(
unselected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
unselectedLabelStyle
.
fontFamily
));
expect
(
unselected
Label
.
text
.
style
!.
fontFamily
,
equals
(
unselectedLabelStyle
.
fontFamily
));
});
});
testWidgets
(
'Tab bar label padding overrides theme label padding'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar label padding overrides theme label padding'
,
(
WidgetTester
tester
)
async
{
...
@@ -295,16 +370,25 @@ void main() {
...
@@ -295,16 +370,25 @@ void main() {
const
Color
unselectedLabelColor
=
Colors
.
black
;
const
Color
unselectedLabelColor
=
Colors
.
black
;
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
unselectedLabelColor:
unselectedLabelColor
);
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
unselectedLabelColor:
unselectedLabelColor
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
final
RenderParagraph
textRenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab2Text
));
final
RenderParagraph
textRenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab2Text
));
expect
(
textRenderObject
.
text
.
style
!.
color
,
equals
(
unselectedLabelColor
));
expect
(
textRenderObject
.
text
.
style
!.
color
,
equals
(
unselectedLabelColor
));
final
RenderParagraph
iconRenderObject
=
_
iconRenderObject
(
tester
,
Icons
.
looks_two
);
final
RenderParagraph
iconRenderObject
=
_
getIcon
(
tester
,
Icons
.
looks_two
);
expect
(
iconRenderObject
.
text
.
style
!.
color
,
equals
(
unselectedLabelColor
));
expect
(
iconRenderObject
.
text
.
style
!.
color
,
equals
(
unselectedLabelColor
));
});
});
testWidgets
(
'Tab bar default tab indicator size'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar default tab indicator size'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
_withTheme
(
null
,
useMaterial3:
true
,
isScrollable:
true
));
await
tester
.
pumpWidget
(
buildTabBar
(
useMaterial3:
true
,
isScrollable:
true
));
await
expectLater
(
find
.
byKey
(
_painterKey
),
matchesGoldenFile
(
'tab_bar.default.tab_indicator_size.png'
),
);
});
testWidgets
(
'Tab bar default tab indicator size'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildTabBar
(
useMaterial3:
true
,
isScrollable:
true
));
await
expectLater
(
await
expectLater
(
find
.
byKey
(
_painterKey
),
find
.
byKey
(
_painterKey
),
...
@@ -315,7 +399,7 @@ void main() {
...
@@ -315,7 +399,7 @@ void main() {
testWidgets
(
'Tab bar theme overrides tab indicator size (tab)'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar theme overrides tab indicator size (tab)'
,
(
WidgetTester
tester
)
async
{
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
indicatorSize:
TabBarIndicatorSize
.
tab
);
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
indicatorSize:
TabBarIndicatorSize
.
tab
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
await
expectLater
(
await
expectLater
(
find
.
byKey
(
_painterKey
),
find
.
byKey
(
_painterKey
),
...
@@ -326,7 +410,7 @@ void main() {
...
@@ -326,7 +410,7 @@ void main() {
testWidgets
(
'Tab bar theme overrides tab indicator size (label)'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar theme overrides tab indicator size (label)'
,
(
WidgetTester
tester
)
async
{
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
indicatorSize:
TabBarIndicatorSize
.
label
);
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
indicatorSize:
TabBarIndicatorSize
.
label
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
await
expectLater
(
await
expectLater
(
find
.
byKey
(
_painterKey
),
find
.
byKey
(
_painterKey
),
...
@@ -337,7 +421,7 @@ void main() {
...
@@ -337,7 +421,7 @@ void main() {
testWidgets
(
'Tab bar theme overrides tab mouse cursor'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar theme overrides tab mouse cursor'
,
(
WidgetTester
tester
)
async
{
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
mouseCursor:
MaterialStateMouseCursor
.
textable
);
const
TabBarTheme
tabBarTheme
=
TabBarTheme
(
mouseCursor:
MaterialStateMouseCursor
.
textable
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
final
Offset
tabBar
=
tester
.
getCenter
(
final
Offset
tabBar
=
tester
.
getCenter
(
find
.
ancestor
(
of:
find
.
text
(
'tab 1'
),
matching:
find
.
byType
(
TabBar
)),
find
.
ancestor
(
of:
find
.
text
(
'tab 1'
),
matching:
find
.
byType
(
TabBar
)),
...
@@ -356,7 +440,7 @@ void main() {
...
@@ -356,7 +440,7 @@ void main() {
),
),
);
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
await
expectLater
(
await
expectLater
(
find
.
byKey
(
_painterKey
),
find
.
byKey
(
_painterKey
),
...
@@ -372,7 +456,7 @@ void main() {
...
@@ -372,7 +456,7 @@ void main() {
),
),
);
);
await
tester
.
pumpWidget
(
_withTheme
(
tabBarTheme
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabBarTheme:
tabBarTheme
));
await
expectLater
(
await
expectLater
(
find
.
byKey
(
_painterKey
),
find
.
byKey
(
_painterKey
),
...
@@ -386,19 +470,19 @@ void main() {
...
@@ -386,19 +470,19 @@ void main() {
testWidgets
(
'Tab bar defaults'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar defaults'
,
(
WidgetTester
tester
)
async
{
// tests for the default label color and label styles when tabBarTheme and tabBar do not provide any
// tests for the default label color and label styles when tabBarTheme and tabBar do not provide any
await
tester
.
pumpWidget
(
_withTheme
(
null
));
await
tester
.
pumpWidget
(
buildTabBar
(
));
final
RenderParagraph
selected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab1Text
)
);
final
RenderParagraph
selected
Label
=
_getText
(
tester
,
_tab1Text
);
expect
(
selected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
'Roboto'
));
expect
(
selected
Label
.
text
.
style
!.
fontFamily
,
equals
(
'Roboto'
));
expect
(
selected
RenderObject
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
selected
Label
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
selected
RenderObject
.
text
.
style
!.
color
,
equals
(
Colors
.
white
));
expect
(
selected
Label
.
text
.
style
!.
color
,
equals
(
Colors
.
white
));
final
RenderParagraph
unselected
RenderObject
=
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
_tab2Text
)
);
final
RenderParagraph
unselected
Label
=
_getText
(
tester
,
_tab2Text
);
expect
(
unselected
RenderObject
.
text
.
style
!.
fontFamily
,
equals
(
'Roboto'
));
expect
(
unselected
Label
.
text
.
style
!.
fontFamily
,
equals
(
'Roboto'
));
expect
(
unselected
RenderObject
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
unselected
Label
.
text
.
style
!.
fontSize
,
equals
(
14.0
));
expect
(
unselected
RenderObject
.
text
.
style
!.
color
,
equals
(
Colors
.
white
.
withAlpha
(
0xB2
)));
expect
(
unselected
Label
.
text
.
style
!.
color
,
equals
(
Colors
.
white
.
withAlpha
(
0xB2
)));
// tests for the default value of labelPadding when tabBarTheme and tabBar do not provide one
// tests for the default value of labelPadding when tabBarTheme and tabBar do not provide one
await
tester
.
pumpWidget
(
_withTheme
(
null
,
tabs:
_sizedTabs
,
isScrollable:
true
));
await
tester
.
pumpWidget
(
buildTabBar
(
tabs:
_sizedTabs
,
isScrollable:
true
));
const
double
indicatorWeight
=
2.0
;
const
double
indicatorWeight
=
2.0
;
final
Rect
tabBar
=
tester
.
getRect
(
find
.
byType
(
TabBar
));
final
Rect
tabBar
=
tester
.
getRect
(
find
.
byType
(
TabBar
));
...
@@ -423,7 +507,7 @@ void main() {
...
@@ -423,7 +507,7 @@ void main() {
});
});
testWidgets
(
'Tab bar default tab indicator size'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Tab bar default tab indicator size'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
_withTheme
(
null
));
await
tester
.
pumpWidget
(
buildTabBar
(
));
await
expectLater
(
await
expectLater
(
find
.
byKey
(
_painterKey
),
find
.
byKey
(
_painterKey
),
...
...
packages/flutter/test/material/tabs_test.dart
View file @
02d5c759
...
@@ -106,6 +106,7 @@ class _NestedTabBarContainer extends StatelessWidget {
...
@@ -106,6 +106,7 @@ class _NestedTabBarContainer extends StatelessWidget {
Widget
buildFrame
(
{
Widget
buildFrame
(
{
Key
?
tabBarKey
,
Key
?
tabBarKey
,
bool
secondaryTabBar
=
false
,
required
List
<
String
>
tabs
,
required
List
<
String
>
tabs
,
required
String
value
,
required
String
value
,
bool
isScrollable
=
false
,
bool
isScrollable
=
false
,
...
@@ -114,6 +115,24 @@ Widget buildFrame({
...
@@ -114,6 +115,24 @@ Widget buildFrame({
EdgeInsetsGeometry
?
padding
,
EdgeInsetsGeometry
?
padding
,
TextDirection
textDirection
=
TextDirection
.
ltr
,
TextDirection
textDirection
=
TextDirection
.
ltr
,
})
{
})
{
if
(
secondaryTabBar
)
{
return
boilerplate
(
textDirection:
textDirection
,
child:
DefaultTabController
(
animationDuration:
animationDuration
,
initialIndex:
tabs
.
indexOf
(
value
),
length:
tabs
.
length
,
child:
TabBar
.
secondary
(
key:
tabBarKey
,
tabs:
tabs
.
map
<
Widget
>((
String
tab
)
=>
Tab
(
text:
tab
)).
toList
(),
isScrollable:
isScrollable
,
indicatorColor:
indicatorColor
,
padding:
padding
,
),
),
);
}
return
boilerplate
(
return
boilerplate
(
textDirection:
textDirection
,
textDirection:
textDirection
,
child:
DefaultTabController
(
child:
DefaultTabController
(
...
@@ -238,6 +257,10 @@ class TestScrollPhysics extends ScrollPhysics {
...
@@ -238,6 +257,10 @@ class TestScrollPhysics extends ScrollPhysics {
SpringDescription
get
spring
=>
_kDefaultSpring
;
SpringDescription
get
spring
=>
_kDefaultSpring
;
}
}
RenderParagraph
_getText
(
WidgetTester
tester
,
String
text
)
{
return
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
text
));
}
void
main
(
)
{
void
main
(
)
{
setUp
(()
{
setUp
(()
{
debugResetSemanticsIdCounter
();
debugResetSemanticsIdCounter
();
...
@@ -358,12 +381,12 @@ void main() {
...
@@ -358,12 +381,12 @@ void main() {
expect
(
find
.
byType
(
TabBar
),
paints
..
line
(
color:
Colors
.
blue
[
500
]));
expect
(
find
.
byType
(
TabBar
),
paints
..
line
(
color:
Colors
.
blue
[
500
]));
});
});
testWidgets
(
'TabBar default selected/unselected
text style
'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'TabBar default selected/unselected
label style (primary)
'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
const
String
selectedValue
=
'A'
;
const
String
selectedValue
=
'A'
;
const
String
un
S
electedValue
=
'C'
;
const
String
un
s
electedValue
=
'C'
;
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
Theme
(
Theme
(
data:
theme
,
data:
theme
,
...
@@ -375,20 +398,95 @@ void main() {
...
@@ -375,20 +398,95 @@ void main() {
expect
(
find
.
text
(
'C'
),
findsOneWidget
);
expect
(
find
.
text
(
'C'
),
findsOneWidget
);
// Test selected label text style.
// Test selected label text style.
expect
(
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
selectedValue
)).
text
.
style
!.
fontFamily
,
'Roboto'
);
final
RenderParagraph
selectedLabel
=
_getText
(
tester
,
selectedValue
);
expect
(
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
selectedValue
)).
text
.
style
!.
fontSize
,
14.0
);
expect
(
selectedLabel
.
text
.
style
!.
fontFamily
,
'Roboto'
);
expect
(
tester
.
renderObject
<
RenderParagraph
>(
expect
(
selectedLabel
.
text
.
style
!.
fontSize
,
14.0
);
find
.
text
(
selectedValue
)).
text
.
style
!.
color
,
expect
(
selectedLabel
.
text
.
style
!.
color
,
theme
.
colorScheme
.
primary
);
theme
.
colorScheme
.
primary
,
// Test unselected label text style.
final
RenderParagraph
unselectedLabel
=
_getText
(
tester
,
unselectedValue
);
expect
(
unselectedLabel
.
text
.
style
!.
fontFamily
,
'Roboto'
);
expect
(
unselectedLabel
.
text
.
style
!.
fontSize
,
14.0
);
expect
(
unselectedLabel
.
text
.
style
!.
color
,
theme
.
colorScheme
.
onSurfaceVariant
);
});
testWidgets
(
'TabBar default selected/unselected label style (secondary)'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
,
'C'
];
const
String
selectedValue
=
'A'
;
const
String
unselectedValue
=
'C'
;
await
tester
.
pumpWidget
(
Theme
(
data:
theme
,
child:
buildFrame
(
tabs:
tabs
,
value:
selectedValue
,
secondaryTabBar:
true
),
),
);
);
expect
(
find
.
text
(
'A'
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
),
findsOneWidget
);
expect
(
find
.
text
(
'C'
),
findsOneWidget
);
// Test selected label text style.
final
RenderParagraph
selectedLabel
=
_getText
(
tester
,
selectedValue
);
expect
(
selectedLabel
.
text
.
style
!.
fontFamily
,
'Roboto'
);
expect
(
selectedLabel
.
text
.
style
!.
fontSize
,
14.0
);
expect
(
selectedLabel
.
text
.
style
!.
color
,
theme
.
colorScheme
.
onSurface
);
// Test unselected label text style.
// Test unselected label text style.
expect
(
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
unSelectedValue
)).
text
.
style
!.
fontFamily
,
'Roboto'
);
final
RenderParagraph
unselectedLabel
=
_getText
(
tester
,
unselectedValue
);
expect
(
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
unSelectedValue
)).
text
.
style
!.
fontSize
,
14.0
);
expect
(
unselectedLabel
.
text
.
style
!.
fontFamily
,
'Roboto'
);
expect
(
tester
.
renderObject
<
RenderParagraph
>(
expect
(
unselectedLabel
.
text
.
style
!.
fontSize
,
14.0
);
find
.
text
(
unSelectedValue
)).
text
.
style
!.
color
,
expect
(
unselectedLabel
.
text
.
style
!.
color
,
theme
.
colorScheme
.
onSurfaceVariant
);
theme
.
colorScheme
.
onSurfaceVariant
,
});
testWidgets
(
'TabBar default overlay (primary)'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
];
const
String
selectedValue
=
'A'
;
const
String
unselectedValue
=
'B'
;
await
tester
.
pumpWidget
(
Theme
(
data:
theme
,
child:
buildFrame
(
tabs:
tabs
,
value:
selectedValue
),
),
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
text
(
selectedValue
)));
await
tester
.
pumpAndSettle
();
final
RenderObject
inkFeatures
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
object
)
=>
object
.
runtimeType
.
toString
()
==
'_RenderInkFeatures'
);
expect
(
inkFeatures
,
paints
..
rect
(
color:
theme
.
colorScheme
.
primary
.
withOpacity
(
0.08
)));
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
text
(
unselectedValue
)));
await
tester
.
pumpAndSettle
();
expect
(
inkFeatures
,
paints
..
rect
(
color:
theme
.
colorScheme
.
onSurface
.
withOpacity
(
0.08
)));
});
testWidgets
(
'TabBar default overlay (secondary)'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
final
List
<
String
>
tabs
=
<
String
>[
'A'
,
'B'
];
const
String
selectedValue
=
'A'
;
const
String
unselectedValue
=
'B'
;
await
tester
.
pumpWidget
(
Theme
(
data:
theme
,
child:
buildFrame
(
tabs:
tabs
,
value:
selectedValue
,
secondaryTabBar:
true
),
),
);
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
text
(
selectedValue
)));
await
tester
.
pumpAndSettle
();
final
RenderObject
inkFeatures
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
object
)
=>
object
.
runtimeType
.
toString
()
==
'_RenderInkFeatures'
);
expect
(
inkFeatures
,
paints
..
rect
(
color:
theme
.
colorScheme
.
onSurface
.
withOpacity
(
0.08
)));
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
text
(
unselectedValue
)));
await
tester
.
pumpAndSettle
();
expect
(
inkFeatures
,
paints
..
rect
(
color:
theme
.
colorScheme
.
onSurface
.
withOpacity
(
0.08
)));
});
});
testWidgets
(
'TabBar tap selects tab'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'TabBar tap selects tab'
,
(
WidgetTester
tester
)
async
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment