Revert "Updated ListTile layout" (#17612)
This reverts commit ee019c0bca05c9d2293599e88aa2da0fdb9a76ce.
This commit is contained in:
parent
b0fa93eada
commit
b7fc015f0a
@ -2,11 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
@ -46,7 +42,6 @@ class ListTileTheme extends InheritedWidget {
|
|||||||
this.selectedColor,
|
this.selectedColor,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
this.contentPadding,
|
|
||||||
Widget child,
|
Widget child,
|
||||||
}) : super(key: key, child: child);
|
}) : super(key: key, child: child);
|
||||||
|
|
||||||
@ -61,7 +56,6 @@ class ListTileTheme extends InheritedWidget {
|
|||||||
Color selectedColor,
|
Color selectedColor,
|
||||||
Color iconColor,
|
Color iconColor,
|
||||||
Color textColor,
|
Color textColor,
|
||||||
EdgeInsetsGeometry contentPadding,
|
|
||||||
@required Widget child,
|
@required Widget child,
|
||||||
}) {
|
}) {
|
||||||
assert(child != null);
|
assert(child != null);
|
||||||
@ -75,7 +69,6 @@ class ListTileTheme extends InheritedWidget {
|
|||||||
selectedColor: selectedColor ?? parent.selectedColor,
|
selectedColor: selectedColor ?? parent.selectedColor,
|
||||||
iconColor: iconColor ?? parent.iconColor,
|
iconColor: iconColor ?? parent.iconColor,
|
||||||
textColor: textColor ?? parent.textColor,
|
textColor: textColor ?? parent.textColor,
|
||||||
contentPadding: contentPadding ?? parent.contentPadding,
|
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -97,12 +90,6 @@ class ListTileTheme extends InheritedWidget {
|
|||||||
/// If specified, the text color used for enabled [ListTile]s that are not selected.
|
/// If specified, the text color used for enabled [ListTile]s that are not selected.
|
||||||
final Color textColor;
|
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.
|
/// The closest instance of this class that encloses the given context.
|
||||||
///
|
///
|
||||||
/// Typical usage is as follows:
|
/// Typical usage is as follows:
|
||||||
@ -121,8 +108,7 @@ class ListTileTheme extends InheritedWidget {
|
|||||||
|| style != oldWidget.style
|
|| style != oldWidget.style
|
||||||
|| selectedColor != oldWidget.selectedColor
|
|| selectedColor != oldWidget.selectedColor
|
||||||
|| iconColor != oldWidget.iconColor
|
|| iconColor != oldWidget.iconColor
|
||||||
|| textColor != oldWidget.textColor
|
|| textColor != oldWidget.textColor;
|
||||||
|| contentPadding != oldWidget.contentPadding;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +210,6 @@ class ListTile extends StatelessWidget {
|
|||||||
this.trailing,
|
this.trailing,
|
||||||
this.isThreeLine: false,
|
this.isThreeLine: false,
|
||||||
this.dense,
|
this.dense,
|
||||||
this.contentPadding,
|
|
||||||
this.enabled: true,
|
this.enabled: true,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
@ -266,14 +251,6 @@ class ListTile extends StatelessWidget {
|
|||||||
/// If this property is null then its value is based on [ListTileTheme.dense].
|
/// If this property is null then its value is based on [ListTileTheme.dense].
|
||||||
final bool 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.
|
/// Whether this list tile is interactive.
|
||||||
///
|
///
|
||||||
/// If false, this list tile is styled with the disabled color from the
|
/// If false, this list tile is styled with the disabled color from the
|
||||||
@ -370,7 +347,7 @@ class ListTile extends StatelessWidget {
|
|||||||
return defaultColor;
|
return defaultColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isDenseLayout(ListTileTheme tileTheme) {
|
bool _denseLayout(ListTileTheme tileTheme) {
|
||||||
return dense != null ? dense : (tileTheme?.dense ?? false);
|
return dense != null ? dense : (tileTheme?.dense ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +366,7 @@ class ListTile extends StatelessWidget {
|
|||||||
style = theme.textTheme.subhead;
|
style = theme.textTheme.subhead;
|
||||||
}
|
}
|
||||||
final Color color = _textColor(theme, tileTheme, style.color);
|
final Color color = _textColor(theme, tileTheme, style.color);
|
||||||
return _isDenseLayout(tileTheme)
|
return _denseLayout(tileTheme)
|
||||||
? style.copyWith(fontSize: 13.0, color: color)
|
? style.copyWith(fontSize: 13.0, color: color)
|
||||||
: style.copyWith(color: color);
|
: style.copyWith(color: color);
|
||||||
}
|
}
|
||||||
@ -397,7 +374,7 @@ class ListTile extends StatelessWidget {
|
|||||||
TextStyle _subtitleTextStyle(ThemeData theme, ListTileTheme tileTheme) {
|
TextStyle _subtitleTextStyle(ThemeData theme, ListTileTheme tileTheme) {
|
||||||
final TextStyle style = theme.textTheme.body1;
|
final TextStyle style = theme.textTheme.body1;
|
||||||
final Color color = _textColor(theme, tileTheme, theme.textTheme.caption.color);
|
final Color color = _textColor(theme, tileTheme, theme.textTheme.caption.color);
|
||||||
return _isDenseLayout(tileTheme)
|
return _denseLayout(tileTheme)
|
||||||
? style.copyWith(color: color, fontSize: 12.0)
|
? style.copyWith(color: color, fontSize: 12.0)
|
||||||
: style.copyWith(color: color);
|
: style.copyWith(color: color);
|
||||||
}
|
}
|
||||||
@ -408,563 +385,91 @@ class ListTile extends StatelessWidget {
|
|||||||
final ThemeData theme = Theme.of(context);
|
final ThemeData theme = Theme.of(context);
|
||||||
final ListTileTheme tileTheme = ListTileTheme.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;
|
IconThemeData iconThemeData;
|
||||||
if (leading != null || trailing != null)
|
if (leading != null || trailing != null)
|
||||||
iconThemeData = new IconThemeData(color: _iconColor(theme, tileTheme));
|
iconThemeData = new IconThemeData(color: _iconColor(theme, tileTheme));
|
||||||
|
|
||||||
Widget leadingIcon;
|
|
||||||
if (leading != null) {
|
if (leading != null) {
|
||||||
leadingIcon = IconTheme.merge(
|
children.add(IconTheme.merge(
|
||||||
data: iconThemeData,
|
data: iconThemeData,
|
||||||
|
child: new Container(
|
||||||
|
margin: const EdgeInsetsDirectional.only(end: 16.0),
|
||||||
|
width: 40.0,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: leading,
|
child: leading,
|
||||||
);
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Widget titleText = new AnimatedDefaultTextStyle(
|
final Widget primaryLine = new AnimatedDefaultTextStyle(
|
||||||
style: _titleTextStyle(theme, tileTheme),
|
style: _titleTextStyle(theme, tileTheme),
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
child: title ?? const SizedBox()
|
child: title ?? new Container()
|
||||||
);
|
);
|
||||||
|
Widget center = primaryLine;
|
||||||
Widget subtitleText;
|
if (subtitle != null && (isTwoLine || isThreeLine)) {
|
||||||
if (subtitle != null) {
|
center = new Column(
|
||||||
subtitleText = new AnimatedDefaultTextStyle(
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
primaryLine,
|
||||||
|
new AnimatedDefaultTextStyle(
|
||||||
style: _subtitleTextStyle(theme, tileTheme),
|
style: _subtitleTextStyle(theme, tileTheme),
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
child: subtitle,
|
child: subtitle,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
children.add(new Expanded(
|
||||||
|
child: center,
|
||||||
|
));
|
||||||
|
|
||||||
Widget trailingIcon;
|
|
||||||
if (trailing != null) {
|
if (trailing != null) {
|
||||||
trailingIcon = IconTheme.merge(
|
children.add(IconTheme.merge(
|
||||||
data: iconThemeData,
|
data: iconThemeData,
|
||||||
|
child: new Container(
|
||||||
|
margin: const EdgeInsetsDirectional.only(start: 16.0),
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: trailing,
|
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(
|
return new InkWell(
|
||||||
onTap: enabled ? onTap : null,
|
onTap: enabled ? onTap : null,
|
||||||
onLongPress: enabled ? onLongPress : null,
|
onLongPress: enabled ? onLongPress : null,
|
||||||
child: new Semantics(
|
child: new Semantics(
|
||||||
selected: selected,
|
selected: selected,
|
||||||
enabled: enabled,
|
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(
|
child: new SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
bottom: false,
|
bottom: false,
|
||||||
minimum: resolvedContentPadding,
|
child: new Row(children: children),
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -97,23 +95,23 @@ void main() {
|
|||||||
double widthKey(Key key) => tester.getSize(find.byKey(key)).width;
|
double widthKey(Key key) => tester.getSize(find.byKey(key)).width;
|
||||||
double heightKey(Key key) => tester.getSize(find.byKey(key)).height;
|
double heightKey(Key key) => tester.getSize(find.byKey(key)).height;
|
||||||
|
|
||||||
// ListTiles are contained by a SafeArea defined like this:
|
|
||||||
// SafeArea(top: false, bottom: false, minimim: contentPadding)
|
// 16.0 padding to the left and right of the leading and trailing widgets
|
||||||
// The default contentPadding is 16.0 on the left and right.
|
// plus the media padding.
|
||||||
void testHorizontalGeometry() {
|
void testHorizontalGeometry() {
|
||||||
expect(leftKey(leadingKey), math.max(16.0, leftPadding));
|
expect(leftKey(leadingKey), 16.0 + leftPadding);
|
||||||
expect(left('title'), 56.0 + math.max(16.0, leftPadding));
|
expect(left('title'), 72.0 + leftPadding);
|
||||||
if (hasSubtitle)
|
if (hasSubtitle)
|
||||||
expect(left('subtitle'), 56.0 + math.max(16.0, leftPadding));
|
expect(left('subtitle'), 72.0 + leftPadding);
|
||||||
expect(left('title'), rightKey(leadingKey) + 32.0);
|
expect(left('title'), rightKey(leadingKey) + 32.0);
|
||||||
expect(rightKey(trailingKey), 800.0 - math.max(16.0, rightPadding));
|
expect(rightKey(trailingKey), 800.0 - 16.0 - rightPadding);
|
||||||
expect(widthKey(trailingKey), 24.0);
|
expect(widthKey(trailingKey), 24.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void testVerticalGeometry(double expectedHeight) {
|
void testVerticalGeometry(double expectedHeight) {
|
||||||
expect(tester.getSize(find.byType(ListTile)), new Size(800.0, expectedHeight));
|
expect(tester.getSize(find.byType(ListTile)), new Size(800.0, expectedHeight));
|
||||||
if (hasSubtitle)
|
if (hasSubtitle)
|
||||||
expect(top('subtitle'), greaterThanOrEqualTo(bottom('title')));
|
expect(top('subtitle'), bottom('title'));
|
||||||
expect(heightKey(trailingKey), 24.0);
|
expect(heightKey(trailingKey), 24.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +133,7 @@ void main() {
|
|||||||
await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true));
|
await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true));
|
||||||
testChildren();
|
testChildren();
|
||||||
testHorizontalGeometry();
|
testHorizontalGeometry();
|
||||||
testVerticalGeometry(64.0);
|
testVerticalGeometry(60.0);
|
||||||
|
|
||||||
await tester.pumpWidget(buildFrame(isThreeLine: true));
|
await tester.pumpWidget(buildFrame(isThreeLine: true));
|
||||||
testChildren();
|
testChildren();
|
||||||
@ -191,9 +189,9 @@ void main() {
|
|||||||
child: const Material(
|
child: const Material(
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: const ListTile(
|
child: const ListTile(
|
||||||
leading: const Text('L'),
|
leading: const Text('leading'),
|
||||||
title: const Text('title'),
|
title: const Text('title'),
|
||||||
trailing: const Text('T'),
|
trailing: const Text('trailing'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -204,9 +202,10 @@ void main() {
|
|||||||
double right(String text) => tester.getTopRight(find.text(text)).dx;
|
double right(String text) => tester.getTopRight(find.text(text)).dx;
|
||||||
|
|
||||||
void testHorizontalGeometry() {
|
void testHorizontalGeometry() {
|
||||||
expect(right('L'), 800.0 - math.max(16.0, rightPadding));
|
expect(right('leading'), 800.0 - 16.0 - rightPadding);
|
||||||
expect(right('title'), 800.0 - 56.0 - math.max(16.0, rightPadding));
|
expect(right('title'), 800.0 - 72.0 - rightPadding);
|
||||||
expect(left('T'), math.max(16.0, leftPadding));
|
expect(left('leading') - right('title'), 16.0);
|
||||||
|
expect(left('trailing'), 16.0 + leftPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
testHorizontalGeometry();
|
testHorizontalGeometry();
|
||||||
@ -386,165 +385,4 @@ void main() {
|
|||||||
|
|
||||||
semantics.dispose();
|
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);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user