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