Clean up baseline logic in material library. (#17922)
This commit is contained in:
parent
587a1b7708
commit
9f1e76e967
@ -1855,7 +1855,7 @@ class _RenderChip extends RenderBox {
|
||||
@override
|
||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
// The baseline of this widget is the baseline of the label.
|
||||
return label.computeDistanceToActualBaseline(baseline);
|
||||
return label.getDistanceToActualBaseline(baseline);
|
||||
}
|
||||
|
||||
Size _layoutLabel(double iconSizes, Size size) {
|
||||
|
@ -537,10 +537,15 @@ class _RenderDecorationLayout {
|
||||
// The workhorse: layout and paint a _Decorator widget's _Decoration.
|
||||
class _RenderDecoration extends RenderBox {
|
||||
_RenderDecoration({
|
||||
_Decoration decoration,
|
||||
TextDirection textDirection,
|
||||
}) : _decoration = decoration,
|
||||
_textDirection = textDirection;
|
||||
@required _Decoration decoration,
|
||||
@required TextDirection textDirection,
|
||||
@required TextBaseline textBaseline,
|
||||
}) : assert(decoration != null),
|
||||
assert(textDirection != null),
|
||||
assert(textBaseline != null),
|
||||
_decoration = decoration,
|
||||
_textDirection = textDirection,
|
||||
_textBaseline = textBaseline;
|
||||
|
||||
final Map<_DecorationSlot, RenderBox> slotToChild = <_DecorationSlot, RenderBox>{};
|
||||
final Map<RenderBox, _DecorationSlot> childToSlot = <RenderBox, _DecorationSlot>{};
|
||||
@ -654,6 +659,7 @@ class _RenderDecoration extends RenderBox {
|
||||
_Decoration get decoration => _decoration;
|
||||
_Decoration _decoration;
|
||||
set decoration(_Decoration value) {
|
||||
assert(value != null);
|
||||
if (_decoration == value)
|
||||
return;
|
||||
_decoration = value;
|
||||
@ -663,12 +669,23 @@ class _RenderDecoration extends RenderBox {
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
assert(value != null);
|
||||
if (_textDirection == value)
|
||||
return;
|
||||
_textDirection = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
TextBaseline get textBaseline => _textBaseline;
|
||||
TextBaseline _textBaseline;
|
||||
set textBaseline(TextBaseline value) {
|
||||
assert(value != null);
|
||||
if (_textBaseline == value)
|
||||
return;
|
||||
_textBaseline = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
@ -748,7 +765,7 @@ class _RenderDecoration extends RenderBox {
|
||||
if (box == null)
|
||||
return;
|
||||
box.layout(boxConstraints, parentUsesSize: true);
|
||||
final double baseline = box.getDistanceToBaseline(TextBaseline.alphabetic);
|
||||
final double baseline = box.getDistanceToBaseline(textBaseline);
|
||||
assert(baseline != null && baseline >= 0.0);
|
||||
boxToBaseline[box] = baseline;
|
||||
aboveBaseline = math.max(baseline, aboveBaseline);
|
||||
@ -913,7 +930,15 @@ class _RenderDecoration extends RenderBox {
|
||||
width: overallWidth - _boxSize(icon).width,
|
||||
);
|
||||
container.layout(containerConstraints, parentUsesSize: true);
|
||||
final double x = textDirection == TextDirection.rtl ? 0.0 : _boxSize(icon).width;
|
||||
double x;
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
x = 0.0;
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
x = _boxSize(icon).width;
|
||||
break;
|
||||
}
|
||||
_boxParentData(container).offset = new Offset(x, 0.0);
|
||||
}
|
||||
|
||||
@ -938,7 +963,15 @@ class _RenderDecoration extends RenderBox {
|
||||
: layout.outlineBaseline;
|
||||
|
||||
if (icon != null) {
|
||||
final double x = textDirection == TextDirection.rtl ? overallWidth - icon.size.width : 0.0;
|
||||
double x;
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
x = overallWidth - icon.size.width;
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
x = 0.0;
|
||||
break;
|
||||
}
|
||||
centerLayout(icon, x);
|
||||
}
|
||||
|
||||
@ -1004,9 +1037,14 @@ class _RenderDecoration extends RenderBox {
|
||||
}
|
||||
|
||||
if (label != null) {
|
||||
decoration.borderGap.start = textDirection == TextDirection.rtl
|
||||
? _boxParentData(label).offset.dx + label.size.width
|
||||
: _boxParentData(label).offset.dx;
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
decoration.borderGap.start = _boxParentData(label).offset.dx + label.size.width;
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
decoration.borderGap.start = _boxParentData(label).offset.dx;
|
||||
break;
|
||||
}
|
||||
decoration.borderGap.extent = label.size.width * 0.75;
|
||||
} else {
|
||||
decoration.borderGap.start = null;
|
||||
@ -1039,9 +1077,15 @@ class _RenderDecoration extends RenderBox {
|
||||
final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline;
|
||||
final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
|
||||
final double scale = lerpDouble(1.0, 0.75, t);
|
||||
final double dx = textDirection == TextDirection.rtl
|
||||
? labelOffset.dx + label.size.width * (1.0 - scale) // origin is on the right
|
||||
: labelOffset.dx; // origin on the left
|
||||
double dx;
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
dx = labelOffset.dx + label.size.width * (1.0 - scale); // origin is on the right
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
dx = labelOffset.dx; // origin on the left
|
||||
break;
|
||||
}
|
||||
final double dy = lerpDouble(0.0, floatingY - labelOffset.dy, t);
|
||||
_labelTransform = new Matrix4.identity()
|
||||
..translate(dx, labelOffset.dy + dy)
|
||||
@ -1237,10 +1281,17 @@ class _RenderDecorationElement extends RenderObjectElement {
|
||||
class _Decorator extends RenderObjectWidget {
|
||||
const _Decorator({
|
||||
Key key,
|
||||
this.decoration,
|
||||
}) : super(key: key);
|
||||
@required this.decoration,
|
||||
@required this.textDirection,
|
||||
@required this.textBaseline,
|
||||
}) : assert(decoration != null),
|
||||
assert(textDirection != null),
|
||||
assert(textBaseline != null),
|
||||
super(key: key);
|
||||
|
||||
final _Decoration decoration;
|
||||
final TextDirection textDirection;
|
||||
final TextBaseline textBaseline;
|
||||
|
||||
@override
|
||||
_RenderDecorationElement createElement() => new _RenderDecorationElement(this);
|
||||
@ -1249,7 +1300,8 @@ class _Decorator extends RenderObjectWidget {
|
||||
_RenderDecoration createRenderObject(BuildContext context) {
|
||||
return new _RenderDecoration(
|
||||
decoration: decoration,
|
||||
textDirection: Directionality.of(context),
|
||||
textDirection: textDirection,
|
||||
textBaseline: textBaseline,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1257,7 +1309,8 @@ class _Decorator extends RenderObjectWidget {
|
||||
void updateRenderObject(BuildContext context, _RenderDecoration renderObject) {
|
||||
renderObject
|
||||
..decoration = decoration
|
||||
..textDirection = Directionality.of(context);
|
||||
..textDirection = textDirection
|
||||
..textBaseline = textBaseline;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1310,6 +1363,9 @@ class InputDecorator extends StatefulWidget {
|
||||
///
|
||||
/// If null, `baseStyle` defaults to the `subhead` style from the
|
||||
/// current [Theme], see [ThemeData.textTheme].
|
||||
///
|
||||
/// The [TextStyle.textBaseline] of the [baseStyle] is used to determine
|
||||
/// the baseline used for text alignment.
|
||||
final TextStyle baseStyle;
|
||||
|
||||
/// How the text in the decoration should be aligned horizontally.
|
||||
@ -1539,6 +1595,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final TextStyle inlineStyle = _getInlineStyle(themeData);
|
||||
final TextBaseline textBaseline = inlineStyle.textBaseline;
|
||||
|
||||
final TextStyle hintStyle = inlineStyle.merge(decoration.hintStyle);
|
||||
final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity(
|
||||
@ -1713,6 +1770,8 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
counter: counter,
|
||||
container: container,
|
||||
),
|
||||
textDirection: textDirection,
|
||||
textBaseline: textBaseline,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -420,16 +420,19 @@ class ListTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
final TextStyle titleStyle = _titleTextStyle(theme, tileTheme);
|
||||
final Widget titleText = new AnimatedDefaultTextStyle(
|
||||
style: _titleTextStyle(theme, tileTheme),
|
||||
style: titleStyle,
|
||||
duration: kThemeChangeDuration,
|
||||
child: title ?? const SizedBox()
|
||||
);
|
||||
|
||||
Widget subtitleText;
|
||||
TextStyle subtitleStyle;
|
||||
if (subtitle != null) {
|
||||
subtitleStyle = _subtitleTextStyle(theme, tileTheme);
|
||||
subtitleText = new AnimatedDefaultTextStyle(
|
||||
style: _subtitleTextStyle(theme, tileTheme),
|
||||
style: subtitleStyle,
|
||||
duration: kThemeChangeDuration,
|
||||
child: subtitle,
|
||||
);
|
||||
@ -466,6 +469,9 @@ class ListTile extends StatelessWidget {
|
||||
trailing: trailingIcon,
|
||||
isDense: _isDenseLayout(tileTheme),
|
||||
isThreeLine: isThreeLine,
|
||||
textDirection: textDirection,
|
||||
titleBaselineType: titleStyle.textBaseline,
|
||||
subtitleBaselineType: subtitleStyle?.textBaseline,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -473,45 +479,6 @@ class ListTile extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@ -520,348 +487,61 @@ enum _ListTileSlot {
|
||||
trailing,
|
||||
}
|
||||
|
||||
class _RenderListTile extends RenderBox {
|
||||
_RenderListTile({
|
||||
bool isDense,
|
||||
bool isThreeLine,
|
||||
TextDirection textDirection,
|
||||
}) : _isDense = isDense,
|
||||
_isThreeLine = isThreeLine,
|
||||
_textDirection = textDirection;
|
||||
class _ListTile extends RenderObjectWidget {
|
||||
const _ListTile({
|
||||
Key key,
|
||||
this.leading,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.trailing,
|
||||
@required this.isThreeLine,
|
||||
@required this.isDense,
|
||||
@required this.textDirection,
|
||||
@required this.titleBaselineType,
|
||||
this.subtitleBaselineType,
|
||||
}) : assert(isThreeLine != null),
|
||||
assert(isDense != null),
|
||||
assert(textDirection != null),
|
||||
assert(titleBaselineType != null),
|
||||
super(key: key);
|
||||
|
||||
static const double _minLeadingWidth = 40.0;
|
||||
// The horizontal gap between the titles and the leading/trailing widgets
|
||||
static const double _horizontalTitleGap = 16.0;
|
||||
// The minimum padding on the top and bottom of the title and subtitle widgets.
|
||||
static const double _minVerticalPadding = 4.0;
|
||||
|
||||
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();
|
||||
}
|
||||
final Widget leading;
|
||||
final Widget title;
|
||||
final Widget subtitle;
|
||||
final Widget trailing;
|
||||
final bool isThreeLine;
|
||||
final bool isDense;
|
||||
final TextDirection textDirection;
|
||||
final TextBaseline titleBaselineType;
|
||||
final TextBaseline subtitleBaselineType;
|
||||
|
||||
@override
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
for (RenderBox child in _children)
|
||||
child.attach(owner);
|
||||
}
|
||||
_ListTileElement createElement() => new _ListTileElement(this);
|
||||
|
||||
@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), _minLeadingWidth) + _horizontalTitleGap
|
||||
: 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), _minLeadingWidth) + _horizontalTitleGap
|
||||
: 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;
|
||||
if (isTwoLine)
|
||||
return isDense ? 64.0 : 72.0;
|
||||
return isDense ? 76.0 : 88.0;
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) {
|
||||
return math.max(
|
||||
_defaultTileHeight,
|
||||
title.getMinIntrinsicHeight(width) + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0)
|
||||
_RenderListTile createRenderObject(BuildContext context) {
|
||||
return new _RenderListTile(
|
||||
isThreeLine: isThreeLine,
|
||||
isDense: isDense,
|
||||
textDirection: textDirection,
|
||||
titleBaselineType: titleBaselineType,
|
||||
subtitleBaselineType: subtitleBaselineType,
|
||||
);
|
||||
}
|
||||
|
||||
@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.getDistanceToActualBaseline(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(_minLeadingWidth, leadingSize.width) + _horizontalTitleGap
|
||||
: 0.0;
|
||||
final BoxConstraints textConstraints = looseConstraints.tighten(
|
||||
width: tileWidth - titleStart - (hasTrailing ? trailingSize.width + _horizontalTitleGap : 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 + 2.0 * _minVerticalPadding);
|
||||
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 plus
|
||||
// _minVerticalPadding on top and bottom.
|
||||
if (titleY < _minVerticalPadding ||
|
||||
(subtitleY + subtitleSize.height + _minVerticalPadding) > tileHeight) {
|
||||
tileHeight = titleSize.height + subtitleSize.height + 2.0 * _minVerticalPadding;
|
||||
titleY = _minVerticalPadding;
|
||||
subtitleY = titleSize.height + _minVerticalPadding;
|
||||
}
|
||||
}
|
||||
|
||||
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 + _horizontalTitleGap : 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;
|
||||
void updateRenderObject(BuildContext context, _RenderListTile renderObject) {
|
||||
renderObject
|
||||
..isThreeLine = isThreeLine
|
||||
..isDense = isDense
|
||||
..textDirection = textDirection
|
||||
..titleBaselineType = titleBaselineType
|
||||
..subtitleBaselineType = subtitleBaselineType;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderListTileElement extends RenderObjectElement {
|
||||
_RenderListTileElement(_ListTile widget) : super(widget);
|
||||
class _ListTileElement extends RenderObjectElement {
|
||||
_ListTileElement(_ListTile widget) : super(widget);
|
||||
|
||||
final Map<_ListTileSlot, Element> slotToChild = <_ListTileSlot, Element>{};
|
||||
final Map<Element, _ListTileSlot> childToSlot = <Element, _ListTileSlot>{};
|
||||
@ -972,3 +652,374 @@ class _RenderListTileElement extends RenderObjectElement {
|
||||
assert(false, 'not reachable');
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderListTile extends RenderBox {
|
||||
_RenderListTile({
|
||||
@required bool isDense,
|
||||
@required bool isThreeLine,
|
||||
@required TextDirection textDirection,
|
||||
@required TextBaseline titleBaselineType,
|
||||
TextBaseline subtitleBaselineType,
|
||||
}) : assert(isDense != null),
|
||||
assert(isThreeLine != null),
|
||||
assert(textDirection != null),
|
||||
assert(titleBaselineType != null),
|
||||
_isDense = isDense,
|
||||
_isThreeLine = isThreeLine,
|
||||
_textDirection = textDirection,
|
||||
_titleBaselineType = titleBaselineType,
|
||||
_subtitleBaselineType = subtitleBaselineType;
|
||||
|
||||
static const double _minLeadingWidth = 40.0;
|
||||
// The horizontal gap between the titles and the leading/trailing widgets
|
||||
static const double _horizontalTitleGap = 16.0;
|
||||
// The minimum padding on the top and bottom of the title and subtitle widgets.
|
||||
static const double _minVerticalPadding = 4.0;
|
||||
|
||||
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) {
|
||||
assert(value != null);
|
||||
if (_isDense == value)
|
||||
return;
|
||||
_isDense = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
bool get isThreeLine => _isThreeLine;
|
||||
bool _isThreeLine;
|
||||
set isThreeLine(bool value) {
|
||||
assert(value != null);
|
||||
if (_isThreeLine == value)
|
||||
return;
|
||||
_isThreeLine = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
assert(value != null);
|
||||
if (_textDirection == value)
|
||||
return;
|
||||
_textDirection = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
TextBaseline get titleBaselineType => _titleBaselineType;
|
||||
TextBaseline _titleBaselineType;
|
||||
set titleBaselineType(TextBaseline value) {
|
||||
assert(value != null);
|
||||
if (_titleBaselineType == value)
|
||||
return;
|
||||
_titleBaselineType = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
TextBaseline get subtitleBaselineType => _subtitleBaselineType;
|
||||
TextBaseline _subtitleBaselineType;
|
||||
set subtitleBaselineType(TextBaseline value) {
|
||||
if (_subtitleBaselineType == value)
|
||||
return;
|
||||
_subtitleBaselineType = 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), _minLeadingWidth) + _horizontalTitleGap
|
||||
: 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), _minLeadingWidth) + _horizontalTitleGap
|
||||
: 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;
|
||||
if (isTwoLine)
|
||||
return isDense ? 64.0 : 72.0;
|
||||
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.getDistanceToActualBaseline(baseline);
|
||||
}
|
||||
|
||||
static double _boxBaseline(RenderBox box, TextBaseline baseline) {
|
||||
return box.getDistanceToBaseline(baseline);
|
||||
}
|
||||
|
||||
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(_minLeadingWidth, leadingSize.width) + _horizontalTitleGap
|
||||
: 0.0;
|
||||
final BoxConstraints textConstraints = looseConstraints.tighten(
|
||||
width: tileWidth - titleStart - (hasTrailing ? trailingSize.width + _horizontalTitleGap : 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 + 2.0 * _minVerticalPadding);
|
||||
titleY = (tileHeight - titleSize.height) / 2.0;
|
||||
} else {
|
||||
assert(subtitleBaselineType != null);
|
||||
titleY = titleBaseline - _boxBaseline(title, titleBaselineType);
|
||||
subtitleY = subtitleBaseline - _boxBaseline(subtitle, subtitleBaselineType);
|
||||
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 plus
|
||||
// _minVerticalPadding on top and bottom.
|
||||
if (titleY < _minVerticalPadding ||
|
||||
(subtitleY + subtitleSize.height + _minVerticalPadding) > tileHeight) {
|
||||
tileHeight = titleSize.height + subtitleSize.height + 2.0 * _minVerticalPadding;
|
||||
titleY = _minVerticalPadding;
|
||||
subtitleY = titleSize.height + _minVerticalPadding;
|
||||
}
|
||||
}
|
||||
|
||||
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 + _horizontalTitleGap : 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;
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
|
||||
duration: kThemeChangeDuration,
|
||||
child: new Baseline(
|
||||
baseline: widget.height - _kBaselineOffsetFromBottom,
|
||||
baselineType: TextBaseline.alphabetic,
|
||||
baselineType: style.textBaseline,
|
||||
child: buildChild(),
|
||||
)
|
||||
);
|
||||
|
@ -1599,9 +1599,12 @@ abstract class RenderBox extends RenderObject {
|
||||
/// Only call this function after calling [layout] on this box. You
|
||||
/// are only allowed to call this from the parent of this box during
|
||||
/// that parent's [performLayout] or [paint] functions.
|
||||
///
|
||||
/// When implementing a [RenderBox] subclass, to override the baseline
|
||||
/// computation, override [computeDistanceToActualBaseline].
|
||||
double getDistanceToBaseline(TextBaseline baseline, { bool onlyReal: false }) {
|
||||
assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
||||
assert(!debugNeedsLayout);
|
||||
assert(!_debugDoingBaseline);
|
||||
assert(() {
|
||||
final RenderObject parent = this.parent;
|
||||
if (owner.debugDoingLayout)
|
||||
@ -1628,7 +1631,7 @@ abstract class RenderBox extends RenderObject {
|
||||
@protected
|
||||
@mustCallSuper
|
||||
double getDistanceToActualBaseline(TextBaseline baseline) {
|
||||
assert(_debugDoingBaseline);
|
||||
assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
||||
_cachedBaselines ??= <TextBaseline, double>{};
|
||||
_cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
|
||||
return _cachedBaselines[baseline];
|
||||
@ -1638,17 +1641,29 @@ abstract class RenderBox extends RenderObject {
|
||||
/// the y-coordinate of the first given baseline in the box's contents, if
|
||||
/// any, or null otherwise.
|
||||
///
|
||||
/// Do not call this function directly. Instead, call [getDistanceToBaseline]
|
||||
/// if you need to know the baseline of a child from an invocation of
|
||||
/// [performLayout] or [paint] and call [getDistanceToActualBaseline] if you
|
||||
/// are implementing [computeDistanceToActualBaseline] and need to defer to a
|
||||
/// child.
|
||||
/// Do not call this function directly. If you need to know the baseline of a
|
||||
/// child from an invocation of [performLayout] or [paint], call
|
||||
/// [getDistanceToBaseline].
|
||||
///
|
||||
/// Subclasses should override this method to supply the distances to their
|
||||
/// baselines.
|
||||
/// baselines. When implementing this method, there are generally three
|
||||
/// strategies:
|
||||
///
|
||||
/// * For classes that use the [ContainerRenderObjectMixin] child model,
|
||||
/// consider mixing in the [RenderBoxContainerDefaultsMixin] class and
|
||||
/// using
|
||||
/// [RenderBoxContainerDefaultsMixin.defaultComputeDistanceToFirstActualBaseline].
|
||||
///
|
||||
/// * For classes that define a particular baseline themselves, return that
|
||||
/// value directly.
|
||||
///
|
||||
/// * For classes that have a child to which they wish to defer the
|
||||
/// computation, call [getDistanceToActualBaseline] on the child (not
|
||||
/// [computeDistanceToActualBaseline], the internal implementation, and not
|
||||
/// [getDistanceToBaseline], the public entry point for this API).
|
||||
@protected
|
||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
assert(_debugDoingBaseline);
|
||||
assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1124,7 +1124,7 @@ class RenderBaseline extends RenderShiftedBox {
|
||||
RenderBaseline({
|
||||
RenderBox child,
|
||||
@required double baseline,
|
||||
@required TextBaseline baselineType
|
||||
@required TextBaseline baselineType,
|
||||
}) : assert(baseline != null),
|
||||
assert(baselineType != null),
|
||||
_baseline = baseline,
|
||||
|
@ -101,7 +101,7 @@ class Table extends RenderObjectWidget {
|
||||
this.textDirection,
|
||||
this.border,
|
||||
this.defaultVerticalAlignment: TableCellVerticalAlignment.top,
|
||||
this.textBaseline
|
||||
this.textBaseline,
|
||||
}) : assert(children != null),
|
||||
assert(defaultColumnWidth != null),
|
||||
assert(defaultVerticalAlignment != null),
|
||||
@ -213,7 +213,7 @@ class Table extends RenderObjectWidget {
|
||||
rowDecorations: _rowDecorations,
|
||||
configuration: createLocalImageConfiguration(context),
|
||||
defaultVerticalAlignment: defaultVerticalAlignment,
|
||||
textBaseline: textBaseline
|
||||
textBaseline: textBaseline,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -65,18 +65,11 @@ Widget _wrapForChip({
|
||||
double textScaleFactor: 1.0,
|
||||
}) {
|
||||
return new MaterialApp(
|
||||
home: new Localizations(
|
||||
locale: const Locale('en', 'US'),
|
||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||
DefaultWidgetsLocalizations.delegate,
|
||||
DefaultMaterialLocalizations.delegate,
|
||||
],
|
||||
child: new Directionality(
|
||||
textDirection: textDirection,
|
||||
child: new MediaQuery(
|
||||
data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
|
||||
child: new Material(child: child),
|
||||
),
|
||||
home: new Directionality(
|
||||
textDirection: textDirection,
|
||||
child: new MediaQuery(
|
||||
data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
|
||||
child: new Material(child: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -3,7 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Baseline - control test', (WidgetTester tester) async {
|
||||
@ -41,4 +41,108 @@ void main() {
|
||||
expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size,
|
||||
within<Size>(from: const Size(100.0, 200.0), distance: 0.001));
|
||||
});
|
||||
|
||||
testWidgets('Chip caches baseline', (WidgetTester tester) async {
|
||||
int calls = 0;
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Baseline(
|
||||
baseline: 100.0,
|
||||
baselineType: TextBaseline.alphabetic,
|
||||
child: new Chip(
|
||||
label: new BaselineDetector(() {
|
||||
calls += 1;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(calls, 1);
|
||||
await tester.pump();
|
||||
expect(calls, 1);
|
||||
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
|
||||
await tester.pump();
|
||||
expect(calls, 2);
|
||||
});
|
||||
|
||||
testWidgets('ListTile caches baseline', (WidgetTester tester) async {
|
||||
int calls = 0;
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Baseline(
|
||||
baseline: 100.0,
|
||||
baselineType: TextBaseline.alphabetic,
|
||||
child: new ListTile(
|
||||
title: new BaselineDetector(() {
|
||||
calls += 1;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(calls, 1);
|
||||
await tester.pump();
|
||||
expect(calls, 1);
|
||||
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
|
||||
await tester.pump();
|
||||
expect(calls, 2);
|
||||
});
|
||||
}
|
||||
|
||||
class BaselineDetector extends LeafRenderObjectWidget {
|
||||
const BaselineDetector(this.callback);
|
||||
|
||||
final VoidCallback callback;
|
||||
|
||||
@override
|
||||
RenderBaselineDetector createRenderObject(BuildContext context) => new RenderBaselineDetector(callback);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderBaselineDetector renderObject) {
|
||||
renderObject.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderBaselineDetector extends RenderBox {
|
||||
RenderBaselineDetector(this.callback);
|
||||
|
||||
VoidCallback callback;
|
||||
|
||||
@override
|
||||
bool get sizedByParent => true;
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) => 0.0;
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicWidth(double height) => 0.0;
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) => 0.0;
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) => 0.0;
|
||||
|
||||
@override
|
||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
if (callback != null)
|
||||
callback();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void dirty() {
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void performResize() {
|
||||
size = constraints.smallest;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) { }
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user