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
123e9e01
Commit
123e9e01
authored
Jun 12, 2017
by
Hans Muller
Committed by
GitHub
Jun 12, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow TabBars, TabBarViews, TabControllers, with zero or one tabs (#10608)
parent
a8487722
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
139 additions
and
21 deletions
+139
-21
tab_controller.dart
packages/flutter/lib/src/material/tab_controller.dart
+26
-11
tabs.dart
packages/flutter/lib/src/material/tabs.dart
+18
-10
tabs_test.dart
packages/flutter/test/material/tabs_test.dart
+95
-0
No files found.
packages/flutter/lib/src/material/tab_controller.dart
View file @
123e9e01
...
...
@@ -16,6 +16,8 @@ import 'constants.dart';
/// A stateful widget that builds a [TabBar] or a [TabBarView] can create
/// a TabController and share it directly.
///
/// ## Sample code
///
/// ```dart
/// class _MyDemoState extends State<MyDemo> with SingleTickerProviderStateMixin {
/// final List<Tab> myTabs = <Tab>[
...
...
@@ -62,12 +64,18 @@ import 'constants.dart';
/// inherited widget.
class
TabController
extends
ChangeNotifier
{
/// Creates an object that manages the state required by [TabBar] and a [TabBarView].
///
/// The [length] cannot be null or negative. Typically its a value greater than one, i.e.
/// typically there are two or more tabs.
///
/// The `initialIndex` must be valid given [length] and cannot be null. If [length] is
/// zero, then `initialIndex` must be 0 (the default).
TabController
({
int
initialIndex:
0
,
@required
this
.
length
,
@required
TickerProvider
vsync
})
:
assert
(
length
!=
null
&&
length
>
1
),
assert
(
initialIndex
!=
null
&&
initialIndex
>=
0
&&
initialIndex
<
length
),
:
assert
(
length
!=
null
&&
length
>
=
0
),
assert
(
initialIndex
!=
null
&&
initialIndex
>=
0
&&
(
length
==
0
||
initialIndex
<
length
)
),
_index
=
initialIndex
,
_previousIndex
=
initialIndex
,
_animationController
=
new
AnimationController
(
_animationController
=
length
<
2
?
null
:
new
AnimationController
(
value:
initialIndex
.
toDouble
(),
upperBound:
(
length
-
1
).
toDouble
(),
vsync:
vsync
...
...
@@ -81,18 +89,21 @@ class TabController extends ChangeNotifier {
/// selected tab is changed, the animation's value equals [index]. The
/// animation's value can be [offset] by +/- 1.0 to reflect [TabBarView]
/// drag scrolling.
Animation
<
double
>
get
animation
=>
_animationController
.
view
;
///
/// If length is zero or one, [index] animations don't happen and the value
/// of this property is [kAlwaysCompleteAnimation].
Animation
<
double
>
get
animation
=>
_animationController
?.
view
??
kAlwaysCompleteAnimation
;
final
AnimationController
_animationController
;
/// The total number of tabs.
Must be
greater than one.
/// The total number of tabs.
Typically
greater than one.
final
int
length
;
void
_changeIndex
(
int
value
,
{
Duration
duration
,
Curve
curve
})
{
assert
(
value
!=
null
);
assert
(
value
>=
0
&&
value
<
length
);
assert
(
value
>=
0
&&
(
value
<
length
||
length
==
0
)
);
assert
(
duration
==
null
?
curve
==
null
:
true
);
assert
(
_indexIsChangingCount
>=
0
);
if
(
value
==
_index
)
if
(
value
==
_index
||
length
<
2
)
return
;
_previousIndex
=
index
;
_index
=
value
;
...
...
@@ -118,6 +129,9 @@ class TabController extends ChangeNotifier {
/// [indexIsChanging] to false, and notifies listeners.
///
/// To change the currently selected tab and play the [animation] use [animateTo].
///
/// The value of [index] must be valid given [length]. If [length] is zero,
/// then [index] will also be zero.
int
get
index
=>
_index
;
int
_index
;
set
index
(
int
value
)
{
...
...
@@ -148,8 +162,9 @@ class TabController extends ChangeNotifier {
/// drags left or right. A value between -1.0 and 0.0 implies that the
/// TabBarView has been dragged to the left. Similarly a value between
/// 0.0 and 1.0 implies that the TabBarView has been dragged to the right.
double
get
offset
=>
_animationController
.
value
-
_index
.
toDouble
()
;
double
get
offset
=>
length
>
1
?
_animationController
.
value
-
_index
.
toDouble
()
:
0.0
;
set
offset
(
double
value
)
{
assert
(
length
>
1
);
assert
(
value
!=
null
);
assert
(
value
>=
-
1.0
&&
value
<=
1.0
);
assert
(!
indexIsChanging
);
...
...
@@ -160,7 +175,7 @@ class TabController extends ChangeNotifier {
@override
void
dispose
()
{
_animationController
.
dispose
();
_animationController
?
.
dispose
();
super
.
dispose
();
}
}
...
...
@@ -220,7 +235,7 @@ class _TabControllerScope extends InheritedWidget {
class
DefaultTabController
extends
StatefulWidget
{
/// Creates a default tab controller for the given [child] widget.
///
/// The [length] argument
must be great
than one.
/// The [length] argument
is typically greater
than one.
///
/// The [initialIndex] argument must not be null.
const
DefaultTabController
({
...
...
@@ -231,7 +246,7 @@ class DefaultTabController extends StatefulWidget {
})
:
assert
(
initialIndex
!=
null
),
super
(
key:
key
);
/// The total number of tabs.
Must be
greater than one.
/// The total number of tabs.
Typically
greater than one.
final
int
length
;
/// The initial index of the selected tab.
...
...
packages/flutter/lib/src/material/tabs.dart
View file @
123e9e01
...
...
@@ -389,22 +389,23 @@ class _TabBarScrollController extends ScrollController {
/// A material design widget that displays a horizontal row of tabs.
///
/// Typically created as
part of an [AppBar] and in conjuction with a
/// [TabBarView].
/// Typically created as
the [AppBar.bottom] part of an [AppBar] and in
///
conjuction with a
[TabBarView].
///
/// If a [TabController] is not provided, then there must be a
/// [DefaultTabController] ancestor.
/// [DefaultTabController] ancestor. The tab controller's [TabController.length]
/// must equal the length of the [tabs] list.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// See also:
///
/// * [TabBarView], which displays the contents that the tab bar is selecting
/// between.
/// * [TabBarView], which displays page views that correspond to each tab.
class
TabBar
extends
StatefulWidget
implements
PreferredSizeWidget
{
/// Creates a material design tab bar.
///
/// The [tabs] argument must not be null and must have more than one widget.
/// The [tabs] argument cannot be null and its length must match the [controller]'s
/// [TabController.length].
///
/// If a [TabController] is not provided, then there must be a
/// [DefaultTabController] ancestor.
...
...
@@ -424,13 +425,15 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
this
.
labelStyle
,
this
.
unselectedLabelColor
,
this
.
unselectedLabelStyle
,
})
:
assert
(
tabs
!=
null
&&
tabs
.
length
>
1
),
})
:
assert
(
tabs
!=
null
),
assert
(
isScrollable
!=
null
),
assert
(
indicatorWeight
!=
null
&&
indicatorWeight
>
0.0
),
assert
(
indicatorPadding
!=
null
),
super
(
key:
key
);
/// Typically a list of [Tab] widgets.
/// Typically a list of two or more [Tab] widgets.
///
/// The length of this list must match the [controller]'s [TabController.length].
final
List
<
Widget
>
tabs
;
/// This widget's selection and animation state.
...
...
@@ -667,6 +670,12 @@ class _TabBarState extends State<TabBar> {
@override
Widget
build
(
BuildContext
context
)
{
if
(
_controller
.
length
==
0
)
{
return
new
Container
(
height:
_kTabHeight
+
widget
.
indicatorWeight
,
);
}
final
List
<
Widget
>
wrappedTabs
=
new
List
<
Widget
>.
from
(
widget
.
tabs
,
growable:
false
);
// If the controller was provided by DefaultTabController and we're part
...
...
@@ -774,8 +783,7 @@ class TabBarView extends StatefulWidget {
Key
key
,
@required
this
.
children
,
this
.
controller
,
})
:
assert
(
children
!=
null
&&
children
.
length
>
1
),
super
(
key:
key
);
})
:
assert
(
children
!=
null
),
super
(
key:
key
);
/// This widget's selection and animation state.
///
...
...
packages/flutter/test/material/tabs_test.dart
View file @
123e9e01
...
...
@@ -900,4 +900,99 @@ void main() {
rect:
new
Rect
.
fromLTRB
(
tabLeft
+
padLeft
,
height
,
tabRight
-
padRight
,
height
+
weight
)
));
});
testWidgets
(
'TabBar etc with zero tabs'
,
(
WidgetTester
tester
)
async
{
final
TabController
controller
=
new
TabController
(
vsync:
const
TestVSync
(),
length:
0
,
);
await
tester
.
pumpWidget
(
new
Material
(
child:
new
Column
(
children:
<
Widget
>[
new
TabBar
(
controller:
controller
,
tabs:
const
<
Widget
>[],
),
new
Flexible
(
child:
new
TabBarView
(
controller:
controller
,
children:
const
<
Widget
>[],
),
),
],
),
),
);
expect
(
controller
.
index
,
0
);
expect
(
tester
.
getSize
(
find
.
byType
(
TabBar
)),
const
Size
(
800.0
,
48.0
));
expect
(
tester
.
getSize
(
find
.
byType
(
TabBarView
)),
const
Size
(
800.0
,
600.0
-
48.0
));
// A fling in the TabBar or TabBarView, shouldn't do anything.
await
(
tester
.
fling
(
find
.
byType
(
TabBar
),
const
Offset
(-
100.0
,
0.0
),
5000.0
));
await
(
tester
.
pumpAndSettle
());
await
(
tester
.
fling
(
find
.
byType
(
TabBarView
),
const
Offset
(
100.0
,
0.0
),
5000.0
));
await
(
tester
.
pumpAndSettle
());
expect
(
controller
.
index
,
0
);
});
testWidgets
(
'TabBar etc with one tab'
,
(
WidgetTester
tester
)
async
{
final
TabController
controller
=
new
TabController
(
vsync:
const
TestVSync
(),
length:
1
,
);
await
tester
.
pumpWidget
(
new
Material
(
child:
new
Column
(
children:
<
Widget
>[
new
TabBar
(
controller:
controller
,
tabs:
const
<
Widget
>[
const
Tab
(
text:
'TAB'
)],
),
new
Flexible
(
child:
new
TabBarView
(
controller:
controller
,
children:
const
<
Widget
>[
const
Text
(
'PAGE'
)],
),
),
],
),
),
);
expect
(
controller
.
index
,
0
);
expect
(
find
.
text
(
'TAB'
),
findsOneWidget
);
expect
(
find
.
text
(
'PAGE'
),
findsOneWidget
);
expect
(
tester
.
getSize
(
find
.
byType
(
TabBar
)),
const
Size
(
800.0
,
48.0
));
expect
(
tester
.
getSize
(
find
.
byType
(
TabBarView
)),
const
Size
(
800.0
,
600.0
-
48.0
));
// The one tab spans the app's width
expect
(
tester
.
getTopLeft
(
find
.
widgetWithText
(
Tab
,
'TAB'
)).
dx
,
0
);
expect
(
tester
.
getTopRight
(
find
.
widgetWithText
(
Tab
,
'TAB'
)).
dx
,
800
);
// A fling in the TabBar or TabBarView, shouldn't move the tab.
await
(
tester
.
fling
(
find
.
byType
(
TabBar
),
const
Offset
(-
100.0
,
0.0
),
5000.0
));
await
(
tester
.
pump
(
const
Duration
(
milliseconds:
50
)));
expect
(
tester
.
getTopLeft
(
find
.
widgetWithText
(
Tab
,
'TAB'
)).
dx
,
0
);
expect
(
tester
.
getTopRight
(
find
.
widgetWithText
(
Tab
,
'TAB'
)).
dx
,
800
);
await
(
tester
.
pumpAndSettle
());
await
(
tester
.
fling
(
find
.
byType
(
TabBarView
),
const
Offset
(
100.0
,
0.0
),
5000.0
));
await
(
tester
.
pump
(
const
Duration
(
milliseconds:
50
)));
expect
(
tester
.
getTopLeft
(
find
.
widgetWithText
(
Tab
,
'TAB'
)).
dx
,
0
);
expect
(
tester
.
getTopRight
(
find
.
widgetWithText
(
Tab
,
'TAB'
)).
dx
,
800
);
await
(
tester
.
pumpAndSettle
());
expect
(
controller
.
index
,
0
);
expect
(
find
.
text
(
'TAB'
),
findsOneWidget
);
expect
(
find
.
text
(
'PAGE'
),
findsOneWidget
);
});
}
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