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
|
@override
|
||||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||||
// The baseline of this widget is the baseline of the label.
|
// 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) {
|
Size _layoutLabel(double iconSizes, Size size) {
|
||||||
|
@ -537,10 +537,15 @@ class _RenderDecorationLayout {
|
|||||||
// The workhorse: layout and paint a _Decorator widget's _Decoration.
|
// The workhorse: layout and paint a _Decorator widget's _Decoration.
|
||||||
class _RenderDecoration extends RenderBox {
|
class _RenderDecoration extends RenderBox {
|
||||||
_RenderDecoration({
|
_RenderDecoration({
|
||||||
_Decoration decoration,
|
@required _Decoration decoration,
|
||||||
TextDirection textDirection,
|
@required TextDirection textDirection,
|
||||||
}) : _decoration = decoration,
|
@required TextBaseline textBaseline,
|
||||||
_textDirection = textDirection;
|
}) : assert(decoration != null),
|
||||||
|
assert(textDirection != null),
|
||||||
|
assert(textBaseline != null),
|
||||||
|
_decoration = decoration,
|
||||||
|
_textDirection = textDirection,
|
||||||
|
_textBaseline = textBaseline;
|
||||||
|
|
||||||
final Map<_DecorationSlot, RenderBox> slotToChild = <_DecorationSlot, RenderBox>{};
|
final Map<_DecorationSlot, RenderBox> slotToChild = <_DecorationSlot, RenderBox>{};
|
||||||
final Map<RenderBox, _DecorationSlot> childToSlot = <RenderBox, _DecorationSlot>{};
|
final Map<RenderBox, _DecorationSlot> childToSlot = <RenderBox, _DecorationSlot>{};
|
||||||
@ -654,6 +659,7 @@ class _RenderDecoration extends RenderBox {
|
|||||||
_Decoration get decoration => _decoration;
|
_Decoration get decoration => _decoration;
|
||||||
_Decoration _decoration;
|
_Decoration _decoration;
|
||||||
set decoration(_Decoration value) {
|
set decoration(_Decoration value) {
|
||||||
|
assert(value != null);
|
||||||
if (_decoration == value)
|
if (_decoration == value)
|
||||||
return;
|
return;
|
||||||
_decoration = value;
|
_decoration = value;
|
||||||
@ -663,12 +669,23 @@ class _RenderDecoration extends RenderBox {
|
|||||||
TextDirection get textDirection => _textDirection;
|
TextDirection get textDirection => _textDirection;
|
||||||
TextDirection _textDirection;
|
TextDirection _textDirection;
|
||||||
set textDirection(TextDirection value) {
|
set textDirection(TextDirection value) {
|
||||||
|
assert(value != null);
|
||||||
if (_textDirection == value)
|
if (_textDirection == value)
|
||||||
return;
|
return;
|
||||||
_textDirection = value;
|
_textDirection = value;
|
||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextBaseline get textBaseline => _textBaseline;
|
||||||
|
TextBaseline _textBaseline;
|
||||||
|
set textBaseline(TextBaseline value) {
|
||||||
|
assert(value != null);
|
||||||
|
if (_textBaseline == value)
|
||||||
|
return;
|
||||||
|
_textBaseline = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void attach(PipelineOwner owner) {
|
void attach(PipelineOwner owner) {
|
||||||
super.attach(owner);
|
super.attach(owner);
|
||||||
@ -748,7 +765,7 @@ class _RenderDecoration extends RenderBox {
|
|||||||
if (box == null)
|
if (box == null)
|
||||||
return;
|
return;
|
||||||
box.layout(boxConstraints, parentUsesSize: true);
|
box.layout(boxConstraints, parentUsesSize: true);
|
||||||
final double baseline = box.getDistanceToBaseline(TextBaseline.alphabetic);
|
final double baseline = box.getDistanceToBaseline(textBaseline);
|
||||||
assert(baseline != null && baseline >= 0.0);
|
assert(baseline != null && baseline >= 0.0);
|
||||||
boxToBaseline[box] = baseline;
|
boxToBaseline[box] = baseline;
|
||||||
aboveBaseline = math.max(baseline, aboveBaseline);
|
aboveBaseline = math.max(baseline, aboveBaseline);
|
||||||
@ -913,7 +930,15 @@ class _RenderDecoration extends RenderBox {
|
|||||||
width: overallWidth - _boxSize(icon).width,
|
width: overallWidth - _boxSize(icon).width,
|
||||||
);
|
);
|
||||||
container.layout(containerConstraints, parentUsesSize: true);
|
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);
|
_boxParentData(container).offset = new Offset(x, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -938,7 +963,15 @@ class _RenderDecoration extends RenderBox {
|
|||||||
: layout.outlineBaseline;
|
: layout.outlineBaseline;
|
||||||
|
|
||||||
if (icon != null) {
|
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);
|
centerLayout(icon, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1004,9 +1037,14 @@ class _RenderDecoration extends RenderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
decoration.borderGap.start = textDirection == TextDirection.rtl
|
switch (textDirection) {
|
||||||
? _boxParentData(label).offset.dx + label.size.width
|
case TextDirection.rtl:
|
||||||
: _boxParentData(label).offset.dx;
|
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;
|
decoration.borderGap.extent = label.size.width * 0.75;
|
||||||
} else {
|
} else {
|
||||||
decoration.borderGap.start = null;
|
decoration.borderGap.start = null;
|
||||||
@ -1039,9 +1077,15 @@ class _RenderDecoration extends RenderBox {
|
|||||||
final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline;
|
final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline;
|
||||||
final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
|
final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
|
||||||
final double scale = lerpDouble(1.0, 0.75, t);
|
final double scale = lerpDouble(1.0, 0.75, t);
|
||||||
final double dx = textDirection == TextDirection.rtl
|
double dx;
|
||||||
? labelOffset.dx + label.size.width * (1.0 - scale) // origin is on the right
|
switch (textDirection) {
|
||||||
: labelOffset.dx; // origin on the left
|
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);
|
final double dy = lerpDouble(0.0, floatingY - labelOffset.dy, t);
|
||||||
_labelTransform = new Matrix4.identity()
|
_labelTransform = new Matrix4.identity()
|
||||||
..translate(dx, labelOffset.dy + dy)
|
..translate(dx, labelOffset.dy + dy)
|
||||||
@ -1237,10 +1281,17 @@ class _RenderDecorationElement extends RenderObjectElement {
|
|||||||
class _Decorator extends RenderObjectWidget {
|
class _Decorator extends RenderObjectWidget {
|
||||||
const _Decorator({
|
const _Decorator({
|
||||||
Key key,
|
Key key,
|
||||||
this.decoration,
|
@required this.decoration,
|
||||||
}) : super(key: key);
|
@required this.textDirection,
|
||||||
|
@required this.textBaseline,
|
||||||
|
}) : assert(decoration != null),
|
||||||
|
assert(textDirection != null),
|
||||||
|
assert(textBaseline != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
final _Decoration decoration;
|
final _Decoration decoration;
|
||||||
|
final TextDirection textDirection;
|
||||||
|
final TextBaseline textBaseline;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RenderDecorationElement createElement() => new _RenderDecorationElement(this);
|
_RenderDecorationElement createElement() => new _RenderDecorationElement(this);
|
||||||
@ -1249,7 +1300,8 @@ class _Decorator extends RenderObjectWidget {
|
|||||||
_RenderDecoration createRenderObject(BuildContext context) {
|
_RenderDecoration createRenderObject(BuildContext context) {
|
||||||
return new _RenderDecoration(
|
return new _RenderDecoration(
|
||||||
decoration: decoration,
|
decoration: decoration,
|
||||||
textDirection: Directionality.of(context),
|
textDirection: textDirection,
|
||||||
|
textBaseline: textBaseline,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1257,7 +1309,8 @@ class _Decorator extends RenderObjectWidget {
|
|||||||
void updateRenderObject(BuildContext context, _RenderDecoration renderObject) {
|
void updateRenderObject(BuildContext context, _RenderDecoration renderObject) {
|
||||||
renderObject
|
renderObject
|
||||||
..decoration = decoration
|
..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
|
/// If null, `baseStyle` defaults to the `subhead` style from the
|
||||||
/// current [Theme], see [ThemeData.textTheme].
|
/// current [Theme], see [ThemeData.textTheme].
|
||||||
|
///
|
||||||
|
/// The [TextStyle.textBaseline] of the [baseStyle] is used to determine
|
||||||
|
/// the baseline used for text alignment.
|
||||||
final TextStyle baseStyle;
|
final TextStyle baseStyle;
|
||||||
|
|
||||||
/// How the text in the decoration should be aligned horizontally.
|
/// 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) {
|
Widget build(BuildContext context) {
|
||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
final TextStyle inlineStyle = _getInlineStyle(themeData);
|
final TextStyle inlineStyle = _getInlineStyle(themeData);
|
||||||
|
final TextBaseline textBaseline = inlineStyle.textBaseline;
|
||||||
|
|
||||||
final TextStyle hintStyle = inlineStyle.merge(decoration.hintStyle);
|
final TextStyle hintStyle = inlineStyle.merge(decoration.hintStyle);
|
||||||
final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity(
|
final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity(
|
||||||
@ -1713,6 +1770,8 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
counter: counter,
|
counter: counter,
|
||||||
container: container,
|
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(
|
final Widget titleText = new AnimatedDefaultTextStyle(
|
||||||
style: _titleTextStyle(theme, tileTheme),
|
style: titleStyle,
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
child: title ?? const SizedBox()
|
child: title ?? const SizedBox()
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget subtitleText;
|
Widget subtitleText;
|
||||||
|
TextStyle subtitleStyle;
|
||||||
if (subtitle != null) {
|
if (subtitle != null) {
|
||||||
|
subtitleStyle = _subtitleTextStyle(theme, tileTheme);
|
||||||
subtitleText = new AnimatedDefaultTextStyle(
|
subtitleText = new AnimatedDefaultTextStyle(
|
||||||
style: _subtitleTextStyle(theme, tileTheme),
|
style: subtitleStyle,
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
child: subtitle,
|
child: subtitle,
|
||||||
);
|
);
|
||||||
@ -466,6 +469,9 @@ class ListTile extends StatelessWidget {
|
|||||||
trailing: trailingIcon,
|
trailing: trailingIcon,
|
||||||
isDense: _isDenseLayout(tileTheme),
|
isDense: _isDenseLayout(tileTheme),
|
||||||
isThreeLine: isThreeLine,
|
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.
|
// Identifies the children of a _ListTileElement.
|
||||||
enum _ListTileSlot {
|
enum _ListTileSlot {
|
||||||
leading,
|
leading,
|
||||||
@ -520,348 +487,61 @@ enum _ListTileSlot {
|
|||||||
trailing,
|
trailing,
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RenderListTile extends RenderBox {
|
class _ListTile extends RenderObjectWidget {
|
||||||
_RenderListTile({
|
const _ListTile({
|
||||||
bool isDense,
|
Key key,
|
||||||
bool isThreeLine,
|
this.leading,
|
||||||
TextDirection textDirection,
|
this.title,
|
||||||
}) : _isDense = isDense,
|
this.subtitle,
|
||||||
_isThreeLine = isThreeLine,
|
this.trailing,
|
||||||
_textDirection = textDirection;
|
@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;
|
final Widget leading;
|
||||||
// The horizontal gap between the titles and the leading/trailing widgets
|
final Widget title;
|
||||||
static const double _horizontalTitleGap = 16.0;
|
final Widget subtitle;
|
||||||
// The minimum padding on the top and bottom of the title and subtitle widgets.
|
final Widget trailing;
|
||||||
static const double _minVerticalPadding = 4.0;
|
final bool isThreeLine;
|
||||||
|
final bool isDense;
|
||||||
final Map<_ListTileSlot, RenderBox> slotToChild = <_ListTileSlot, RenderBox>{};
|
final TextDirection textDirection;
|
||||||
final Map<RenderBox, _ListTileSlot> childToSlot = <RenderBox, _ListTileSlot>{};
|
final TextBaseline titleBaselineType;
|
||||||
|
final TextBaseline subtitleBaselineType;
|
||||||
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
|
@override
|
||||||
void attach(PipelineOwner owner) {
|
_ListTileElement createElement() => new _ListTileElement(this);
|
||||||
super.attach(owner);
|
|
||||||
for (RenderBox child in _children)
|
|
||||||
child.attach(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void detach() {
|
_RenderListTile createRenderObject(BuildContext context) {
|
||||||
super.detach();
|
return new _RenderListTile(
|
||||||
for (RenderBox child in _children)
|
isThreeLine: isThreeLine,
|
||||||
child.detach();
|
isDense: isDense,
|
||||||
}
|
textDirection: textDirection,
|
||||||
|
titleBaselineType: titleBaselineType,
|
||||||
@override
|
subtitleBaselineType: subtitleBaselineType,
|
||||||
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
|
@override
|
||||||
double computeMaxIntrinsicHeight(double width) {
|
void updateRenderObject(BuildContext context, _RenderListTile renderObject) {
|
||||||
return computeMinIntrinsicHeight(width);
|
renderObject
|
||||||
}
|
..isThreeLine = isThreeLine
|
||||||
|
..isDense = isDense
|
||||||
@override
|
..textDirection = textDirection
|
||||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
..titleBaselineType = titleBaselineType
|
||||||
assert(title != null);
|
..subtitleBaselineType = subtitleBaselineType;
|
||||||
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;
|
class _ListTileElement extends RenderObjectElement {
|
||||||
final double trailingY = (tileHeight - trailingSize.height) / 2.0;
|
_ListTileElement(_ListTile widget) : super(widget);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RenderListTileElement extends RenderObjectElement {
|
|
||||||
_RenderListTileElement(_ListTile widget) : super(widget);
|
|
||||||
|
|
||||||
final Map<_ListTileSlot, Element> slotToChild = <_ListTileSlot, Element>{};
|
final Map<_ListTileSlot, Element> slotToChild = <_ListTileSlot, Element>{};
|
||||||
final Map<Element, _ListTileSlot> childToSlot = <Element, _ListTileSlot>{};
|
final Map<Element, _ListTileSlot> childToSlot = <Element, _ListTileSlot>{};
|
||||||
@ -972,3 +652,374 @@ class _RenderListTileElement extends RenderObjectElement {
|
|||||||
assert(false, 'not reachable');
|
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,
|
duration: kThemeChangeDuration,
|
||||||
child: new Baseline(
|
child: new Baseline(
|
||||||
baseline: widget.height - _kBaselineOffsetFromBottom,
|
baseline: widget.height - _kBaselineOffsetFromBottom,
|
||||||
baselineType: TextBaseline.alphabetic,
|
baselineType: style.textBaseline,
|
||||||
child: buildChild(),
|
child: buildChild(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -1599,9 +1599,12 @@ abstract class RenderBox extends RenderObject {
|
|||||||
/// Only call this function after calling [layout] on this box. You
|
/// Only call this function after calling [layout] on this box. You
|
||||||
/// are only allowed to call this from the parent of this box during
|
/// are only allowed to call this from the parent of this box during
|
||||||
/// that parent's [performLayout] or [paint] functions.
|
/// 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 }) {
|
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(!debugNeedsLayout);
|
||||||
assert(!_debugDoingBaseline);
|
|
||||||
assert(() {
|
assert(() {
|
||||||
final RenderObject parent = this.parent;
|
final RenderObject parent = this.parent;
|
||||||
if (owner.debugDoingLayout)
|
if (owner.debugDoingLayout)
|
||||||
@ -1628,7 +1631,7 @@ abstract class RenderBox extends RenderObject {
|
|||||||
@protected
|
@protected
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
double getDistanceToActualBaseline(TextBaseline baseline) {
|
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 ??= <TextBaseline, double>{};
|
||||||
_cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
|
_cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
|
||||||
return _cachedBaselines[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
|
/// the y-coordinate of the first given baseline in the box's contents, if
|
||||||
/// any, or null otherwise.
|
/// any, or null otherwise.
|
||||||
///
|
///
|
||||||
/// Do not call this function directly. Instead, call [getDistanceToBaseline]
|
/// Do not call this function directly. If you need to know the baseline of a
|
||||||
/// if you need to know the baseline of a child from an invocation of
|
/// child from an invocation of [performLayout] or [paint], call
|
||||||
/// [performLayout] or [paint] and call [getDistanceToActualBaseline] if you
|
/// [getDistanceToBaseline].
|
||||||
/// are implementing [computeDistanceToActualBaseline] and need to defer to a
|
|
||||||
/// child.
|
|
||||||
///
|
///
|
||||||
/// Subclasses should override this method to supply the distances to their
|
/// 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
|
@protected
|
||||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||||
assert(_debugDoingBaseline);
|
assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1124,7 +1124,7 @@ class RenderBaseline extends RenderShiftedBox {
|
|||||||
RenderBaseline({
|
RenderBaseline({
|
||||||
RenderBox child,
|
RenderBox child,
|
||||||
@required double baseline,
|
@required double baseline,
|
||||||
@required TextBaseline baselineType
|
@required TextBaseline baselineType,
|
||||||
}) : assert(baseline != null),
|
}) : assert(baseline != null),
|
||||||
assert(baselineType != null),
|
assert(baselineType != null),
|
||||||
_baseline = baseline,
|
_baseline = baseline,
|
||||||
|
@ -101,7 +101,7 @@ class Table extends RenderObjectWidget {
|
|||||||
this.textDirection,
|
this.textDirection,
|
||||||
this.border,
|
this.border,
|
||||||
this.defaultVerticalAlignment: TableCellVerticalAlignment.top,
|
this.defaultVerticalAlignment: TableCellVerticalAlignment.top,
|
||||||
this.textBaseline
|
this.textBaseline,
|
||||||
}) : assert(children != null),
|
}) : assert(children != null),
|
||||||
assert(defaultColumnWidth != null),
|
assert(defaultColumnWidth != null),
|
||||||
assert(defaultVerticalAlignment != null),
|
assert(defaultVerticalAlignment != null),
|
||||||
@ -213,7 +213,7 @@ class Table extends RenderObjectWidget {
|
|||||||
rowDecorations: _rowDecorations,
|
rowDecorations: _rowDecorations,
|
||||||
configuration: createLocalImageConfiguration(context),
|
configuration: createLocalImageConfiguration(context),
|
||||||
defaultVerticalAlignment: defaultVerticalAlignment,
|
defaultVerticalAlignment: defaultVerticalAlignment,
|
||||||
textBaseline: textBaseline
|
textBaseline: textBaseline,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,20 +65,13 @@ Widget _wrapForChip({
|
|||||||
double textScaleFactor: 1.0,
|
double textScaleFactor: 1.0,
|
||||||
}) {
|
}) {
|
||||||
return new MaterialApp(
|
return new MaterialApp(
|
||||||
home: new Localizations(
|
home: new Directionality(
|
||||||
locale: const Locale('en', 'US'),
|
|
||||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
|
||||||
DefaultWidgetsLocalizations.delegate,
|
|
||||||
DefaultMaterialLocalizations.delegate,
|
|
||||||
],
|
|
||||||
child: new Directionality(
|
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
child: new MediaQuery(
|
child: new MediaQuery(
|
||||||
data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
|
data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
|
||||||
child: new Material(child: child),
|
child: new Material(child: child),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Baseline - control test', (WidgetTester tester) async {
|
testWidgets('Baseline - control test', (WidgetTester tester) async {
|
||||||
@ -41,4 +41,108 @@ void main() {
|
|||||||
expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size,
|
expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size,
|
||||||
within<Size>(from: const Size(100.0, 200.0), distance: 0.001));
|
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