Merge pull request #2599 from abarth/rebuild_render_object_widget
Prepare to make RenderObjectElement buildable
This commit is contained in:
commit
3dc22dce58
@ -2319,9 +2319,17 @@ class KeyedSubtree extends StatelessComponent {
|
|||||||
Widget build(BuildContext context) => child;
|
Widget build(BuildContext context) => child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A platonic widget that invokes a closure to obtain its child widget.
|
||||||
class Builder extends StatelessComponent {
|
class Builder extends StatelessComponent {
|
||||||
Builder({ Key key, this.builder }) : super(key: key);
|
Builder({ Key key, this.builder }) : super(key: key);
|
||||||
|
|
||||||
|
/// Called to obtain the child widget.
|
||||||
|
///
|
||||||
|
/// This function is invoked whether this widget is included in its parent's
|
||||||
|
/// build and the old widget (if any) that it synchronizes with has a distinct
|
||||||
|
/// object identity.
|
||||||
final WidgetBuilder builder;
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
Widget build(BuildContext context) => builder(context);
|
Widget build(BuildContext context) => builder(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1482,14 +1482,11 @@ class InheritedElement extends _ProxyElement<InheritedWidget> {
|
|||||||
|
|
||||||
/// Base class for instantiations of RenderObjectWidget subclasses
|
/// Base class for instantiations of RenderObjectWidget subclasses
|
||||||
abstract class RenderObjectElement<T extends RenderObjectWidget> extends BuildableElement<T> {
|
abstract class RenderObjectElement<T extends RenderObjectWidget> extends BuildableElement<T> {
|
||||||
RenderObjectElement(T widget)
|
RenderObjectElement(T widget) : super(widget);
|
||||||
: _renderObject = widget.createRenderObject(), super(widget) {
|
|
||||||
assert(() { debugUpdateRenderObjectOwner(); return true; });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The underlying [RenderObject] for this element
|
/// The underlying [RenderObject] for this element
|
||||||
RenderObject get renderObject => _renderObject;
|
RenderObject get renderObject => _renderObject;
|
||||||
final RenderObject _renderObject;
|
RenderObject _renderObject;
|
||||||
|
|
||||||
RenderObjectElement _ancestorRenderObjectElement;
|
RenderObjectElement _ancestorRenderObjectElement;
|
||||||
|
|
||||||
@ -1512,8 +1509,9 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
|
|||||||
|
|
||||||
void mount(Element parent, dynamic newSlot) {
|
void mount(Element parent, dynamic newSlot) {
|
||||||
super.mount(parent, newSlot);
|
super.mount(parent, newSlot);
|
||||||
assert(_slot == newSlot);
|
_renderObject = widget.createRenderObject();
|
||||||
assert(() { debugUpdateRenderObjectOwner(); return true; });
|
assert(() { debugUpdateRenderObjectOwner(); return true; });
|
||||||
|
assert(_slot == newSlot);
|
||||||
attachRenderObject(newSlot);
|
attachRenderObject(newSlot);
|
||||||
_dirty = false;
|
_dirty = false;
|
||||||
}
|
}
|
||||||
@ -1532,29 +1530,9 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
|
|||||||
}
|
}
|
||||||
|
|
||||||
void performRebuild() {
|
void performRebuild() {
|
||||||
reinvokeBuilders();
|
|
||||||
_dirty = false;
|
_dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reinvokeBuilders() {
|
|
||||||
// There's no way to mark a normal RenderObjectElement dirty.
|
|
||||||
// We inherit from BuildableElement so that subclasses can themselves
|
|
||||||
// implement reinvokeBuilders() if they do provide a way to mark themeselves
|
|
||||||
// dirty, e.g. if they have a builder callback. (Builder callbacks have a
|
|
||||||
// 'BuildContext' argument which you can pass to Theme.of() and other
|
|
||||||
// InheritedWidget APIs which eventually trigger a rebuild.)
|
|
||||||
assert(() {
|
|
||||||
throw new WidgetError(
|
|
||||||
'$runtimeType failed to implement reinvokeBuilders(), but got marked dirty.\n'
|
|
||||||
'If a RenderObjectElement subclass supports being marked dirty, then the '
|
|
||||||
'reinvokeBuilders() method must be implemented.\n'
|
|
||||||
'If a RenderObjectElement uses a builder callback, it must support being '
|
|
||||||
'marked dirty, because builder callbacks can register the object as having '
|
|
||||||
'an Inherited dependency.'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function for subclasses that have one or more lists of children.
|
/// Utility function for subclasses that have one or more lists of children.
|
||||||
/// Attempts to update the given old children list using the given new
|
/// Attempts to update the given old children list using the given new
|
||||||
/// widgets, removing obsolete elements and introducing new ones as necessary,
|
/// widgets, removing obsolete elements and introducing new ones as necessary,
|
||||||
|
@ -192,15 +192,18 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
|
|||||||
// includes some of the state from that component. The component calls
|
// includes some of the state from that component. The component calls
|
||||||
// setState() on itself. It rebuilds. Part of that involves rebuilding
|
// setState() on itself. It rebuilds. Part of that involves rebuilding
|
||||||
// us, but now what? If we don't reinvoke the builders. then they will
|
// us, but now what? If we don't reinvoke the builders. then they will
|
||||||
// not be rebuilt, and so the new state won't be used.
|
// not be rebuilt, and so the new state won't be used. Therefore, we use
|
||||||
// Note that if the builders are to change so much that the _sizes_ of
|
// the object identity of the widget to determine whether to reinvoke the
|
||||||
|
// builders.
|
||||||
|
//
|
||||||
|
// If the builders are to change so much that the _sizes_ of
|
||||||
// the children would change, then the parent must change the 'token'.
|
// the children would change, then the parent must change the 'token'.
|
||||||
if (!renderObject.needsLayout)
|
if (!renderObject.needsLayout)
|
||||||
reinvokeBuilders();
|
performRebuild();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reinvokeBuilders() {
|
void performRebuild() {
|
||||||
// we just need to redraw our existing widgets as-is
|
// we just need to redraw our existing widgets as-is
|
||||||
if (_childrenByKey.length > 0) {
|
if (_childrenByKey.length > 0) {
|
||||||
assert(_firstVisibleChildIndex >= 0);
|
assert(_firstVisibleChildIndex >= 0);
|
||||||
@ -223,6 +226,7 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
|
|||||||
previousChild = newElement;
|
previousChild = newElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
super.performRebuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
void layout(BoxConstraints constraints) {
|
void layout(BoxConstraints constraints) {
|
||||||
@ -242,7 +246,7 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
|
|||||||
final double newExtent = _didReachLastChild ? _childOffsets.last : double.INFINITY;
|
final double newExtent = _didReachLastChild ? _childOffsets.last : double.INFINITY;
|
||||||
Size contentSize;
|
Size contentSize;
|
||||||
switch (widget.direction) {
|
switch (widget.direction) {
|
||||||
case Axis.vertical:
|
case Axis.vertical:
|
||||||
contentSize = new Size(containerSize.width, newExtent);
|
contentSize = new Size(containerSize.width, newExtent);
|
||||||
break;
|
break;
|
||||||
case Axis.horizontal:
|
case Axis.horizontal:
|
||||||
@ -257,7 +261,7 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
|
|||||||
_lastReportedDimensions = dimensions;
|
_lastReportedDimensions = dimensions;
|
||||||
Offset overrideOffset = widget.onPaintOffsetUpdateNeeded(dimensions);
|
Offset overrideOffset = widget.onPaintOffsetUpdateNeeded(dimensions);
|
||||||
switch (widget.direction) {
|
switch (widget.direction) {
|
||||||
case Axis.vertical:
|
case Axis.vertical:
|
||||||
assert(overrideOffset.dx == 0.0);
|
assert(overrideOffset.dx == 0.0);
|
||||||
_overrideStartOffset = overrideOffset.dy;
|
_overrideStartOffset = overrideOffset.dy;
|
||||||
break;
|
break;
|
||||||
|
@ -256,6 +256,8 @@ class _LazyWidgetProvider extends _WidgetProvider {
|
|||||||
List<Widget> _widgets;
|
List<Widget> _widgets;
|
||||||
|
|
||||||
void didUpdateWidget(VirtualViewportFromBuilder oldWidget, VirtualViewportFromBuilder newWidget) {
|
void didUpdateWidget(VirtualViewportFromBuilder oldWidget, VirtualViewportFromBuilder newWidget) {
|
||||||
|
// TODO(abarth): We shouldn't check the itemBuilder closure for equality with.
|
||||||
|
// instead, we should use the widget's identity to decide whether to rebuild.
|
||||||
if (_length != newWidget.itemCount || oldWidget?.itemBuilder != newWidget.itemBuilder) {
|
if (_length != newWidget.itemCount || oldWidget?.itemBuilder != newWidget.itemBuilder) {
|
||||||
_length = newWidget.itemCount;
|
_length = newWidget.itemCount;
|
||||||
_base = null;
|
_base = null;
|
||||||
|
@ -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';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'test_widgets.dart';
|
import 'test_widgets.dart';
|
||||||
@ -196,4 +196,48 @@ void main() {
|
|||||||
text.clear();
|
text.clear();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('MixedViewport reinvoke builders', () {
|
||||||
|
testWidgets((WidgetTester tester) {
|
||||||
|
StateSetter setState;
|
||||||
|
ThemeData themeData = new ThemeData.light();
|
||||||
|
|
||||||
|
IndexedBuilder itemBuilder = (BuildContext context, int i) {
|
||||||
|
return new Container(
|
||||||
|
key: new ValueKey<int>(i),
|
||||||
|
width: 500.0, // this should be ignored
|
||||||
|
height: 220.0,
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor
|
||||||
|
),
|
||||||
|
child: new Text("$i")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Widget viewport = new MixedViewport(builder: itemBuilder);
|
||||||
|
|
||||||
|
tester.pumpWidget(
|
||||||
|
new StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setter) {
|
||||||
|
setState = setter;
|
||||||
|
return new Theme(data: themeData, child: viewport);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DecoratedBox widget = tester.findWidgetOfType(DecoratedBox);
|
||||||
|
BoxDecoration decoraton = widget.decoration;
|
||||||
|
expect(decoraton.backgroundColor, equals(Colors.blue[500]));
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
themeData = new ThemeData(primarySwatch: Colors.green);
|
||||||
|
});
|
||||||
|
|
||||||
|
tester.pump();
|
||||||
|
|
||||||
|
widget = tester.findWidgetOfType(DecoratedBox);
|
||||||
|
decoraton = widget.decoration;
|
||||||
|
expect(decoraton.backgroundColor, equals(Colors.green[500]));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user