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
3a6530d5
Commit
3a6530d5
authored
Sep 22, 2016
by
Dragoș Tiselice
Committed by
GitHub
Sep 22, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added bottom navigation. (#5877)
parent
d3f540d4
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
775 additions
and
8 deletions
+775
-8
material.dart
packages/flutter/lib/material.dart
+1
-0
bottom_navigation_bar.dart
packages/flutter/lib/src/material/bottom_navigation_bar.dart
+569
-0
constants.dart
packages/flutter/lib/src/material/constants.dart
+3
-0
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+35
-8
bottom_navigation_bar_test.dart
...ges/flutter/test/material/bottom_navigation_bar_test.dart
+167
-0
No files found.
packages/flutter/lib/material.dart
View file @
3a6530d5
...
...
@@ -15,6 +15,7 @@ export 'src/material/about.dart';
export
'src/material/app.dart'
;
export
'src/material/app_bar.dart'
;
export
'src/material/arc.dart'
;
export
'src/material/bottom_navigation_bar.dart'
;
export
'src/material/bottom_sheet.dart'
;
export
'src/material/button.dart'
;
export
'src/material/button_bar.dart'
;
...
...
packages/flutter/lib/src/material/bottom_navigation_bar.dart
0 → 100644
View file @
3a6530d5
// Copyright 2016 The Chromium 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
'dart:math'
as
math
;
import
'dart:collection'
show
Queue
;
import
'package:flutter/widgets.dart'
;
import
'package:meta/meta.dart'
;
import
'package:vector_math/vector_math_64.dart'
show
Vector3
;
import
'colors.dart'
;
import
'constants.dart'
;
import
'icon.dart'
;
import
'icon_theme.dart'
;
import
'icon_theme_data.dart'
;
import
'ink_well.dart'
;
import
'material.dart'
;
import
'theme.dart'
;
const
double
_kActiveMaxWidth
=
168.0
;
const
double
_kInactiveMaxWidth
=
96.0
;
/// Defines the layout and behavior of a [BottomNavigationBar].
///
/// See also:
///
/// * [BottomNavigationBar]
/// * [DestinationLabel]
/// * <https://material.google.com/components/bottom-navigation.html#bottom-navigation-specs>
enum
BottomNavigationBarType
{
/// The [BottomNavigationBar]'s [DestinationLabel]s have fixed width.
fixed
,
/// The location and size of the [BottomNavigationBar] [DestinationLabel]s
/// animate larger when they are tapped.
shifting
,
}
/// An interactive destination label within [BottomNavigationBar] with an icon
/// and title.
///
/// See also:
///
/// * [BottomNavigationBar]
/// * <https://material.google.com/components/bottom-navigation.html>
class
DestinationLabel
{
/// Creates a label that is used with [BottomNavigationBar.labels].
///
/// The arguments [icon] and [title] should not be null.
DestinationLabel
({
@required
this
.
icon
,
@required
this
.
title
,
this
.
backgroundColor
})
{
assert
(
this
.
icon
!=
null
);
assert
(
this
.
title
!=
null
);
}
/// The icon of the label.
final
Icon
icon
;
/// The title of the label.
final
Widget
title
;
/// The color of the background radial animation.
///
/// If the navigation bar's type is [BottomNavigationBarType.shifting], then
/// the entire bar is flooded with the [backgroundColor] when this label is
/// tapped.
final
Color
backgroundColor
;
}
/// A material widget displayed at the bottom of an app for selecting among a
/// small number of views.
///
/// The bottom navigation bar consists of multiple destinations in the form of
/// labels laid out on top of a piece of material. It provies quick navigation
/// between top-level views of an app and is typically used on mobile. For
/// larger screens, side navigation may be a better fit.
///
/// A bottom navigation bar is usually used in conjunction with [Scaffold] where
/// it is provided as the [Scaffold.bottomNavigationBar] argument.
///
/// See also:
///
/// * [DestinationLabel]
/// * [Scaffold]
/// * <https://material.google.com/components/bottom-navigation.html>
class
BottomNavigationBar
extends
StatefulWidget
{
/// Creates a bottom navigation bar, typically used in a [Scaffold] where it
/// is provided as the [Scaffold.bottomNavigationBar] argument.
///
/// The arguments [labels] and [type] should not be null.
///
/// The number of labels passed should be equal or greater than 2.
///
/// Passing a null [fixedColor] will cause a fallback to the theme's primary
/// color.
BottomNavigationBar
({
Key
key
,
@required
this
.
labels
,
this
.
onTap
,
this
.
currentIndex
:
0
,
this
.
type
:
BottomNavigationBarType
.
fixed
,
this
.
fixedColor
})
:
super
(
key:
key
)
{
assert
(
this
.
labels
!=
null
);
assert
(
this
.
labels
.
length
>=
2
);
assert
(
0
<=
currentIndex
&&
currentIndex
<
this
.
labels
.
length
);
assert
(
this
.
type
!=
null
);
assert
(
this
.
type
==
BottomNavigationBarType
.
fixed
||
this
.
fixedColor
==
null
);
}
/// The interactive labels laid out within the bottom navigation bar.
final
List
<
DestinationLabel
>
labels
;
/// The callback that is called when a label is tapped.
///
/// The widget creating the bottom navigation bar needs to keep track of the
/// current index and call `setState` to rebuild it with the newly provided
/// index.
final
ValueChanged
<
int
>
onTap
;
/// The index into [labels] of the current active label.
final
int
currentIndex
;
/// Defines the layout and behavior of a [BottomNavigationBar].
final
BottomNavigationBarType
type
;
/// The color of the selected label when bottom navigation bar is
/// [BottomNavigationBarType.fixed].
final
Color
fixedColor
;
@override
BottomNavigationBarState
createState
()
=>
new
BottomNavigationBarState
();
}
class
BottomNavigationBarState
extends
State
<
BottomNavigationBar
>
{
List
<
AnimationController
>
_controllers
;
List
<
CurvedAnimation
>
animations
;
double
_weight
;
final
Queue
<
_Circle
>
_circles
=
new
Queue
<
_Circle
>();
Color
_backgroundColor
;
// Last growing circle's color.
static
final
Tween
<
double
>
_flexTween
=
new
Tween
<
double
>(
begin:
1.0
,
end:
1.5
);
@override
void
initState
()
{
super
.
initState
();
_controllers
=
new
List
<
AnimationController
>.
generate
(
config
.
labels
.
length
,
(
int
index
)
{
return
new
AnimationController
(
duration:
kThemeAnimationDuration
)..
addListener
(
_rebuild
);
});
animations
=
new
List
<
CurvedAnimation
>.
generate
(
config
.
labels
.
length
,
(
int
index
)
{
return
new
CurvedAnimation
(
parent:
_controllers
[
index
],
curve:
Curves
.
fastOutSlowIn
,
reverseCurve:
Curves
.
fastOutSlowIn
.
flipped
);
});
_controllers
[
config
.
currentIndex
].
value
=
1.0
;
_backgroundColor
=
config
.
labels
[
config
.
currentIndex
].
backgroundColor
;
}
@override
void
dispose
()
{
for
(
AnimationController
controller
in
_controllers
)
controller
.
dispose
();
for
(
_Circle
circle
in
_circles
)
circle
.
dispose
();
super
.
dispose
();
}
void
_rebuild
()
{
setState
(()
{
// Rebuilding when any of the controllers tick, i.e. when the labels are
// animated.
});
}
double
get
_maxWidth
{
assert
(
config
.
type
!=
null
);
switch
(
config
.
type
)
{
case
BottomNavigationBarType
.
fixed
:
return
config
.
labels
.
length
*
_kActiveMaxWidth
;
case
BottomNavigationBarType
.
shifting
:
return
_kActiveMaxWidth
+
(
config
.
labels
.
length
-
1
)
*
_kInactiveMaxWidth
;
}
return
null
;
}
bool
_isAnimating
(
Animation
<
double
>
animation
)
{
return
animation
.
status
==
AnimationStatus
.
forward
||
animation
.
status
==
AnimationStatus
.
reverse
;
}
// Because of non-linear nature of the animations, the animations that are
// currently animating might not add up to the flex weight we are expecting.
// (1.5 + N - 1, since the max flex that the animating ones can have is 1.5)
// This causes instability in the animation when multiple labels are tapped.
// To solves this, we always store a weight that normalizes animating
// animations such that their resulting flex values will add up to the desired
// value.
void
_computeWeight
()
{
final
Iterable
<
Animation
<
double
>>
animating
=
animations
.
where
(
(
Animation
<
double
>
animation
)
=>
_isAnimating
(
animation
)
);
if
(
animating
.
isNotEmpty
)
{
final
double
sum
=
animating
.
fold
(
0.0
,
(
double
sum
,
Animation
<
double
>
animation
)
{
return
sum
+
_flexTween
.
evaluate
(
animation
);
});
_weight
=
(
animating
.
length
+
0.5
)
/
sum
;
}
else
{
_weight
=
1.0
;
}
}
double
_flex
(
Animation
<
double
>
animation
)
{
if
(
_isAnimating
(
animation
))
{
assert
(
_weight
!=
null
);
return
_flexTween
.
evaluate
(
animation
)
*
_weight
;
}
else
{
return
_flexTween
.
evaluate
(
animation
);
}
}
double
_xOffset
(
int
index
)
{
double
weightSum
(
Iterable
<
Animation
<
double
>>
animations
)
{
return
animations
.
map
(
// We're adding flex values instead of animation values to have correct
// ratios.
(
Animation
<
double
>
animation
)
=>
_flex
(
animation
)
).
fold
(
0.0
,
(
double
sum
,
double
value
)
=>
sum
+
value
);
}
final
double
allWeights
=
weightSum
(
animations
);
// This weight corresponds to the left edge of the indexed label.
final
double
leftWeights
=
weightSum
(
animations
.
sublist
(
0
,
index
));
// Add half of its flex value in order to get the center.
return
(
leftWeights
+
_flex
(
animations
[
index
])
/
2.0
)
/
allWeights
;
}
FractionalOffset
cirleOffset
(
int
index
)
{
final
double
iconSize
=
config
.
labels
[
index
].
icon
.
size
??
24.0
;
final
Tween
<
double
>
yOffsetTween
=
new
Tween
<
double
>(
begin:
(
18.0
+
iconSize
/
2.0
)
/
kBottomNavigationBarHeight
,
// 18dp + icon center
end:
(
6.0
+
iconSize
/
2.0
)
/
kBottomNavigationBarHeight
// 6dp + icon center
);
return
new
FractionalOffset
(
_xOffset
(
index
),
yOffsetTween
.
evaluate
(
animations
[
index
])
);
}
void
_pushCircle
(
int
index
)
{
if
(
config
.
labels
[
index
].
backgroundColor
!=
null
)
_circles
.
add
(
new
_Circle
(
state:
this
,
index:
index
,
color:
config
.
labels
[
index
].
backgroundColor
)..
controller
.
addStatusListener
((
AnimationStatus
status
)
{
if
(
status
==
AnimationStatus
.
completed
)
{
setState
(()
{
_Circle
circle
=
_circles
.
removeFirst
();
_backgroundColor
=
circle
.
color
;
circle
.
dispose
();
});
}
})
);
}
@override
void
didUpdateConfig
(
BottomNavigationBar
oldConfig
)
{
if
(
config
.
currentIndex
!=
oldConfig
.
currentIndex
)
{
if
(
config
.
type
==
BottomNavigationBarType
.
shifting
)
_pushCircle
(
config
.
currentIndex
);
_controllers
[
oldConfig
.
currentIndex
].
reverse
();
_controllers
[
config
.
currentIndex
].
forward
();
}
}
@override
Widget
build
(
BuildContext
context
)
{
Widget
bottomNavigation
;
switch
(
config
.
type
)
{
case
BottomNavigationBarType
.
fixed
:
final
List
<
Widget
>
children
=
<
Widget
>[];
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
ColorTween
colorTween
=
new
ColorTween
(
begin:
Colors
.
black54
,
end:
config
.
fixedColor
??
themeData
.
primaryColor
);
for
(
int
i
=
0
;
i
<
config
.
labels
.
length
;
i
+=
1
)
{
children
.
add
(
new
Flexible
(
child:
new
InkResponse
(
onTap:
()
{
if
(
config
.
onTap
!=
null
)
config
.
onTap
(
i
);
},
child:
new
Stack
(
alignment:
FractionalOffset
.
center
,
children:
<
Widget
>[
new
Align
(
alignment:
FractionalOffset
.
topCenter
,
child:
new
Container
(
margin:
new
EdgeInsets
.
only
(
top:
new
Tween
<
double
>(
begin:
8.0
,
end:
6.0
).
evaluate
(
animations
[
i
])
),
child:
new
IconTheme
(
data:
new
IconThemeData
(
color:
colorTween
.
evaluate
(
animations
[
i
])
),
child:
config
.
labels
[
i
].
icon
)
)
),
new
Align
(
alignment:
FractionalOffset
.
bottomCenter
,
child:
new
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
10.0
),
child:
new
DefaultTextStyle
(
style:
new
TextStyle
(
fontSize:
14.0
,
color:
colorTween
.
evaluate
(
animations
[
i
])
),
child:
new
Transform
(
transform:
new
Matrix4
.
diagonal3
(
new
Vector3
.
all
(
new
Tween
<
double
>(
begin:
0.85
,
end:
1.0
,
).
evaluate
(
animations
[
i
])
)),
alignment:
FractionalOffset
.
bottomCenter
,
child:
config
.
labels
[
i
].
title
)
)
)
)
]
)
)
)
);
}
bottomNavigation
=
new
SizedBox
(
width:
_maxWidth
,
child:
new
Row
(
children:
children
)
);
break
;
case
BottomNavigationBarType
.
shifting
:
final
List
<
Widget
>
children
=
<
Widget
>[];
_computeWeight
();
for
(
int
i
=
0
;
i
<
config
.
labels
.
length
;
i
+=
1
)
{
children
.
add
(
new
Flexible
(
// Since Flexible only supports integers, we're using large
// numbers in order to simulate floating point flex values.
flex:
(
_flex
(
animations
[
i
])
*
1000.0
).
round
(),
child:
new
InkResponse
(
onTap:
()
{
if
(
config
.
onTap
!=
null
)
config
.
onTap
(
i
);
},
child:
new
Stack
(
alignment:
FractionalOffset
.
center
,
children:
<
Widget
>[
new
Align
(
alignment:
FractionalOffset
.
topCenter
,
child:
new
Container
(
margin:
new
EdgeInsets
.
only
(
top:
new
Tween
<
double
>(
begin:
18.0
,
end:
6.0
).
evaluate
(
animations
[
i
])
),
child:
new
IconTheme
(
data:
new
IconThemeData
(
color:
Colors
.
white
),
child:
config
.
labels
[
i
].
icon
)
)
),
new
Align
(
alignment:
FractionalOffset
.
bottomCenter
,
child:
new
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
10.0
),
child:
new
FadeTransition
(
opacity:
animations
[
i
],
child:
new
DefaultTextStyle
(
style:
new
TextStyle
(
fontSize:
14.0
,
color:
Colors
.
white
),
child:
config
.
labels
[
i
].
title
)
)
)
)
]
)
)
)
);
}
bottomNavigation
=
new
SizedBox
(
width:
_maxWidth
,
child:
new
Row
(
children:
children
)
);
break
;
}
return
new
Stack
(
children:
<
Widget
>[
new
Positioned
.
fill
(
child:
new
Material
(
// Casts shadow.
elevation:
8
,
color:
_backgroundColor
)
),
new
SizedBox
(
height:
kBottomNavigationBarHeight
,
child:
new
Center
(
child:
new
Stack
(
children:
<
Widget
>[
new
Positioned
(
left:
0.0
,
top:
0.0
,
right:
0.0
,
bottom:
0.0
,
child:
new
CustomPaint
(
painter:
new
_RadialPainter
(
circles:
_circles
.
toList
()
)
)
),
new
Material
(
// Splashes.
type:
MaterialType
.
transparency
,
child:
new
Center
(
child:
bottomNavigation
)
)
]
)
)
)
]
);
}
}
class
_Circle
{
_Circle
({
this
.
state
,
this
.
index
,
this
.
color
})
{
assert
(
this
.
state
!=
null
);
assert
(
this
.
index
!=
null
);
assert
(
this
.
color
!=
null
);
controller
=
new
AnimationController
(
duration:
kThemeAnimationDuration
);
animation
=
new
CurvedAnimation
(
parent:
controller
,
curve:
Curves
.
fastOutSlowIn
);
controller
.
forward
();
}
final
BottomNavigationBarState
state
;
final
int
index
;
final
Color
color
;
AnimationController
controller
;
CurvedAnimation
animation
;
FractionalOffset
get
offset
{
return
state
.
cirleOffset
(
index
);
}
void
dispose
()
{
controller
.
dispose
();
}
}
class
_RadialPainter
extends
CustomPainter
{
_RadialPainter
({
this
.
circles
});
final
List
<
_Circle
>
circles
;
// Computes the maximum radius attainable such that at least one of the
// bounding rectangle's corners touches the egde of the circle. Drawing a
// circle beyond this radius is futile since there is no perceivable
// difference within the cropped rectangle.
double
_maxRadius
(
FractionalOffset
offset
,
Size
size
)
{
final
double
dx
=
offset
.
dx
;
final
double
dy
=
offset
.
dy
;
final
double
x
=
(
dx
>
0.5
?
dx
:
1.0
-
dx
)
*
size
.
width
;
final
double
y
=
(
dy
>
0.5
?
dy
:
1.0
-
dy
)
*
size
.
height
;
return
math
.
sqrt
(
x
*
x
+
y
*
y
);
}
@override
bool
shouldRepaint
(
_RadialPainter
oldPainter
)
{
if
(
circles
==
oldPainter
.
circles
)
return
false
;
if
(
circles
.
length
!=
oldPainter
.
circles
.
length
)
return
true
;
for
(
int
i
=
0
;
i
<
circles
.
length
;
i
+=
1
)
if
(
circles
[
i
]
!=
oldPainter
.
circles
[
i
])
return
true
;
return
false
;
}
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
for
(
_Circle
circle
in
circles
)
{
final
Tween
<
double
>
radiusTween
=
new
Tween
<
double
>(
begin:
0.0
,
end:
_maxRadius
(
circle
.
offset
,
size
)
);
final
Paint
paint
=
new
Paint
()..
color
=
circle
.
color
;
final
Rect
rect
=
new
Rect
.
fromLTWH
(
0.0
,
0.0
,
size
.
width
,
size
.
height
);
canvas
.
clipRect
(
rect
);
canvas
.
drawCircle
(
circle
.
offset
.
withinRect
(
rect
),
radiusTween
.
lerp
(
circle
.
animation
.
value
),
paint
);
}
}
}
packages/flutter/lib/src/material/constants.dart
View file @
3a6530d5
...
...
@@ -5,6 +5,9 @@
/// The height of the toolbar component of the [AppBar].
const
double
kToolbarHeight
=
56.0
;
/// The height of the bottom navigation bar.
const
double
kBottomNavigationBarHeight
=
60.0
;
/// The height of a tab bar containing text.
const
double
kTextTabBarHeight
=
48.0
;
...
...
packages/flutter/lib/src/material/scaffold.dart
View file @
3a6530d5
...
...
@@ -53,6 +53,7 @@ enum _ScaffoldSlot {
appBar
,
bottomSheet
,
snackBar
,
bottomNavigationBar
,
floatingActionButton
,
drawer
,
statusBar
,
...
...
@@ -80,7 +81,8 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
final
BoxConstraints
fullWidthConstraints
=
looseConstraints
.
tighten
(
width:
size
.
width
);
double
contentTop
=
padding
.
top
;
double
contentBottom
=
size
.
height
-
padding
.
bottom
;
double
bottom
=
size
.
height
-
padding
.
bottom
;
double
contentBottom
=
bottom
;
if
(
hasChild
(
_ScaffoldSlot
.
appBar
))
{
final
double
appBarHeight
=
layoutChild
(
_ScaffoldSlot
.
appBar
,
fullWidthConstraints
).
height
;
...
...
@@ -89,6 +91,12 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
positionChild
(
_ScaffoldSlot
.
appBar
,
Offset
.
zero
);
}
if
(
hasChild
(
_ScaffoldSlot
.
bottomNavigationBar
))
{
final
double
bottomNavigationBarHeight
=
layoutChild
(
_ScaffoldSlot
.
bottomNavigationBar
,
fullWidthConstraints
).
height
;
contentBottom
-=
bottomNavigationBarHeight
;
positionChild
(
_ScaffoldSlot
.
bottomNavigationBar
,
new
Offset
(
0.0
,
contentBottom
));
}
if
(
hasChild
(
_ScaffoldSlot
.
body
))
{
final
double
bodyHeight
=
contentBottom
-
contentTop
;
final
BoxConstraints
bodyConstraints
=
fullWidthConstraints
.
tighten
(
height:
bodyHeight
);
...
...
@@ -97,7 +105,10 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
}
// The BottomSheet and the SnackBar are anchored to the bottom of the parent,
// they're as wide as the parent and are given their intrinsic height.
// they're as wide as the parent and are given their intrinsic height. The
// only difference is that SnackBar appears on the top side of the
// BottomNavigationBar while the BottomSheet is stacked on top of it.
//
// If all three elements are present then either the center of the FAB straddles
// the top edge of the BottomSheet or the bottom of the FAB is
// _kFloatingActionButtonMargin above the SnackBar, whichever puts the FAB
...
...
@@ -110,7 +121,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
if
(
hasChild
(
_ScaffoldSlot
.
bottomSheet
))
{
bottomSheetSize
=
layoutChild
(
_ScaffoldSlot
.
bottomSheet
,
fullWidthConstraints
);
positionChild
(
_ScaffoldSlot
.
bottomSheet
,
new
Offset
((
size
.
width
-
bottomSheetSize
.
width
)
/
2.0
,
contentB
ottom
-
bottomSheetSize
.
height
));
positionChild
(
_ScaffoldSlot
.
bottomSheet
,
new
Offset
((
size
.
width
-
bottomSheetSize
.
width
)
/
2.0
,
b
ottom
-
bottomSheetSize
.
height
));
}
if
(
hasChild
(
_ScaffoldSlot
.
snackBar
))
{
...
...
@@ -265,6 +276,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// * [AppBar]
/// * [FloatingActionButton]
/// * [Drawer]
/// * [BottomNavigationBar]
/// * [SnackBar]
/// * [BottomSheet]
/// * [ScaffoldState]
...
...
@@ -281,6 +293,7 @@ class Scaffold extends StatefulWidget {
this
.
body
,
this
.
floatingActionButton
,
this
.
drawer
,
this
.
bottomNavigationBar
,
this
.
scrollableKey
,
this
.
appBarBehavior
:
AppBarBehavior
.
anchor
,
this
.
resizeToAvoidBottomPadding
:
true
...
...
@@ -306,6 +319,12 @@ class Scaffold extends StatefulWidget {
/// Typically a [Drawer].
final
Widget
drawer
;
/// A bottom navigation bar to display at the bottom of the scaffold.
///
/// Snack bars slide from underneath the botton navigation while bottom sheets
/// are stacked on top.
final
Widget
bottomNavigationBar
;
/// The key of the primary [Scrollable] widget in the [body].
///
/// Used to control scroll-linked effects, such as the collapse of the
...
...
@@ -759,7 +778,18 @@ class ScaffoldState extends State<Scaffold> {
}
else
{
children
.
add
(
new
LayoutId
(
child:
_buildScrollableAppBar
(
context
,
padding
),
id:
_ScaffoldSlot
.
appBar
));
}
// Otherwise the AppBar will be part of a [app bar, body] Stack. See AppBarBehavior.scroll below.
// Otherwise the AppBar will be part of a [app bar, body] Stack. See
// AppBarBehavior.scroll below.
if
(
_snackBars
.
isNotEmpty
)
_addIfNonNull
(
children
,
_snackBars
.
first
.
_widget
,
_ScaffoldSlot
.
snackBar
);
if
(
config
.
bottomNavigationBar
!=
null
)
{
children
.
add
(
new
LayoutId
(
id:
_ScaffoldSlot
.
bottomNavigationBar
,
child:
config
.
bottomNavigationBar
));
}
if
(
_currentBottomSheet
!=
null
||
_dismissedBottomSheets
.
isNotEmpty
)
{
final
List
<
Widget
>
bottomSheets
=
<
Widget
>[];
...
...
@@ -774,9 +804,6 @@ class ScaffoldState extends State<Scaffold> {
_addIfNonNull
(
children
,
stack
,
_ScaffoldSlot
.
bottomSheet
);
}
if
(
_snackBars
.
isNotEmpty
)
_addIfNonNull
(
children
,
_snackBars
.
first
.
_widget
,
_ScaffoldSlot
.
snackBar
);
children
.
add
(
new
LayoutId
(
id:
_ScaffoldSlot
.
floatingActionButton
,
child:
new
_FloatingActionButtonTransition
(
...
...
packages/flutter/test/material/bottom_navigation_bar_test.dart
0 → 100644
View file @
3a6530d5
// Copyright 2016 The Chromium 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_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'BottomNavigationBar callback test'
,
(
WidgetTester
tester
)
async
{
int
mutatedIndex
;
await
tester
.
pumpWidget
(
new
Scaffold
(
bottomNavigationBar:
new
BottomNavigationBar
(
labels:
<
DestinationLabel
>[
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
ac_unit
),
title:
new
Text
(
'AC'
)
),
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
access_alarm
),
title:
new
Text
(
'Alarm'
)
)
],
onTap:
(
int
index
)
{
mutatedIndex
=
index
;
}
)
)
);
await
tester
.
tap
(
find
.
text
(
'Alarm'
));
expect
(
mutatedIndex
,
1
);
});
testWidgets
(
'BottomNavigationBar content test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scaffold
(
bottomNavigationBar:
new
BottomNavigationBar
(
labels:
<
DestinationLabel
>[
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
ac_unit
),
title:
new
Text
(
'AC'
)
),
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
access_alarm
),
title:
new
Text
(
'Alarm'
)
)
]
)
)
);
RenderBox
box
=
tester
.
renderObject
(
find
.
byType
(
BottomNavigationBar
));
expect
(
box
.
size
.
height
,
60.0
);
expect
(
find
.
text
(
'AC'
),
findsOneWidget
);
expect
(
find
.
text
(
'Alarm'
),
findsOneWidget
);
});
testWidgets
(
'BottomNavigationBar action size test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scaffold
(
bottomNavigationBar:
new
BottomNavigationBar
(
type:
BottomNavigationBarType
.
shifting
,
labels:
<
DestinationLabel
>[
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
ac_unit
),
title:
new
Text
(
'AC'
)
),
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
access_alarm
),
title:
new
Text
(
'Alarm'
)
)
]
)
)
);
Iterable
<
RenderBox
>
actions
=
tester
.
renderObjectList
(
find
.
byType
(
InkResponse
));
expect
(
actions
.
length
,
2
);
expect
(
actions
.
elementAt
(
0
).
size
.
width
,
158.4
);
expect
(
actions
.
elementAt
(
1
).
size
.
width
,
105.6
);
await
tester
.
pumpWidget
(
new
Scaffold
(
bottomNavigationBar:
new
BottomNavigationBar
(
currentIndex:
1
,
type:
BottomNavigationBarType
.
shifting
,
labels:
<
DestinationLabel
>[
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
ac_unit
),
title:
new
Text
(
'AC'
)
),
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
access_alarm
),
title:
new
Text
(
'Alarm'
)
)
]
)
)
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
actions
=
tester
.
renderObjectList
(
find
.
byType
(
InkResponse
));
expect
(
actions
.
length
,
2
);
expect
(
actions
.
elementAt
(
0
).
size
.
width
,
105.6
);
expect
(
actions
.
elementAt
(
1
).
size
.
width
,
158.4
);
});
testWidgets
(
'BottomNavigationBar multiple taps test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scaffold
(
bottomNavigationBar:
new
BottomNavigationBar
(
type:
BottomNavigationBarType
.
shifting
,
labels:
<
DestinationLabel
>[
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
ac_unit
),
title:
new
Text
(
'AC'
)
),
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
access_alarm
),
title:
new
Text
(
'Alarm'
)
),
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
access_time
),
title:
new
Text
(
'Time'
)
),
new
DestinationLabel
(
icon:
new
Icon
(
Icons
.
add
),
title:
new
Text
(
'Add'
)
)
]
)
)
);
// We want to make sure that the last label does not get displaced,
// irrespective of how many taps happen on the first N - 1 labels and how
// they grow.
Iterable
<
RenderBox
>
actions
=
tester
.
renderObjectList
(
find
.
byType
(
InkResponse
));
Point
originalOrigin
=
actions
.
elementAt
(
3
).
localToGlobal
(
Point
.
origin
);
await
tester
.
tap
(
find
.
text
(
'AC'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
actions
=
tester
.
renderObjectList
(
find
.
byType
(
InkResponse
));
expect
(
actions
.
elementAt
(
3
).
localToGlobal
(
Point
.
origin
),
equals
(
originalOrigin
));
await
tester
.
tap
(
find
.
text
(
'Alarm'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
actions
=
tester
.
renderObjectList
(
find
.
byType
(
InkResponse
));
expect
(
actions
.
elementAt
(
3
).
localToGlobal
(
Point
.
origin
),
equals
(
originalOrigin
));
await
tester
.
tap
(
find
.
text
(
'Time'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
actions
=
tester
.
renderObjectList
(
find
.
byType
(
InkResponse
));
expect
(
actions
.
elementAt
(
3
).
localToGlobal
(
Point
.
origin
),
equals
(
originalOrigin
));
});
}
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