Commit bf216110 authored by Simon Lightfoot's avatar Simon Lightfoot Committed by Michael Goderbauer

Adds relayout option to CustomMultiChildLayout. (#39252)

parent 86d200a9
......@@ -40,4 +40,5 @@ Frederik Schweiger <mail@flschweiger.net>
Martin Staadecker <machstg@gmail.com>
Igor Katsuba <katsuba.igor@gmail.com>
Diego Velásquez <diego.velasquez.lopez@gmail.com>
Sarbagya Dhaubanjar <mail@sarbagyastha.com.np>
\ No newline at end of file
Simon Lightfoot <simon@devangels.london>
Sarbagya Dhaubanjar <mail@sarbagyastha.com.np>
......@@ -20,6 +20,9 @@ class MultiChildLayoutParentData extends ContainerBoxParentData<RenderBox> {
/// A delegate that controls the layout of multiple children.
///
/// Used with [CustomMultiChildLayout] (in the widgets library) and
/// [RenderCustomMultiChildLayoutBox] (in the rendering library).
///
/// Delegates must be idempotent. Specifically, if two delegates are equal, then
/// they must produce the same layout. To change the layout, replace the
/// delegate with a different instance whose [shouldRelayout] returns true when
......@@ -38,8 +41,11 @@ class MultiChildLayoutParentData extends ContainerBoxParentData<RenderBox> {
/// Override [shouldRelayout] to determine when the layout of the children needs
/// to be recomputed when the delegate changes.
///
/// Used with [CustomMultiChildLayout], the widget for the
/// [RenderCustomMultiChildLayoutBox] render object.
/// The most efficient way to trigger a relayout is to supply a `relayout`
/// argument to the constructor of the [MultiChildLayoutDelegate]. The custom
/// layout will listen to this value and relayout whenever the Listenable
/// notifies its listeners, such as when an [Animation] ticks. This allows
/// the custom layout to avoid the build phase of the pipeline.
///
/// Each child must be wrapped in a [LayoutId] widget to assign the id that
/// identifies it to the delegate. The [LayoutId.id] needs to be unique among
......@@ -94,7 +100,20 @@ class MultiChildLayoutParentData extends ContainerBoxParentData<RenderBox> {
/// The leader and follower widget will paint in the order they appear in the
/// child list, regardless of the order in which [layoutChild] is called on
/// them.
///
/// See also:
///
/// * [CustomMultiChildLayout], the widget that uses this delegate.
/// * [RenderCustomMultiChildLayoutBox], render object that uses this
/// delegate.
abstract class MultiChildLayoutDelegate {
/// Creates a layout delegate.
///
/// The layout will update whenever [relayout] notifies its listeners.
MultiChildLayoutDelegate({ Listenable relayout }) : _relayout = relayout;
final Listenable _relayout;
Map<Object, RenderBox> _idToChild;
Set<RenderBox> _debugChildrenNeedingLayout;
......@@ -300,13 +319,30 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
/// The delegate that controls the layout of the children.
MultiChildLayoutDelegate get delegate => _delegate;
MultiChildLayoutDelegate _delegate;
set delegate(MultiChildLayoutDelegate value) {
assert(value != null);
if (_delegate == value)
set delegate(MultiChildLayoutDelegate newDelegate) {
assert(newDelegate != null);
if (_delegate == newDelegate)
return;
if (value.runtimeType != _delegate.runtimeType || value.shouldRelayout(_delegate))
final MultiChildLayoutDelegate oldDelegate = _delegate;
if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
markNeedsLayout();
_delegate = value;
_delegate = newDelegate;
if (attached) {
oldDelegate?._relayout?.removeListener(markNeedsLayout);
newDelegate?._relayout?.addListener(markNeedsLayout);
}
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_delegate?._relayout?.addListener(markNeedsLayout);
}
@override
void detach() {
_delegate?._relayout?.removeListener(markNeedsLayout);
super.detach();
}
Size _getSize(BoxConstraints constraints) {
......
......@@ -962,10 +962,11 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
/// is provided, to check if the new instance actually represents different
/// information.
///
/// The most efficient way to trigger a relayout is to supply a relayout
/// The most efficient way to trigger a relayout is to supply a `relayout`
/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
/// object will listen to this value and relayout whenever the animation
/// ticks, avoiding both the build phase of the pipeline.
/// layout will listen to this value and relayout whenever the Listenable
/// notifies its listeners, such as when an [Animation] ticks. This allows
/// the custom layout to avoid the build phase of the pipeline.
///
/// See also:
///
......
......@@ -73,6 +73,23 @@ class PreferredSizeDelegate extends MultiChildLayoutDelegate {
}
}
class NotifierLayoutDelegate extends MultiChildLayoutDelegate {
NotifierLayoutDelegate(this.size) : super(relayout: size);
final ValueNotifier<Size> size;
@override
Size getSize(BoxConstraints constraints) => size.value;
@override
void performLayout(Size size) { }
@override
bool shouldRelayout(NotifierLayoutDelegate oldDelegate) {
return size != oldDelegate.size;
}
}
void main() {
testWidgets('Control test for CustomMultiChildLayout', (WidgetTester tester) async {
final TestMultiChildLayoutDelegate delegate = TestMultiChildLayoutDelegate();
......@@ -161,4 +178,25 @@ void main() {
expect(box.size.width, equals(350.0));
expect(box.size.height, equals(250.0));
});
testWidgets('Can use listener for relayout', (WidgetTester tester) async {
final ValueNotifier<Size> size = ValueNotifier<Size>(const Size(100.0, 200.0));
await tester.pumpWidget(
Center(
child: CustomMultiChildLayout(
delegate: NotifierLayoutDelegate(size),
),
),
);
RenderBox box = tester.renderObject(find.byType(CustomMultiChildLayout));
expect(box.size, equals(const Size(100.0, 200.0)));
size.value = const Size(150.0, 240.0);
await tester.pump();
box = tester.renderObject(find.byType(CustomMultiChildLayout));
expect(box.size, equals(const Size(150.0, 240.0)));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment