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
ee019c0b
Unverified
Commit
ee019c0b
authored
May 14, 2018
by
Hans Muller
Committed by
GitHub
May 14, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Updated ListTile layout (#17496)
parent
4931b467
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
735 additions
and
78 deletions
+735
-78
list_tile.dart
packages/flutter/lib/src/material/list_tile.dart
+558
-63
list_tile_test.dart
packages/flutter/test/material/list_tile_test.dart
+177
-15
No files found.
packages/flutter/lib/src/material/list_tile.dart
View file @
ee019c0b
...
...
@@ -2,7 +2,11 @@
// 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/widgets.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'colors.dart'
;
import
'constants.dart'
;
...
...
@@ -42,6 +46,7 @@ class ListTileTheme extends InheritedWidget {
this
.
selectedColor
,
this
.
iconColor
,
this
.
textColor
,
this
.
contentPadding
,
Widget
child
,
})
:
super
(
key:
key
,
child:
child
);
...
...
@@ -56,6 +61,7 @@ class ListTileTheme extends InheritedWidget {
Color
selectedColor
,
Color
iconColor
,
Color
textColor
,
EdgeInsetsGeometry
contentPadding
,
@required
Widget
child
,
})
{
assert
(
child
!=
null
);
...
...
@@ -69,6 +75,7 @@ class ListTileTheme extends InheritedWidget {
selectedColor:
selectedColor
??
parent
.
selectedColor
,
iconColor:
iconColor
??
parent
.
iconColor
,
textColor:
textColor
??
parent
.
textColor
,
contentPadding:
contentPadding
??
parent
.
contentPadding
,
child:
child
,
);
},
...
...
@@ -90,6 +97,12 @@ class ListTileTheme extends InheritedWidget {
/// If specified, the text color used for enabled [ListTile]s that are not selected.
final
Color
textColor
;
/// The tile's internal padding.
///
/// Insets a [ListTile]'s contents: its [leading], [title], [subtitle],
/// and [trailing] widgets.
final
EdgeInsetsGeometry
contentPadding
;
/// The closest instance of this class that encloses the given context.
///
/// Typical usage is as follows:
...
...
@@ -108,7 +121,8 @@ class ListTileTheme extends InheritedWidget {
||
style
!=
oldWidget
.
style
||
selectedColor
!=
oldWidget
.
selectedColor
||
iconColor
!=
oldWidget
.
iconColor
||
textColor
!=
oldWidget
.
textColor
;
||
textColor
!=
oldWidget
.
textColor
||
contentPadding
!=
oldWidget
.
contentPadding
;
}
}
...
...
@@ -210,6 +224,7 @@ class ListTile extends StatelessWidget {
this
.
trailing
,
this
.
isThreeLine
:
false
,
this
.
dense
,
this
.
contentPadding
,
this
.
enabled
:
true
,
this
.
onTap
,
this
.
onLongPress
,
...
...
@@ -251,6 +266,14 @@ class ListTile extends StatelessWidget {
/// If this property is null then its value is based on [ListTileTheme.dense].
final
bool
dense
;
/// The tile's internal padding.
///
/// Insets a [ListTile]'s contents: its [leading], [title], [subtitle],
/// and [trailing] widgets.
///
/// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used.
final
EdgeInsetsGeometry
contentPadding
;
/// Whether this list tile is interactive.
///
/// If false, this list tile is styled with the disabled color from the
...
...
@@ -347,7 +370,7 @@ class ListTile extends StatelessWidget {
return
defaultColor
;
}
bool
_
d
enseLayout
(
ListTileTheme
tileTheme
)
{
bool
_
isD
enseLayout
(
ListTileTheme
tileTheme
)
{
return
dense
!=
null
?
dense
:
(
tileTheme
?.
dense
??
false
);
}
...
...
@@ -366,7 +389,7 @@ class ListTile extends StatelessWidget {
style
=
theme
.
textTheme
.
subhead
;
}
final
Color
color
=
_textColor
(
theme
,
tileTheme
,
style
.
color
);
return
_
d
enseLayout
(
tileTheme
)
return
_
isD
enseLayout
(
tileTheme
)
?
style
.
copyWith
(
fontSize:
13.0
,
color:
color
)
:
style
.
copyWith
(
color:
color
);
}
...
...
@@ -374,7 +397,7 @@ class ListTile extends StatelessWidget {
TextStyle
_subtitleTextStyle
(
ThemeData
theme
,
ListTileTheme
tileTheme
)
{
final
TextStyle
style
=
theme
.
textTheme
.
body1
;
final
Color
color
=
_textColor
(
theme
,
tileTheme
,
theme
.
textTheme
.
caption
.
color
);
return
_
d
enseLayout
(
tileTheme
)
return
_
isD
enseLayout
(
tileTheme
)
?
style
.
copyWith
(
color:
color
,
fontSize:
12.0
)
:
style
.
copyWith
(
color:
color
);
}
...
...
@@ -385,91 +408,563 @@ class ListTile extends StatelessWidget {
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ListTileTheme
tileTheme
=
ListTileTheme
.
of
(
context
);
final
bool
isTwoLine
=
!
isThreeLine
&&
subtitle
!=
null
;
final
bool
isOneLine
=
!
isThreeLine
&&
!
isTwoLine
;
double
tileHeight
;
if
(
isOneLine
)
tileHeight
=
_denseLayout
(
tileTheme
)
?
48.0
:
56.0
;
else
if
(
isTwoLine
)
tileHeight
=
_denseLayout
(
tileTheme
)
?
60.0
:
72.0
;
else
tileHeight
=
_denseLayout
(
tileTheme
)
?
76.0
:
88.0
;
// Overall, the list tile is a Row() with these children.
final
List
<
Widget
>
children
=
<
Widget
>[];
IconThemeData
iconThemeData
;
if
(
leading
!=
null
||
trailing
!=
null
)
iconThemeData
=
new
IconThemeData
(
color:
_iconColor
(
theme
,
tileTheme
));
Widget
leadingIcon
;
if
(
leading
!=
null
)
{
children
.
add
(
IconTheme
.
merge
(
leadingIcon
=
IconTheme
.
merge
(
data:
iconThemeData
,
child:
new
Container
(
margin:
const
EdgeInsetsDirectional
.
only
(
end:
16.0
),
width:
40.0
,
alignment:
AlignmentDirectional
.
centerStart
,
child:
leading
,
),
));
);
}
final
Widget
primaryLine
=
new
AnimatedDefaultTextStyle
(
final
Widget
titleText
=
new
AnimatedDefaultTextStyle
(
style:
_titleTextStyle
(
theme
,
tileTheme
),
duration:
kThemeChangeDuration
,
child:
title
??
new
Container
()
child:
title
??
const
SizedBox
()
);
Widget
center
=
primaryLine
;
if
(
subtitle
!=
null
&&
(
isTwoLine
||
isThreeLine
))
{
center
=
new
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
primaryLine
,
new
AnimatedDefaultTextStyle
(
Widget
subtitleText
;
if
(
subtitle
!=
null
)
{
subtitleText
=
new
AnimatedDefaultTextStyle
(
style:
_subtitleTextStyle
(
theme
,
tileTheme
),
duration:
kThemeChangeDuration
,
child:
subtitle
,
),
],
);
}
children
.
add
(
new
Expanded
(
child:
center
,
));
Widget
trailingIcon
;
if
(
trailing
!=
null
)
{
children
.
add
(
IconTheme
.
merge
(
trailingIcon
=
IconTheme
.
merge
(
data:
iconThemeData
,
child:
new
Container
(
margin:
const
EdgeInsetsDirectional
.
only
(
start:
16.0
),
alignment:
AlignmentDirectional
.
centerEnd
,
child:
trailing
,
),
));
);
}
const
EdgeInsets
_kDefaultContentPadding
=
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
);
final
TextDirection
textDirection
=
Directionality
.
of
(
context
);
final
EdgeInsets
resolvedContentPadding
=
contentPadding
?.
resolve
(
textDirection
)
??
tileTheme
?.
contentPadding
?.
resolve
(
textDirection
)
??
_kDefaultContentPadding
;
return
new
InkWell
(
onTap:
enabled
?
onTap
:
null
,
onLongPress:
enabled
?
onLongPress
:
null
,
child:
new
Semantics
(
selected:
selected
,
enabled:
enabled
,
child:
new
ConstrainedBox
(
constraints:
new
BoxConstraints
(
minHeight:
tileHeight
),
child:
new
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
),
child:
new
UnconstrainedBox
(
constrainedAxis:
Axis
.
horizontal
,
child:
new
SafeArea
(
top:
false
,
bottom:
false
,
child:
new
Row
(
children:
children
),
),
minimum:
resolvedContentPadding
,
child:
new
_ListTile
(
leading:
leadingIcon
,
title:
titleText
,
subtitle:
subtitleText
,
trailing:
trailingIcon
,
isDense:
_isDenseLayout
(
tileTheme
),
isThreeLine:
isThreeLine
,
),
)
),
),
);
}
}
class
_ListTile
extends
RenderObjectWidget
{
const
_ListTile
({
Key
key
,
this
.
leading
,
this
.
title
,
this
.
subtitle
,
this
.
trailing
,
this
.
isThreeLine
,
this
.
isDense
,
})
:
super
(
key:
key
);
final
Widget
leading
;
final
Widget
title
;
final
Widget
subtitle
;
final
Widget
trailing
;
final
bool
isThreeLine
;
final
bool
isDense
;
@override
_RenderListTileElement
createElement
()
=>
new
_RenderListTileElement
(
this
);
@override
_RenderListTile
createRenderObject
(
BuildContext
context
)
{
return
new
_RenderListTile
(
isThreeLine:
isThreeLine
,
isDense:
isDense
,
textDirection:
Directionality
.
of
(
context
),
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderListTile
renderObject
)
{
renderObject
..
isThreeLine
=
isThreeLine
..
isDense
=
isDense
..
textDirection
=
Directionality
.
of
(
context
);
}
}
// Identifies the children of a _ListTileElement.
enum
_ListTileSlot
{
leading
,
title
,
subtitle
,
trailing
,
}
class
_RenderListTile
extends
RenderBox
{
_RenderListTile
({
bool
isDense
,
bool
isThreeLine
,
TextDirection
textDirection
,
})
:
_isDense
=
isDense
,
_isThreeLine
=
isThreeLine
,
_textDirection
=
textDirection
;
static
const
double
_kMinLeadingWidth
=
40.0
;
static
const
double
_kTitleGap
=
16.0
;
// between the titles and the leading/trailing widgets
final
Map
<
_ListTileSlot
,
RenderBox
>
slotToChild
=
<
_ListTileSlot
,
RenderBox
>{};
final
Map
<
RenderBox
,
_ListTileSlot
>
childToSlot
=
<
RenderBox
,
_ListTileSlot
>{};
RenderBox
_updateChild
(
RenderBox
oldChild
,
RenderBox
newChild
,
_ListTileSlot
slot
)
{
if
(
oldChild
!=
null
)
{
dropChild
(
oldChild
);
childToSlot
.
remove
(
oldChild
);
slotToChild
.
remove
(
slot
);
}
if
(
newChild
!=
null
)
{
childToSlot
[
newChild
]
=
slot
;
slotToChild
[
slot
]
=
newChild
;
adoptChild
(
newChild
);
}
return
newChild
;
}
RenderBox
_leading
;
RenderBox
get
leading
=>
_leading
;
set
leading
(
RenderBox
value
)
{
_leading
=
_updateChild
(
_leading
,
value
,
_ListTileSlot
.
leading
);
}
RenderBox
_title
;
RenderBox
get
title
=>
_title
;
set
title
(
RenderBox
value
)
{
_title
=
_updateChild
(
_title
,
value
,
_ListTileSlot
.
title
);
}
RenderBox
_subtitle
;
RenderBox
get
subtitle
=>
_subtitle
;
set
subtitle
(
RenderBox
value
)
{
_subtitle
=
_updateChild
(
_subtitle
,
value
,
_ListTileSlot
.
subtitle
);
}
RenderBox
_trailing
;
RenderBox
get
trailing
=>
_trailing
;
set
trailing
(
RenderBox
value
)
{
_trailing
=
_updateChild
(
_trailing
,
value
,
_ListTileSlot
.
trailing
);
}
// The returned list is ordered for hit testing.
Iterable
<
RenderBox
>
get
_children
sync
*{
if
(
leading
!=
null
)
yield
leading
;
if
(
title
!=
null
)
yield
title
;
if
(
subtitle
!=
null
)
yield
subtitle
;
if
(
trailing
!=
null
)
yield
trailing
;
}
bool
get
isDense
=>
_isDense
;
bool
_isDense
;
set
isDense
(
bool
value
)
{
if
(
_isDense
==
value
)
return
;
_isDense
=
value
;
markNeedsLayout
();
}
bool
get
isThreeLine
=>
_isThreeLine
;
bool
_isThreeLine
;
set
isThreeLine
(
bool
value
)
{
if
(
_isThreeLine
==
value
)
return
;
_isThreeLine
=
value
;
markNeedsLayout
();
}
TextDirection
get
textDirection
=>
_textDirection
;
TextDirection
_textDirection
;
set
textDirection
(
TextDirection
value
)
{
if
(
_textDirection
==
value
)
return
;
_textDirection
=
value
;
markNeedsLayout
();
}
@override
void
attach
(
PipelineOwner
owner
)
{
super
.
attach
(
owner
);
for
(
RenderBox
child
in
_children
)
child
.
attach
(
owner
);
}
@override
void
detach
()
{
super
.
detach
();
for
(
RenderBox
child
in
_children
)
child
.
detach
();
}
@override
void
redepthChildren
()
{
_children
.
forEach
(
redepthChild
);
}
@override
void
visitChildren
(
RenderObjectVisitor
visitor
)
{
_children
.
forEach
(
visitor
);
}
@override
List
<
DiagnosticsNode
>
debugDescribeChildren
()
{
final
List
<
DiagnosticsNode
>
value
=
<
DiagnosticsNode
>[];
void
add
(
RenderBox
child
,
String
name
)
{
if
(
child
!=
null
)
value
.
add
(
child
.
toDiagnosticsNode
(
name:
name
));
}
add
(
leading
,
'leading'
);
add
(
title
,
'title'
);
add
(
subtitle
,
'subtitle'
);
add
(
trailing
,
'trailing'
);
return
value
;
}
@override
bool
get
sizedByParent
=>
false
;
static
double
_minWidth
(
RenderBox
box
,
double
height
)
{
return
box
==
null
?
0.0
:
box
.
getMinIntrinsicWidth
(
height
);
}
static
double
_maxWidth
(
RenderBox
box
,
double
height
)
{
return
box
==
null
?
0.0
:
box
.
getMaxIntrinsicWidth
(
height
);
}
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
final
double
leadingWidth
=
leading
!=
null
?
math
.
max
(
leading
.
getMinIntrinsicWidth
(
height
),
_kMinLeadingWidth
)
+
_kTitleGap
:
0.0
;
return
leadingWidth
+
math
.
max
(
_minWidth
(
title
,
height
),
_minWidth
(
subtitle
,
height
))
+
_maxWidth
(
trailing
,
height
);
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
final
double
leadingWidth
=
leading
!=
null
?
math
.
max
(
leading
.
getMaxIntrinsicWidth
(
height
),
_kMinLeadingWidth
)
+
_kTitleGap
:
0.0
;
return
leadingWidth
+
math
.
max
(
_maxWidth
(
title
,
height
),
_maxWidth
(
subtitle
,
height
))
+
_maxWidth
(
trailing
,
height
);
}
double
get
_defaultTileHeight
{
final
bool
hasSubtitle
=
subtitle
!=
null
;
final
bool
isTwoLine
=
!
isThreeLine
&&
hasSubtitle
;
final
bool
isOneLine
=
!
isThreeLine
&&
!
hasSubtitle
;
if
(
isOneLine
)
return
isDense
?
48.0
:
56.0
;
else
if
(
isTwoLine
)
return
isDense
?
64.0
:
72.0
;
else
return
isDense
?
76.0
:
88.0
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
return
math
.
max
(
_defaultTileHeight
,
title
.
getMinIntrinsicHeight
(
width
)
+
(
subtitle
?.
getMinIntrinsicHeight
(
width
)
??
0.0
)
);
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
return
computeMinIntrinsicHeight
(
width
);
}
@override
double
computeDistanceToActualBaseline
(
TextBaseline
baseline
)
{
assert
(
title
!=
null
);
final
BoxParentData
parentData
=
title
.
parentData
;
return
parentData
.
offset
.
dy
+
title
.
getDistanceToBaseline
(
TextBaseline
.
alphabetic
);
}
static
double
_boxBaseline
(
RenderBox
box
)
{
return
box
.
getDistanceToBaseline
(
TextBaseline
.
alphabetic
);
}
static
Size
_layoutBox
(
RenderBox
box
,
BoxConstraints
constraints
)
{
if
(
box
==
null
)
return
Size
.
zero
;
box
.
layout
(
constraints
,
parentUsesSize:
true
);
return
box
.
size
;
}
static
void
_positionBox
(
RenderBox
box
,
Offset
offset
)
{
final
BoxParentData
parentData
=
box
.
parentData
;
parentData
.
offset
=
offset
;
}
// All of the dimensions below were taken from the Material Design spec:
// https://material.io/design/components/lists.html#specs
@override
void
performLayout
()
{
final
bool
hasLeading
=
leading
!=
null
;
final
bool
hasSubtitle
=
subtitle
!=
null
;
final
bool
hasTrailing
=
trailing
!=
null
;
final
bool
isTwoLine
=
!
isThreeLine
&&
hasSubtitle
;
final
bool
isOneLine
=
!
isThreeLine
&&
!
hasSubtitle
;
final
BoxConstraints
looseConstraints
=
constraints
.
loosen
();
final
double
tileWidth
=
looseConstraints
.
maxWidth
;
final
Size
leadingSize
=
_layoutBox
(
leading
,
looseConstraints
);
final
Size
trailingSize
=
_layoutBox
(
trailing
,
looseConstraints
);
final
double
titleStart
=
hasLeading
?
math
.
max
(
_kMinLeadingWidth
,
leadingSize
.
width
)
+
_kTitleGap
:
0.0
;
final
BoxConstraints
textConstraints
=
looseConstraints
.
tighten
(
width:
tileWidth
-
titleStart
-
(
hasTrailing
?
trailingSize
.
width
+
_kTitleGap
:
0.0
),
);
final
Size
titleSize
=
_layoutBox
(
title
,
textConstraints
);
final
Size
subtitleSize
=
_layoutBox
(
subtitle
,
textConstraints
);
double
titleBaseline
;
double
subtitleBaseline
;
if
(
isTwoLine
)
{
titleBaseline
=
isDense
?
28.0
:
32.0
;
subtitleBaseline
=
isDense
?
48.0
:
52.0
;
}
else
if
(
isThreeLine
)
{
titleBaseline
=
isDense
?
22.0
:
28.0
;
subtitleBaseline
=
isDense
?
42.0
:
48.0
;
}
else
{
assert
(
isOneLine
);
}
double
tileHeight
;
double
titleY
;
double
subtitleY
;
if
(!
hasSubtitle
)
{
tileHeight
=
math
.
max
(
_defaultTileHeight
,
titleSize
.
height
);
titleY
=
(
tileHeight
-
titleSize
.
height
)
/
2.0
;
}
else
{
titleY
=
titleBaseline
-
_boxBaseline
(
title
);
subtitleY
=
subtitleBaseline
-
_boxBaseline
(
subtitle
);
tileHeight
=
_defaultTileHeight
;
// If the title and subtitle overlap, move the title upwards by half
// the overlap and the subtitle down by the same amount, and adjust
// tileHeight so that both titles fit.
final
double
titleOverlap
=
titleY
+
titleSize
.
height
-
subtitleY
;
if
(
titleOverlap
>
0.0
)
{
titleY
-=
titleOverlap
/
2.0
;
subtitleY
+=
titleOverlap
/
2.0
;
}
// If the title or subtitle overflow tileHeight then punt: title
// and subtitle are arranged in a column, tileHeight = column height.
if
(
titleY
<
0.0
||
subtitleY
>
tileHeight
)
{
tileHeight
=
titleSize
.
height
+
subtitleSize
.
height
;
titleY
=
0.0
;
subtitleY
=
titleSize
.
height
;
}
}
final
double
leadingY
=
(
tileHeight
-
leadingSize
.
height
)
/
2.0
;
final
double
trailingY
=
(
tileHeight
-
trailingSize
.
height
)
/
2.0
;
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
{
if
(
hasLeading
)
_positionBox
(
leading
,
new
Offset
(
tileWidth
-
leadingSize
.
width
,
leadingY
));
final
double
titleX
=
hasTrailing
?
trailingSize
.
width
+
_kTitleGap
:
0.0
;
_positionBox
(
title
,
new
Offset
(
titleX
,
titleY
));
if
(
hasSubtitle
)
_positionBox
(
subtitle
,
new
Offset
(
titleX
,
subtitleY
));
if
(
hasTrailing
)
_positionBox
(
trailing
,
new
Offset
(
0.0
,
trailingY
));
break
;
}
case
TextDirection
.
ltr
:
{
if
(
hasLeading
)
_positionBox
(
leading
,
new
Offset
(
0.0
,
leadingY
));
_positionBox
(
title
,
new
Offset
(
titleStart
,
titleY
));
if
(
hasSubtitle
)
_positionBox
(
subtitle
,
new
Offset
(
titleStart
,
subtitleY
));
if
(
hasTrailing
)
_positionBox
(
trailing
,
new
Offset
(
tileWidth
-
trailingSize
.
width
,
trailingY
));
break
;
}
}
size
=
constraints
.
constrain
(
new
Size
(
tileWidth
,
tileHeight
));
assert
(
size
.
width
==
constraints
.
constrainWidth
(
tileWidth
));
assert
(
size
.
height
==
constraints
.
constrainHeight
(
tileHeight
));
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
void
doPaint
(
RenderBox
child
)
{
if
(
child
!=
null
)
{
final
BoxParentData
parentData
=
child
.
parentData
;
context
.
paintChild
(
child
,
parentData
.
offset
+
offset
);
}
}
doPaint
(
leading
);
doPaint
(
title
);
doPaint
(
subtitle
);
doPaint
(
trailing
);
}
@override
bool
hitTestSelf
(
Offset
position
)
=>
true
;
@override
bool
hitTestChildren
(
HitTestResult
result
,
{
@required
Offset
position
})
{
assert
(
position
!=
null
);
for
(
RenderBox
child
in
_children
)
{
final
BoxParentData
parentData
=
child
.
parentData
;
if
(
child
.
hitTest
(
result
,
position:
position
-
parentData
.
offset
))
return
true
;
}
return
false
;
}
}
class
_RenderListTileElement
extends
RenderObjectElement
{
_RenderListTileElement
(
_ListTile
widget
)
:
super
(
widget
);
final
Map
<
_ListTileSlot
,
Element
>
slotToChild
=
<
_ListTileSlot
,
Element
>{};
final
Map
<
Element
,
_ListTileSlot
>
childToSlot
=
<
Element
,
_ListTileSlot
>{};
@override
_ListTile
get
widget
=>
super
.
widget
;
@override
_RenderListTile
get
renderObject
=>
super
.
renderObject
;
@override
void
visitChildren
(
ElementVisitor
visitor
)
{
slotToChild
.
values
.
forEach
(
visitor
);
}
@override
void
forgetChild
(
Element
child
)
{
assert
(
slotToChild
.
values
.
contains
(
child
));
assert
(
childToSlot
.
keys
.
contains
(
child
));
final
_ListTileSlot
slot
=
childToSlot
[
child
];
childToSlot
.
remove
(
child
);
slotToChild
.
remove
(
slot
);
}
void
_mountChild
(
Widget
widget
,
_ListTileSlot
slot
)
{
final
Element
oldChild
=
slotToChild
[
slot
];
final
Element
newChild
=
updateChild
(
oldChild
,
widget
,
slot
);
if
(
oldChild
!=
null
)
{
slotToChild
.
remove
(
slot
);
childToSlot
.
remove
(
oldChild
);
}
if
(
newChild
!=
null
)
{
slotToChild
[
slot
]
=
newChild
;
childToSlot
[
newChild
]
=
slot
;
}
}
@override
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
_mountChild
(
widget
.
leading
,
_ListTileSlot
.
leading
);
_mountChild
(
widget
.
title
,
_ListTileSlot
.
title
);
_mountChild
(
widget
.
subtitle
,
_ListTileSlot
.
subtitle
);
_mountChild
(
widget
.
trailing
,
_ListTileSlot
.
trailing
);
}
void
_updateChild
(
Widget
widget
,
_ListTileSlot
slot
)
{
final
Element
oldChild
=
slotToChild
[
slot
];
final
Element
newChild
=
updateChild
(
oldChild
,
widget
,
slot
);
if
(
oldChild
!=
null
)
{
childToSlot
.
remove
(
oldChild
);
slotToChild
.
remove
(
slot
);
}
if
(
newChild
!=
null
)
{
slotToChild
[
slot
]
=
newChild
;
childToSlot
[
newChild
]
=
slot
;
}
}
@override
void
update
(
_ListTile
newWidget
)
{
super
.
update
(
newWidget
);
assert
(
widget
==
newWidget
);
_updateChild
(
widget
.
leading
,
_ListTileSlot
.
leading
);
_updateChild
(
widget
.
title
,
_ListTileSlot
.
title
);
_updateChild
(
widget
.
subtitle
,
_ListTileSlot
.
subtitle
);
_updateChild
(
widget
.
trailing
,
_ListTileSlot
.
trailing
);
}
void
_updateRenderObject
(
RenderObject
child
,
_ListTileSlot
slot
)
{
switch
(
slot
)
{
case
_ListTileSlot
.
leading
:
renderObject
.
leading
=
child
;
break
;
case
_ListTileSlot
.
title
:
renderObject
.
title
=
child
;
break
;
case
_ListTileSlot
.
subtitle
:
renderObject
.
subtitle
=
child
;
break
;
case
_ListTileSlot
.
trailing
:
renderObject
.
trailing
=
child
;
break
;
}
}
@override
void
insertChildRenderObject
(
RenderObject
child
,
dynamic
slotValue
)
{
assert
(
child
is
RenderBox
);
assert
(
slotValue
is
_ListTileSlot
);
final
_ListTileSlot
slot
=
slotValue
;
_updateRenderObject
(
child
,
slot
);
assert
(
renderObject
.
childToSlot
.
keys
.
contains
(
child
));
assert
(
renderObject
.
slotToChild
.
keys
.
contains
(
slot
));
}
@override
void
removeChildRenderObject
(
RenderObject
child
)
{
assert
(
child
is
RenderBox
);
assert
(
renderObject
.
childToSlot
.
keys
.
contains
(
child
));
_updateRenderObject
(
null
,
renderObject
.
childToSlot
[
child
]);
assert
(!
renderObject
.
childToSlot
.
keys
.
contains
(
child
));
assert
(!
renderObject
.
slotToChild
.
keys
.
contains
(
slot
));
}
@override
void
moveChildRenderObject
(
RenderObject
child
,
dynamic
slotValue
)
{
assert
(
false
,
'not reachable'
);
}
}
packages/flutter/test/material/list_tile_test.dart
View file @
ee019c0b
...
...
@@ -2,6 +2,8 @@
// 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/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
...
@@ -95,23 +97,23 @@ void main() {
double
widthKey
(
Key
key
)
=>
tester
.
getSize
(
find
.
byKey
(
key
)).
width
;
double
heightKey
(
Key
key
)
=>
tester
.
getSize
(
find
.
byKey
(
key
)).
height
;
//
16.0 padding to the left and right of the leading and trailing widgets
//
plus the media padding
.
// ListTiles are contained by a SafeArea defined like this:
//
SafeArea(top: false, bottom: false, minimim: contentPadding)
//
The default contentPadding is 16.0 on the left and right
.
void
testHorizontalGeometry
()
{
expect
(
leftKey
(
leadingKey
),
16.0
+
leftPadding
);
expect
(
left
(
'title'
),
72.0
+
leftPadding
);
expect
(
leftKey
(
leadingKey
),
math
.
max
(
16.0
,
leftPadding
)
);
expect
(
left
(
'title'
),
56.0
+
math
.
max
(
16.0
,
leftPadding
)
);
if
(
hasSubtitle
)
expect
(
left
(
'subtitle'
),
72.0
+
leftPadding
);
expect
(
left
(
'subtitle'
),
56.0
+
math
.
max
(
16.0
,
leftPadding
)
);
expect
(
left
(
'title'
),
rightKey
(
leadingKey
)
+
32.0
);
expect
(
rightKey
(
trailingKey
),
800.0
-
16.0
-
rightPadding
);
expect
(
rightKey
(
trailingKey
),
800.0
-
math
.
max
(
16.0
,
rightPadding
)
);
expect
(
widthKey
(
trailingKey
),
24.0
);
}
void
testVerticalGeometry
(
double
expectedHeight
)
{
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
new
Size
(
800.0
,
expectedHeight
));
if
(
hasSubtitle
)
expect
(
top
(
'subtitle'
),
bottom
(
'title'
));
expect
(
top
(
'subtitle'
),
greaterThanOrEqualTo
(
bottom
(
'title'
)
));
expect
(
heightKey
(
trailingKey
),
24.0
);
}
...
...
@@ -133,7 +135,7 @@ void main() {
await
tester
.
pumpWidget
(
buildFrame
(
isTwoLine:
true
,
dense:
true
));
testChildren
();
testHorizontalGeometry
();
testVerticalGeometry
(
6
0
.0
);
testVerticalGeometry
(
6
4
.0
);
await
tester
.
pumpWidget
(
buildFrame
(
isThreeLine:
true
));
testChildren
();
...
...
@@ -189,9 +191,9 @@ void main() {
child:
const
Material
(
child:
const
Center
(
child:
const
ListTile
(
leading:
const
Text
(
'
leading
'
),
leading:
const
Text
(
'
L
'
),
title:
const
Text
(
'title'
),
trailing:
const
Text
(
'
trailing
'
),
trailing:
const
Text
(
'
T
'
),
),
),
),
...
...
@@ -202,10 +204,9 @@ void main() {
double
right
(
String
text
)
=>
tester
.
getTopRight
(
find
.
text
(
text
)).
dx
;
void
testHorizontalGeometry
()
{
expect
(
right
(
'leading'
),
800.0
-
16.0
-
rightPadding
);
expect
(
right
(
'title'
),
800.0
-
72.0
-
rightPadding
);
expect
(
left
(
'leading'
)
-
right
(
'title'
),
16.0
);
expect
(
left
(
'trailing'
),
16.0
+
leftPadding
);
expect
(
right
(
'L'
),
800.0
-
math
.
max
(
16.0
,
rightPadding
));
expect
(
right
(
'title'
),
800.0
-
56.0
-
math
.
max
(
16.0
,
rightPadding
));
expect
(
left
(
'T'
),
math
.
max
(
16.0
,
leftPadding
));
}
testHorizontalGeometry
();
...
...
@@ -385,4 +386,165 @@ void main() {
semantics
.
dispose
();
});
testWidgets
(
'ListTile contentPadding'
,
(
WidgetTester
tester
)
async
{
Widget
buildFrame
(
TextDirection
textDirection
)
{
return
new
MediaQuery
(
data:
const
MediaQueryData
(
padding:
EdgeInsets
.
zero
,
textScaleFactor:
1.0
),
child:
new
Directionality
(
textDirection:
textDirection
,
child:
new
Material
(
child:
new
Container
(
alignment:
Alignment
.
topLeft
,
child:
const
ListTile
(
contentPadding:
const
EdgeInsetsDirectional
.
only
(
start:
10.0
,
end:
20.0
,
top:
30.0
,
bottom:
40.0
,
),
leading:
const
Text
(
'L'
),
title:
const
Text
(
'title'
),
trailing:
const
Text
(
'T'
),
),
),
),
),
);
}
double
left
(
String
text
)
=>
tester
.
getTopLeft
(
find
.
text
(
text
)).
dx
;
double
right
(
String
text
)
=>
tester
.
getTopRight
(
find
.
text
(
text
)).
dx
;
await
tester
.
pumpWidget
(
buildFrame
(
TextDirection
.
ltr
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
126.0
));
// 126 = 56 + 30 + 40
expect
(
left
(
'L'
),
10.0
);
// contentPadding.start = 10
expect
(
right
(
'T'
),
780.0
);
// 800 - contentPadding.end
await
tester
.
pumpWidget
(
buildFrame
(
TextDirection
.
rtl
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
126.0
));
// 126 = 56 + 30 + 40
expect
(
left
(
'T'
),
20.0
);
// contentPadding.end = 20
expect
(
right
(
'L'
),
790.0
);
// 800 - contentPadding.start
});
testWidgets
(
'ListTile contentPadding'
,
(
WidgetTester
tester
)
async
{
Widget
buildFrame
(
TextDirection
textDirection
)
{
return
new
MediaQuery
(
data:
const
MediaQueryData
(
padding:
EdgeInsets
.
zero
,
textScaleFactor:
1.0
),
child:
new
Directionality
(
textDirection:
textDirection
,
child:
new
Material
(
child:
new
Container
(
alignment:
Alignment
.
topLeft
,
child:
const
ListTile
(
contentPadding:
const
EdgeInsetsDirectional
.
only
(
start:
10.0
,
end:
20.0
,
top:
30.0
,
bottom:
40.0
,
),
leading:
const
Text
(
'L'
),
title:
const
Text
(
'title'
),
trailing:
const
Text
(
'T'
),
),
),
),
),
);
}
double
left
(
String
text
)
=>
tester
.
getTopLeft
(
find
.
text
(
text
)).
dx
;
double
right
(
String
text
)
=>
tester
.
getTopRight
(
find
.
text
(
text
)).
dx
;
await
tester
.
pumpWidget
(
buildFrame
(
TextDirection
.
ltr
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
126.0
));
// 126 = 56 + 30 + 40
expect
(
left
(
'L'
),
10.0
);
// contentPadding.start = 10
expect
(
right
(
'T'
),
780.0
);
// 800 - contentPadding.end
await
tester
.
pumpWidget
(
buildFrame
(
TextDirection
.
rtl
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
126.0
));
// 126 = 56 + 30 + 40
expect
(
left
(
'T'
),
20.0
);
// contentPadding.end = 20
expect
(
right
(
'L'
),
790.0
);
// 800 - contentPadding.start
});
testWidgets
(
'ListTileTheme wide leading Widget'
,
(
WidgetTester
tester
)
async
{
const
Key
leadingKey
=
const
ValueKey
<
String
>(
'L'
);
Widget
buildFrame
(
double
leadingWidth
,
TextDirection
textDirection
)
{
return
new
MediaQuery
(
data:
const
MediaQueryData
(
padding:
EdgeInsets
.
zero
,
textScaleFactor:
1.0
),
child:
new
Directionality
(
textDirection:
textDirection
,
child:
new
Material
(
child:
new
Container
(
alignment:
Alignment
.
topLeft
,
child:
new
ListTile
(
contentPadding:
EdgeInsets
.
zero
,
leading:
new
SizedBox
(
key:
leadingKey
,
width:
leadingWidth
,
height:
32.0
),
title:
const
Text
(
'title'
),
subtitle:
const
Text
(
'subtitle'
),
),
),
),
),
);
}
double
left
(
String
text
)
=>
tester
.
getTopLeft
(
find
.
text
(
text
)).
dx
;
double
right
(
String
text
)
=>
tester
.
getTopRight
(
find
.
text
(
text
)).
dx
;
// textDirection = LTR
// Two-line tile's height = 72, leading 24x32 widget is vertically centered
await
tester
.
pumpWidget
(
buildFrame
(
24.0
,
TextDirection
.
ltr
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
72.0
));
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
0.0
,
20.0
));
expect
(
tester
.
getBottomRight
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
24.0
,
52.0
));
// Leading widget's width is 20, so default layout: the left edges of the
// title and subtitle are at 56dps (contentPadding is zero).
expect
(
left
(
'title'
),
56.0
);
expect
(
left
(
'subtitle'
),
56.0
);
// If the leading widget is wider than 40 it is separated from the
// title and subtitle by 16.
await
tester
.
pumpWidget
(
buildFrame
(
56.0
,
TextDirection
.
ltr
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
72.0
));
expect
(
tester
.
getTopLeft
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
0.0
,
20.0
));
expect
(
tester
.
getBottomRight
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
56.0
,
52.0
));
expect
(
left
(
'title'
),
72.0
);
expect
(
left
(
'subtitle'
),
72.0
);
// Same tests, textDirection = RTL
await
tester
.
pumpWidget
(
buildFrame
(
24.0
,
TextDirection
.
rtl
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
72.0
));
expect
(
tester
.
getTopRight
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
800.0
,
20.0
));
expect
(
tester
.
getBottomLeft
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
800.0
-
24.0
,
52.0
));
expect
(
right
(
'title'
),
800.0
-
56.0
);
expect
(
right
(
'subtitle'
),
800.0
-
56.0
);
await
tester
.
pumpWidget
(
buildFrame
(
56.0
,
TextDirection
.
rtl
));
expect
(
tester
.
getSize
(
find
.
byType
(
ListTile
)),
const
Size
(
800.0
,
72.0
));
expect
(
tester
.
getTopRight
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
800.0
,
20.0
));
expect
(
tester
.
getBottomLeft
(
find
.
byKey
(
leadingKey
)),
const
Offset
(
800.0
-
56.0
,
52.0
));
expect
(
right
(
'title'
),
800.0
-
72.0
);
expect
(
right
(
'subtitle'
),
800.0
-
72.0
);
});
}
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