1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/rendering.dart';
6
7import 'framework.dart';
8import 'notification_listener.dart';
9
10/// Indicates that the size of one of the descendants of the object receiving
11/// this notification has changed, and that therefore any assumptions about that
12/// layout are no longer valid.
13///
14/// For example, sent by the [SizeChangedLayoutNotifier] widget whenever that
15/// widget changes size.
16///
17/// This notification can be used for triggering repaints, but if you use this
18/// notification to trigger rebuilds or relayouts, you'll create a backwards
19/// dependency in the frame pipeline because [SizeChangedLayoutNotification]s
20/// are generated during layout, which is after the build phase and in the
21/// middle of the layout phase. This backwards dependency can lead to visual
22/// corruption or lags.
23///
24/// See [LayoutChangedNotification] for additional discussion of layout
25/// notifications such as this one.
26///
27/// See also:
28///
29/// * [SizeChangedLayoutNotifier], which sends this notification.
30/// * [LayoutChangedNotification], of which this is a subclass.
31class SizeChangedLayoutNotification extends LayoutChangedNotification {
32 /// Create a new [SizeChangedLayoutNotification].
33 const SizeChangedLayoutNotification();
34}
35
36/// A widget that automatically dispatches a [SizeChangedLayoutNotification]
37/// when the layout dimensions of its child change.
38///
39/// The notification is not sent for the initial layout (since the size doesn't
40/// change in that case, it's just established).
41///
42/// To listen for the notification dispatched by this widget, use a
43/// [NotificationListener<SizeChangedLayoutNotification>].
44///
45/// The [Material] class listens for [LayoutChangedNotification]s, including
46/// [SizeChangedLayoutNotification]s, to repaint [InkResponse] and [InkWell] ink
47/// effects. When a widget is likely to change size, wrapping it in a
48/// [SizeChangedLayoutNotifier] will cause the ink effects to correctly repaint
49/// when the child changes size.
50///
51/// See also:
52///
53/// * [Notification], the base class for notifications that bubble through the
54/// widget tree.
55class SizeChangedLayoutNotifier extends SingleChildRenderObjectWidget {
56 /// Creates a [SizeChangedLayoutNotifier] that dispatches layout changed
57 /// notifications when [child] changes layout size.
58 const SizeChangedLayoutNotifier({
59 super.key,
60 super.child,
61 });
62
63 @override
64 RenderObject createRenderObject(BuildContext context) {
65 return _RenderSizeChangedWithCallback(
66 onLayoutChangedCallback: () {
67 const SizeChangedLayoutNotification().dispatch(context);
68 },
69 );
70 }
71}
72
73class _RenderSizeChangedWithCallback extends RenderProxyBox {
74 _RenderSizeChangedWithCallback({
75 RenderBox? child,
76 required this.onLayoutChangedCallback,
77 }) : super(child);
78
79 // There's a 1:1 relationship between the _RenderSizeChangedWithCallback and
80 // the `context` that is captured by the closure created by createRenderObject
81 // above to assign to onLayoutChangedCallback, and thus we know that the
82 // onLayoutChangedCallback will never change nor need to change.
83
84 final VoidCallback onLayoutChangedCallback;
85
86 Size? _oldSize;
87
88 @override
89 void performLayout() {
90 super.performLayout();
91 // Don't send the initial notification, or this will be SizeObserver all
92 // over again!
93 if (_oldSize != null && size != _oldSize) {
94 onLayoutChangedCallback();
95 }
96 _oldSize = size;
97 }
98}
99