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
5ceaaeef
Commit
5ceaaeef
authored
Nov 18, 2016
by
Hans Muller
Committed by
GitHub
Nov 18, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add dense layout support to dropdown (#6906)
parent
b7f8ec66
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
165 additions
and
60 deletions
+165
-60
drop_down.dart
packages/flutter/lib/src/material/drop_down.dart
+45
-24
drop_down_test.dart
packages/flutter/test/material/drop_down_test.dart
+120
-36
No files found.
packages/flutter/lib/src/material/drop_down.dart
View file @
5ceaaeef
...
...
@@ -20,6 +20,7 @@ import 'material.dart';
const
Duration
_kDropdownMenuDuration
=
const
Duration
(
milliseconds:
300
);
const
double
_kMenuItemHeight
=
48.0
;
const
double
_kDenseButtonHeight
=
24.0
;
const
EdgeInsets
_kMenuVerticalPadding
=
const
EdgeInsets
.
symmetric
(
vertical:
8.0
);
const
EdgeInsets
_kMenuHorizontalPadding
=
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
);
...
...
@@ -226,7 +227,7 @@ class _DropdownMenuRouteLayout<T> extends SingleChildLayoutDelegate {
Offset
getPositionForChild
(
Size
size
,
Size
childSize
)
{
final
double
buttonTop
=
buttonRect
.
top
;
final
double
selectedItemOffset
=
selectedIndex
*
_kMenuItemHeight
+
_kMenuVerticalPadding
.
top
;
double
top
=
buttonTop
-
selectedItemOffset
;
double
top
=
(
buttonTop
-
selectedItemOffset
)
-
(
_kMenuItemHeight
-
buttonRect
.
height
)
/
2.0
;
final
double
topPreferredLimit
=
_kMenuItemHeight
;
if
(
top
<
topPreferredLimit
)
top
=
math
.
min
(
buttonTop
,
topPreferredLimit
);
...
...
@@ -403,8 +404,9 @@ class DropdownButtonHideUnderline extends InheritedWidget {
///
/// See also:
///
/// * [RaisedButton]
/// * [FlatButton]
/// * [DropdownButtonHideUnderline], which prevents its descendant drop down buttons
/// from displaying their underlines.
/// * [RaisedButton], [FlatButton], ordinary buttons that trigger a single action.
/// * <https://material.google.com/components/buttons.html#buttons-dropdown-buttons>
class
DropdownButton
<
T
>
extends
StatefulWidget
{
/// Creates a dropdown button.
...
...
@@ -420,7 +422,8 @@ class DropdownButton<T> extends StatefulWidget {
@required
this
.
onChanged
,
this
.
elevation
:
8
,
this
.
style
,
this
.
iconSize
:
24.0
this
.
iconSize
:
24.0
,
this
.
isDense
:
false
,
})
:
super
(
key:
key
)
{
assert
(
items
!=
null
);
assert
(
items
.
where
((
DropdownMenuItem
<
T
>
item
)
=>
item
.
value
==
value
).
length
==
1
);
...
...
@@ -454,6 +457,14 @@ class DropdownButton<T> extends StatefulWidget {
/// Defaults to 24.0.
final
double
iconSize
;
/// Reduce the button's height.
///
/// By default this button's height is the same as its menu items' heights.
/// If isDense is true, the button's height is reduced by about half. This
/// can be useful when the button is embedded in a container that adds
/// its own decorations, like [InputContainer].
final
bool
isDense
;
@override
_DropdownButtonState
<
T
>
createState
()
=>
new
_DropdownButtonState
<
T
>();
}
...
...
@@ -466,7 +477,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> {
assert
(
_selectedIndex
!=
null
);
}
@override
@override
void
didUpdateConfig
(
DropdownButton
<
T
>
oldConfig
)
{
if
(
config
.
items
[
_selectedIndex
].
value
!=
config
.
value
)
_updateSelectedIndex
();
...
...
@@ -503,39 +514,49 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> {
});
}
// When isDense is true, reduce the height of this button from _kMenuItemHeight to
// _kDenseButtonHeight, but don't make it smaller than the text that it contains.
// Similarly, we don't reduce the height of the button so much that its icon
// would be clipped.
double
get
_denseButtonHeight
{
return
math
.
max
(
_textStyle
.
fontSize
,
math
.
max
(
config
.
iconSize
,
_kDenseButtonHeight
));
}
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMaterial
(
context
));
Widget
result
=
new
DefaultTextStyle
(
style:
_textStyle
,
child:
new
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
// We use an IndexedStack to make sure we have enough width to show any
// possible item as the selected item without changing size.
new
IndexedStack
(
index:
_selectedIndex
,
alignment:
FractionalOffset
.
centerLeft
,
children:
config
.
items
),
new
Icon
(
Icons
.
arrow_drop_down
,
size:
config
.
iconSize
,
// These colors are not defined in the Material Design spec.
color:
Theme
.
of
(
context
).
brightness
==
Brightness
.
light
?
Colors
.
grey
[
700
]
:
Colors
.
white70
)
]
)
child:
new
SizedBox
(
height:
config
.
isDense
?
_denseButtonHeight
:
null
,
child:
new
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
new
IndexedStack
(
index:
_selectedIndex
,
alignment:
FractionalOffset
.
centerLeft
,
children:
config
.
items
),
new
Icon
(
Icons
.
arrow_drop_down
,
size:
config
.
iconSize
,
// These colors are not defined in the Material Design spec.
color:
Theme
.
of
(
context
).
brightness
==
Brightness
.
light
?
Colors
.
grey
[
700
]
:
Colors
.
white70
),
],
),
),
);
if
(!
DropdownButtonHideUnderline
.
at
(
context
))
{
final
double
bottom
=
config
.
isDense
?
0.0
:
8.0
;
result
=
new
Stack
(
children:
<
Widget
>[
result
,
new
Positioned
(
left:
0.0
,
right:
0.0
,
bottom:
8.0
,
bottom:
bottom
,
child:
new
Container
(
height:
1.0
,
decoration:
const
BoxDecoration
(
...
...
packages/flutter/test/material/drop_down_test.dart
View file @
5ceaaeef
...
...
@@ -2,36 +2,62 @@
// 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
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
Widget
buildFrame
(
{
Key
buttonKey
,
String
value:
'two'
,
ValueChanged
<
String
>
onChanged
,
bool
isDense:
false
})
{
final
List
<
String
>
items
=
<
String
>[
'one'
,
'two'
,
'three'
,
'four'
];
return
new
MaterialApp
(
home:
new
Material
(
child:
new
Center
(
child:
new
DropdownButton
<
String
>(
key:
buttonKey
,
value:
value
,
onChanged:
onChanged
,
isDense:
isDense
,
items:
items
.
map
((
String
item
)
{
return
new
DropdownMenuItem
<
String
>(
key:
new
ValueKey
<
String
>(
item
),
value:
item
,
child:
new
Text
(
item
,
key:
new
ValueKey
<
String
>(
item
+
"Text"
)),
);
}).
toList
(),
),
),
),
);
}
// When the dropdown's menu is popped up, a RenderParagraph for the selected
// menu's text item will appear both in the dropdown button and in the menu.
// The RenderParagraphs should be aligned, i.e. they should have the same
// size and location.
void
checkSelectedItemTextGeometry
(
WidgetTester
tester
,
String
value
)
{
final
List
<
RenderBox
>
boxes
=
tester
.
renderObjectList
(
find
.
byKey
(
new
ValueKey
<
String
>(
value
+
'Text'
))).
toList
();
expect
(
boxes
.
length
,
equals
(
2
));
final
RenderBox
box0
=
boxes
[
0
];
final
RenderBox
box1
=
boxes
[
1
];
expect
(
box0
.
localToGlobal
(
Point
.
origin
),
equals
(
box1
.
localToGlobal
(
Point
.
origin
)));
expect
(
box0
.
size
,
equals
(
box1
.
size
));
}
bool
sameGeometry
(
RenderBox
box1
,
RenderBox
box2
)
{
expect
(
box1
.
localToGlobal
(
Point
.
origin
),
equals
(
box2
.
localToGlobal
(
Point
.
origin
)));
expect
(
box1
.
size
.
height
,
equals
(
box2
.
size
.
height
));
return
true
;
}
void
main
(
)
{
testWidgets
(
'Drop down button control test'
,
(
WidgetTester
tester
)
async
{
List
<
String
>
items
=
<
String
>[
'one'
,
'two'
,
'three'
,
'four'
];
String
value
=
items
.
first
;
String
value
=
'one'
;
void
didChangeValue
(
String
newValue
)
{
value
=
newValue
;
}
Widget
build
()
{
return
new
MaterialApp
(
home:
new
Material
(
child:
new
Center
(
child:
new
DropdownButton
<
String
>(
value:
value
,
items:
items
.
map
((
String
item
)
{
return
new
DropdownMenuItem
<
String
>(
value:
item
,
child:
new
Text
(
item
),
);
}).
toList
(),
onChanged:
didChangeValue
,
),
),
),
);
}
Widget
build
()
=>
buildFrame
(
value:
value
,
onChanged:
didChangeValue
);
await
tester
.
pumpWidget
(
build
());
...
...
@@ -65,9 +91,7 @@ void main() {
});
testWidgets
(
'Drop down button with no app'
,
(
WidgetTester
tester
)
async
{
List
<
String
>
items
=
<
String
>[
'one'
,
'two'
,
'three'
,
'four'
];
String
value
=
items
.
first
;
String
value
=
'one'
;
void
didChangeValue
(
String
newValue
)
{
value
=
newValue
;
}
...
...
@@ -80,18 +104,7 @@ void main() {
settings:
settings
,
builder:
(
BuildContext
context
)
{
return
new
Material
(
child:
new
Center
(
child:
new
DropdownButton
<
String
>(
value:
value
,
items:
items
.
map
((
String
item
)
{
return
new
DropdownMenuItem
<
String
>(
value:
item
,
child:
new
Text
(
item
),
);
}).
toList
(),
onChanged:
didChangeValue
,
),
)
child:
buildFrame
(
value:
'one'
,
onChanged:
didChangeValue
),
);
},
);
...
...
@@ -181,4 +194,75 @@ void main() {
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the menu animation
});
testWidgets
(
'Drop down button aligns selected menu item'
,
(
WidgetTester
tester
)
async
{
Key
buttonKey
=
new
UniqueKey
();
String
value
=
'two'
;
Widget
build
()
=>
buildFrame
(
buttonKey:
buttonKey
,
value:
value
);
await
tester
.
pumpWidget
(
build
());
RenderBox
buttonBox
=
tester
.
renderObject
(
find
.
byKey
(
buttonKey
));
assert
(
buttonBox
.
attached
);
Point
buttonOriginBeforeTap
=
buttonBox
.
localToGlobal
(
Point
.
origin
);
await
tester
.
tap
(
find
.
text
(
'two'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the menu animation
// Tapping the dropdown button should not cause it to move.
expect
(
buttonBox
.
localToGlobal
(
Point
.
origin
),
equals
(
buttonOriginBeforeTap
));
// The selected dropdown item is both in menu we just popped up, and in
// the IndexedStack contained by the dropdown button. Both of them should
// have the same origin and height as the dropdown button.
List
<
RenderObject
>
itemBoxes
=
tester
.
renderObjectList
(
find
.
byKey
(
new
ValueKey
<
String
>(
'two'
))).
toList
();
expect
(
itemBoxes
.
length
,
equals
(
2
));
for
(
RenderBox
itemBox
in
itemBoxes
)
{
assert
(
itemBox
.
attached
);
expect
(
buttonBox
.
localToGlobal
(
Point
.
origin
),
equals
(
itemBox
.
localToGlobal
(
Point
.
origin
)));
expect
(
buttonBox
.
size
.
height
,
equals
(
itemBox
.
size
.
height
));
}
// The two RenderParagraph objects, for the 'two' items' Text children,
// should have the same size and location.
checkSelectedItemTextGeometry
(
tester
,
'two'
);
});
testWidgets
(
'Drop down button with isDense:true aligns selected menu item'
,
(
WidgetTester
tester
)
async
{
Key
buttonKey
=
new
UniqueKey
();
String
value
=
'two'
;
Widget
build
()
=>
buildFrame
(
buttonKey:
buttonKey
,
value:
value
,
isDense:
true
);
await
tester
.
pumpWidget
(
build
());
RenderBox
buttonBox
=
tester
.
renderObject
(
find
.
byKey
(
buttonKey
));
assert
(
buttonBox
.
attached
);
await
tester
.
tap
(
find
.
text
(
'two'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the menu animation
// The selected dropdown item is both in menu we just popped up, and in
// the IndexedStack contained by the dropdown button. Both of them should
// have the same vertical center as the button.
List
<
RenderBox
>
itemBoxes
=
tester
.
renderObjectList
(
find
.
byKey
(
new
ValueKey
<
String
>(
'two'
))).
toList
();
expect
(
itemBoxes
.
length
,
equals
(
2
));
// When isDense is true, the button's height is reduced. The menu items'
// heights are not.
double
menuItemHeight
=
itemBoxes
.
map
((
RenderBox
box
)
=>
box
.
size
.
height
).
reduce
(
math
.
max
);
expect
(
menuItemHeight
,
greaterThan
(
buttonBox
.
size
.
height
));
for
(
RenderBox
itemBox
in
itemBoxes
)
{
assert
(
itemBox
.
attached
);
Point
buttonBoxCenter
=
buttonBox
.
size
.
center
(
buttonBox
.
localToGlobal
(
Point
.
origin
));
Point
itemBoxCenter
=
itemBox
.
size
.
center
(
itemBox
.
localToGlobal
(
Point
.
origin
));
expect
(
buttonBoxCenter
.
y
,
equals
(
itemBoxCenter
.
y
));
}
// The two RenderParagraph objects, for the 'two' items' Text children,
// should have the same size and location.
checkSelectedItemTextGeometry
(
tester
,
'two'
);
});
}
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