Don't relayout a Text if only its color changed. (#11313)
This commit is contained in:
parent
6dbf2269f0
commit
194bf41ee8
@ -52,3 +52,41 @@ export 'dart:ui' show
|
||||
// - window, WindowPadding
|
||||
// These are generally wrapped by other APIs so we always refer to them directly
|
||||
// as ui.* to avoid making them seem like high-level APIs.
|
||||
|
||||
/// The description of the difference between two objects, in the context of how
|
||||
/// it will affect the rendering.
|
||||
///
|
||||
/// Used by [TextSpan.compareTo] and [TextStyle.compareTo].
|
||||
///
|
||||
/// The values in this enum are ordered such that they are in increasing order
|
||||
/// of cost. A value with index N implies all the values with index less than N.
|
||||
/// For example, [layout] (index 3) implies [paint] (2).
|
||||
enum RenderComparison {
|
||||
/// The two objects are identical (meaning deeply equal, not necessarily
|
||||
/// [identical]).
|
||||
identical,
|
||||
|
||||
/// The two objects are identical for the purpose of layout, but may be different
|
||||
/// in other ways.
|
||||
///
|
||||
/// For example, maybe some event handlers changed.
|
||||
metadata,
|
||||
|
||||
/// The two objects are different but only in ways that affect paint, not layout.
|
||||
///
|
||||
/// For example, only the color is changed.
|
||||
///
|
||||
/// [RenderObject.markNeedsPaint] would be necessary to handle this kind of
|
||||
/// change in a render object.
|
||||
paint,
|
||||
|
||||
/// The two objects are different in ways that affect layout (and therefore paint).
|
||||
///
|
||||
/// For example, the size is changed.
|
||||
///
|
||||
/// This is the most drastic level of change possible.
|
||||
///
|
||||
/// [RenderObject.markNeedsLayout] would be necessary to handle this kind of
|
||||
/// change in a render object.
|
||||
layout,
|
||||
}
|
||||
|
@ -317,6 +317,39 @@ class TextSpan {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Describe the difference between this text span and another, in terms of
|
||||
/// how much damage it will make to the rendering. The comparison is deep.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TextStyle.compareTo], which does the same thing for [TextStyle]s.
|
||||
RenderComparison compareTo(TextSpan other) {
|
||||
if (identical(this, other))
|
||||
return RenderComparison.identical;
|
||||
if (other.text != text ||
|
||||
children?.length != other.children?.length ||
|
||||
(style == null) != (other.style == null))
|
||||
return RenderComparison.layout;
|
||||
RenderComparison result = recognizer == other.recognizer ? RenderComparison.identical : RenderComparison.metadata;
|
||||
if (style != null) {
|
||||
final RenderComparison candidate = style.compareTo(other.style);
|
||||
if (candidate.index > result.index)
|
||||
result = candidate;
|
||||
if (result == RenderComparison.layout)
|
||||
return result;
|
||||
}
|
||||
if (children != null) {
|
||||
for (int index = 0; index < children.length; index += 1) {
|
||||
final RenderComparison candidate = children[index].compareTo(other.children[index]);
|
||||
if (candidate.index > result.index)
|
||||
result = candidate;
|
||||
if (result == RenderComparison.layout)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other))
|
||||
|
@ -387,6 +387,33 @@ class TextStyle {
|
||||
);
|
||||
}
|
||||
|
||||
/// Describe the difference between this style and another, in terms of how
|
||||
/// much damage it will make to the rendering.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TextSpan.compareTo], which does the same thing for entire [TextSpan]s.
|
||||
RenderComparison compareTo(TextStyle other) {
|
||||
if (identical(this, other))
|
||||
return RenderComparison.identical;
|
||||
if (inherit != other.inherit ||
|
||||
fontFamily != other.fontFamily ||
|
||||
fontSize != other.fontSize ||
|
||||
fontWeight != other.fontWeight ||
|
||||
fontStyle != other.fontStyle ||
|
||||
letterSpacing != other.letterSpacing ||
|
||||
wordSpacing != other.wordSpacing ||
|
||||
textBaseline != other.textBaseline ||
|
||||
height != other.height)
|
||||
return RenderComparison.layout;
|
||||
if (color != other.color ||
|
||||
decoration != other.decoration ||
|
||||
decorationColor != other.decorationColor ||
|
||||
decorationStyle != other.decorationStyle)
|
||||
return RenderComparison.paint;
|
||||
return RenderComparison.identical;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other))
|
||||
|
@ -1559,6 +1559,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
/// to condition their runtime behavior on whether they are dirty or not,
|
||||
/// since they should only be marked dirty immediately prior to being laid
|
||||
/// out and painted.
|
||||
///
|
||||
/// It is intended to be used by tests and asserts.
|
||||
bool get debugNeedsLayout {
|
||||
bool result;
|
||||
assert(() {
|
||||
@ -2133,6 +2135,28 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
_needsCompositingBitsUpdate = false;
|
||||
}
|
||||
|
||||
/// Whether this render object's paint information is dirty.
|
||||
///
|
||||
/// This is only set in debug mode. In general, render objects should not need
|
||||
/// to condition their runtime behavior on whether they are dirty or not,
|
||||
/// since they should only be marked dirty immediately prior to being laid
|
||||
/// out and painted.
|
||||
///
|
||||
/// It is intended to be used by tests and asserts.
|
||||
///
|
||||
/// It is possible (and indeed, quite common) for [debugNeedsPaint] to be
|
||||
/// false and [debugNeedsLayout] to be true. The render object will still be
|
||||
/// repainted in the next frame when this is the case, because the
|
||||
/// [markNeedsPaint] method is implicitly called by the framework after a
|
||||
/// render object is laid out, prior to the paint phase.
|
||||
bool get debugNeedsPaint {
|
||||
bool result;
|
||||
assert(() {
|
||||
result = _needsPaint;
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
bool _needsPaint = true;
|
||||
|
||||
/// Mark this render object as having changed its visual appearance.
|
||||
@ -2145,6 +2169,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
///
|
||||
/// This mechanism batches the painting work so that multiple sequential
|
||||
/// writes are coalesced, removing redundant computation.
|
||||
///
|
||||
/// Once [markNeedsPaint] has been called on a render object,
|
||||
/// [debugNeedsPaint] returns true for that render object until just after
|
||||
/// the pipeline owner has called [paint] on the render object.
|
||||
void markNeedsPaint() {
|
||||
assert(owner == null || !owner.debugDoingPaint);
|
||||
if (_needsPaint)
|
||||
|
@ -64,11 +64,20 @@ class RenderParagraph extends RenderBox {
|
||||
TextSpan get text => _textPainter.text;
|
||||
set text(TextSpan value) {
|
||||
assert(value != null);
|
||||
if (_textPainter.text == value)
|
||||
return;
|
||||
_textPainter.text = value;
|
||||
_overflowShader = null;
|
||||
markNeedsLayout();
|
||||
switch (_textPainter.text.compareTo(value)) {
|
||||
case RenderComparison.identical:
|
||||
case RenderComparison.metadata:
|
||||
return;
|
||||
case RenderComparison.paint:
|
||||
_textPainter.text = value;
|
||||
markNeedsPaint();
|
||||
break;
|
||||
case RenderComparison.layout:
|
||||
_textPainter.text = value;
|
||||
_overflowShader = null;
|
||||
markNeedsLayout();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// How the text should be aligned horizontally.
|
||||
|
@ -177,6 +177,36 @@ void main() {
|
||||
expect(paragraph.size.height, 30.0);
|
||||
});
|
||||
|
||||
test('changing color does not do layout', () {
|
||||
final RenderParagraph paragraph = new RenderParagraph(
|
||||
const TextSpan(
|
||||
text: 'Hello',
|
||||
style: const TextStyle(color: const Color(0xFF000000)),
|
||||
),
|
||||
);
|
||||
layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0), phase: EnginePhase.paint);
|
||||
expect(paragraph.debugNeedsLayout, isFalse);
|
||||
expect(paragraph.debugNeedsPaint, isFalse);
|
||||
paragraph.text = const TextSpan(
|
||||
text: 'Hello World',
|
||||
style: const TextStyle(color: const Color(0xFF000000)),
|
||||
);
|
||||
expect(paragraph.debugNeedsLayout, isTrue);
|
||||
expect(paragraph.debugNeedsPaint, isFalse);
|
||||
pumpFrame(phase: EnginePhase.paint);
|
||||
expect(paragraph.debugNeedsLayout, isFalse);
|
||||
expect(paragraph.debugNeedsPaint, isFalse);
|
||||
paragraph.text = const TextSpan(
|
||||
text: 'Hello World',
|
||||
style: const TextStyle(color: const Color(0xFFFFFFFF)),
|
||||
);
|
||||
expect(paragraph.debugNeedsLayout, isFalse);
|
||||
expect(paragraph.debugNeedsPaint, isTrue);
|
||||
pumpFrame(phase: EnginePhase.paint);
|
||||
expect(paragraph.debugNeedsLayout, isFalse);
|
||||
expect(paragraph.debugNeedsPaint, isFalse);
|
||||
});
|
||||
|
||||
test('toStringDeep', () {
|
||||
final RenderParagraph paragraph = new RenderParagraph(
|
||||
const TextSpan(text: _kText),
|
||||
|
Loading…
x
Reference in New Issue
Block a user