passthrough_overlay.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. // Copyright 2015 The Chromium 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. import 'dart:async';
  5. import 'dart:collection';
  6. import 'package:flutter/foundation.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter/rendering.dart';
  9. import 'package:flutter/scheduler.dart';
  10. //import 'basic.dart';
  11. //import 'debug.dart';
  12. //import 'framework.dart';
  13. //import 'ticker_provider.dart';
  14. /// A place in an [Overlay] that can contain a widget.
  15. ///
  16. /// Overlay entries are inserted into an [Overlay] using the
  17. /// [OverlayState.insert] or [OverlayState.insertAll] functions. To find the
  18. /// closest enclosing overlay for a given [BuildContext], use the [Overlay.of]
  19. /// function.
  20. ///
  21. /// An overlay entry can be in at most one overlay at a time. To remove an entry
  22. /// from its overlay, call the [remove] function on the overlay entry.
  23. ///
  24. /// Because an [Overlay] uses a [Stack] layout, overlay entries can use
  25. /// [Positioned] and [AnimatedPositioned] to position themselves within the
  26. /// overlay.
  27. ///
  28. /// For example, [Draggable] uses an [OverlayEntry] to show the drag avatar that
  29. /// follows the user's finger across the screen after the drag begins. Using the
  30. /// overlay to display the drag avatar lets the avatar float over the other
  31. /// widgets in the app. As the user's finger moves, draggable calls
  32. /// [markNeedsBuild] on the overlay entry to cause it to rebuild. It its build,
  33. /// the entry includes a [Positioned] with its top and left property set to
  34. /// position the drag avatar near the user's finger. When the drag is over,
  35. /// [Draggable] removes the entry from the overlay to remove the drag avatar
  36. /// from view.
  37. ///
  38. /// By default, if there is an entirely [opaque] entry over this one, then this
  39. /// one will not be included in the widget tree (in particular, stateful widgets
  40. /// within the overlay entry will not be instantiated). To ensure that your
  41. /// overlay entry is still built even if it is not visible, set [maintainState]
  42. /// to true. This is more expensive, so should be done with care. In particular,
  43. /// if widgets in an overlay entry with [maintainState] set to true repeatedly
  44. /// call [State.setState], the user's battery will be drained unnecessarily.
  45. ///
  46. /// See also:
  47. ///
  48. /// * [Overlay]
  49. /// * [OverlayState]
  50. /// * [WidgetsApp]
  51. /// * [MaterialApp]
  52. class PassthroughOverlayEntry {
  53. /// Creates an overlay entry.
  54. ///
  55. /// To insert the entry into an [Overlay], first find the overlay using
  56. /// [Overlay.of] and then call [OverlayState.insert]. To remove the entry,
  57. /// call [remove] on the overlay entry itself.
  58. PassthroughOverlayEntry({
  59. required this.builder,
  60. bool opaque = false,
  61. bool maintainState = false,
  62. }) : _opaque = opaque,
  63. _maintainState = maintainState;
  64. /// This entry will include the widget built by this builder in the overlay at
  65. /// the entry's position.
  66. ///
  67. /// To cause this builder to be called again, call [markNeedsBuild] on this
  68. /// overlay entry.
  69. final WidgetBuilder builder;
  70. /// Whether this entry occludes the entire overlay.
  71. ///
  72. /// If an entry claims to be opaque, then, for efficiency, the overlay will
  73. /// skip building entries below that entry unless they have [maintainState]
  74. /// set.
  75. bool get opaque => _opaque;
  76. bool _opaque;
  77. set opaque(bool value) {
  78. if (_opaque == value) return;
  79. _opaque = value;
  80. assert(_overlay != null);
  81. _overlay!._didChangeEntryOpacity();
  82. }
  83. /// Whether this entry must be included in the tree even if there is a fully
  84. /// [opaque] entry above it.
  85. ///
  86. /// By default, if there is an entirely [opaque] entry over this one, then this
  87. /// one will not be included in the widget tree (in particular, stateful widgets
  88. /// within the overlay entry will not be instantiated). To ensure that your
  89. /// overlay entry is still built even if it is not visible, set [maintainState]
  90. /// to true. This is more expensive, so should be done with care. In particular,
  91. /// if widgets in an overlay entry with [maintainState] set to true repeatedly
  92. /// call [State.setState], the user's battery will be drained unnecessarily.
  93. ///
  94. /// This is used by the [Navigator] and [Route] objects to ensure that routes
  95. /// are kept around even when in the background, so that [Future]s promised
  96. /// from subsequent routes will be handled properly when they complete.
  97. bool get maintainState => _maintainState;
  98. bool _maintainState;
  99. set maintainState(bool value) {
  100. if (_maintainState == value) return;
  101. _maintainState = value;
  102. assert(_overlay != null);
  103. _overlay!._didChangeEntryOpacity();
  104. }
  105. PassthroughOverlayState? _overlay;
  106. final GlobalKey<_OverlayEntryState> _key = GlobalKey<_OverlayEntryState>();
  107. /// Remove this entry from the overlay.
  108. ///
  109. /// This should only be called once.
  110. ///
  111. /// If this method is called while the [SchedulerBinding.schedulerPhase] is
  112. /// [SchedulerPhase.persistentCallbacks], i.e. during the build, layout, or
  113. /// paint phases (see [WidgetsBinding.drawFrame]), then the removal is
  114. /// delayed until the post-frame callbacks phase. Otherwise the removal is
  115. /// done synchronously. This means that it is safe to call during builds, but
  116. /// also that if you do call this during a build, the UI will not update until
  117. /// the next frame (i.e. many milliseconds later).
  118. void remove() {
  119. assert(_overlay != null);
  120. final PassthroughOverlayState overlay = _overlay!;
  121. _overlay = null;
  122. if (SchedulerBinding.instance.schedulerPhase ==
  123. SchedulerPhase.persistentCallbacks) {
  124. SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
  125. overlay._remove(this);
  126. });
  127. } else {
  128. overlay._remove(this);
  129. }
  130. }
  131. /// Cause this entry to rebuild during the next pipeline flush.
  132. ///
  133. /// You need to call this function if the output of [builder] has changed.
  134. void markNeedsBuild() {
  135. _key.currentState?._markNeedsBuild();
  136. }
  137. @override
  138. String toString() =>
  139. '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)';
  140. }
  141. class _OverlayEntry extends StatefulWidget {
  142. _OverlayEntry(this.entry) : super(key: entry._key);
  143. final PassthroughOverlayEntry entry;
  144. @override
  145. _OverlayEntryState createState() => _OverlayEntryState();
  146. }
  147. class _OverlayEntryState extends State<_OverlayEntry> {
  148. @override
  149. Widget build(BuildContext context) {
  150. return widget.entry.builder(context);
  151. }
  152. void _markNeedsBuild() {
  153. setState(() {
  154. /* the state that changed is in the builder */
  155. });
  156. }
  157. }
  158. /// A [Stack] of entries that can be managed independently.
  159. ///
  160. /// Overlays let independent child widgets "float" visual elements on top of
  161. /// other widgets by inserting them into the overlay's [Stack]. The overlay lets
  162. /// each of these widgets manage their participation in the overlay using
  163. /// [OverlayEntry] objects.
  164. ///
  165. /// Although you can create an [Overlay] directly, it's most common to use the
  166. /// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The
  167. /// navigator uses its overlay to manage the visual appearance of its routes.
  168. ///
  169. /// See also:
  170. ///
  171. /// * [OverlayEntry].
  172. /// * [OverlayState].
  173. /// * [WidgetsApp].
  174. /// * [MaterialApp].
  175. class PassthroughOverlay extends StatefulWidget {
  176. /// Creates an overlay.
  177. ///
  178. /// The initial entries will be inserted into the overlay when its associated
  179. /// [OverlayState] is initialized.
  180. ///
  181. /// Rather than creating an overlay, consider using the overlay that is
  182. /// created by the [WidgetsApp] or the [MaterialApp] for the application.
  183. const PassthroughOverlay({
  184. this.initialEntries = const <PassthroughOverlayEntry>[],
  185. Key? key,
  186. }) : super(key: key);
  187. /// The entries to include in the overlay initially.
  188. ///
  189. /// These entries are only used when the [OverlayState] is initialized. If you
  190. /// are providing a new [Overlay] description for an overlay that's already in
  191. /// the tree, then the new entries are ignored.
  192. ///
  193. /// To add entries to an [Overlay] that is already in the tree, use
  194. /// [Overlay.of] to obtain the [OverlayState] (or assign a [GlobalKey] to the
  195. /// [Overlay] widget and obtain the [OverlayState] via
  196. /// [GlobalKey.currentState]), and then use [OverlayState.insert] or
  197. /// [OverlayState.insertAll].
  198. ///
  199. /// To remove an entry from an [Overlay], use [OverlayEntry.remove].
  200. final List<PassthroughOverlayEntry> initialEntries;
  201. /// The state from the closest instance of this class that encloses the given context.
  202. ///
  203. /// In checked mode, if the [debugRequiredFor] argument is provided then this
  204. /// function will assert that an overlay was found and will throw an exception
  205. /// if not. The exception attempts to explain that the calling [Widget] (the
  206. /// one given by the [debugRequiredFor] argument) needs an [Overlay] to be
  207. /// present to function.
  208. ///
  209. /// Typical usage is as follows:
  210. ///
  211. /// ```dart
  212. /// OverlayState overlay = Overlay.of(context);
  213. /// ```
  214. static PassthroughOverlayState of(BuildContext context,
  215. {Widget? debugRequiredFor}) {
  216. final PassthroughOverlayState? result =
  217. context.findAncestorStateOfType<PassthroughOverlayState>();
  218. assert(() {
  219. if (debugRequiredFor != null && result == null) {
  220. final String additional = context.widget != debugRequiredFor
  221. ? '\nThe context from which that widget was searching for an overlay was:\n $context'
  222. : '';
  223. throw FlutterError('No Overlay widget found.\n'
  224. '${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.\n'
  225. 'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.\n'
  226. 'The specific widget that failed to find an overlay was:\n'
  227. ' $debugRequiredFor'
  228. '$additional');
  229. }
  230. return true;
  231. }());
  232. return result!;
  233. }
  234. @override
  235. PassthroughOverlayState createState() => PassthroughOverlayState();
  236. }
  237. /// The current state of an [Overlay].
  238. ///
  239. /// Used to insert [OverlayEntry]s into the overlay using the [insert] and
  240. /// [insertAll] functions.
  241. class PassthroughOverlayState extends State<PassthroughOverlay>
  242. with TickerProviderStateMixin {
  243. final List<PassthroughOverlayEntry> _entries = <PassthroughOverlayEntry>[];
  244. @override
  245. void initState() {
  246. super.initState();
  247. insertAll(widget.initialEntries);
  248. }
  249. /// Insert the given entry into the overlay.
  250. ///
  251. /// If [above] is non-null, the entry is inserted just above [above].
  252. /// Otherwise, the entry is inserted on top.
  253. void insert(PassthroughOverlayEntry entry, {PassthroughOverlayEntry? above}) {
  254. assert(entry._overlay == null);
  255. assert(
  256. above == null || (above._overlay == this && _entries.contains(above)));
  257. entry._overlay = this;
  258. setState(() {
  259. final int index =
  260. above == null ? _entries.length : _entries.indexOf(above) + 1;
  261. _entries.insert(index, entry);
  262. });
  263. }
  264. /// Insert all the entries in the given iterable.
  265. ///
  266. /// If [above] is non-null, the entries are inserted just above [above].
  267. /// Otherwise, the entries are inserted on top.
  268. void insertAll(Iterable<PassthroughOverlayEntry> entries,
  269. {PassthroughOverlayEntry? above}) {
  270. assert(
  271. above == null || (above._overlay == this && _entries.contains(above)));
  272. if (entries.isEmpty) return;
  273. for (PassthroughOverlayEntry entry in entries) {
  274. assert(entry._overlay == null);
  275. entry._overlay = this;
  276. }
  277. setState(() {
  278. final int index =
  279. above == null ? _entries.length : _entries.indexOf(above) + 1;
  280. _entries.insertAll(index, entries);
  281. });
  282. }
  283. void _remove(PassthroughOverlayEntry entry) {
  284. if (mounted) {
  285. _entries.remove(entry);
  286. setState(() {
  287. /* entry was removed */
  288. });
  289. }
  290. }
  291. /// (DEBUG ONLY) Check whether a given entry is visible (i.e., not behind an
  292. /// opaque entry).
  293. ///
  294. /// This is an O(N) algorithm, and should not be necessary except for debug
  295. /// asserts. To avoid people depending on it, this function is implemented
  296. /// only in checked mode.
  297. bool debugIsVisible(PassthroughOverlayEntry entry) {
  298. bool result = false;
  299. assert(_entries.contains(entry));
  300. assert(() {
  301. for (int i = _entries.length - 1; i > 0; i -= 1) {
  302. final PassthroughOverlayEntry candidate = _entries[i];
  303. if (candidate == entry) {
  304. result = true;
  305. break;
  306. }
  307. if (candidate.opaque) break;
  308. }
  309. return true;
  310. }());
  311. return result;
  312. }
  313. void _didChangeEntryOpacity() {
  314. setState(() {
  315. // We use the opacity of the entry in our build function, which means we
  316. // our state has changed.
  317. });
  318. }
  319. @override
  320. Widget build(BuildContext context) {
  321. // These lists are filled backwards. For the offstage children that
  322. // does not matter since they aren't rendered, but for the onstage
  323. // children we reverse the list below before adding it to the tree.
  324. final List<Widget> onstageChildren = <Widget>[];
  325. final List<Widget> offstageChildren = <Widget>[];
  326. bool onstage = true;
  327. for (int i = _entries.length - 1; i >= 0; i -= 1) {
  328. final PassthroughOverlayEntry entry = _entries[i];
  329. if (onstage) {
  330. onstageChildren.add(_OverlayEntry(entry));
  331. if (entry.opaque) onstage = false;
  332. } else if (entry.maintainState) {
  333. offstageChildren
  334. .add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
  335. }
  336. }
  337. return _Theatre(
  338. onstage: Stack(
  339. fit: StackFit.passthrough,
  340. //HanSheng changed it to passthrough so that this widget doesn't change layout constraints
  341. children: onstageChildren.reversed.toList(growable: false),
  342. ),
  343. offstage: offstageChildren,
  344. );
  345. }
  346. @override
  347. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  348. super.debugFillProperties(properties);
  349. // TODO(jacobr): use IterableProperty instead as that would
  350. // provide a slightly more consistent string summary of the List.
  351. properties.add(DiagnosticsProperty<List<PassthroughOverlayEntry>>(
  352. 'entries', _entries));
  353. }
  354. }
  355. /// A widget that has one [onstage] child which is visible, and one or more
  356. /// [offstage] widgets which are kept alive, and are built, but are not laid out
  357. /// or painted.
  358. ///
  359. /// The onstage widget must be a Stack.
  360. ///
  361. /// For convenience, it is legal to use [Positioned] widgets around the offstage
  362. /// widgets.
  363. class _Theatre extends RenderObjectWidget {
  364. _Theatre({
  365. required this.offstage,
  366. this.onstage,
  367. });
  368. final Stack? onstage;
  369. final List<Widget> offstage;
  370. @override
  371. _TheatreElement createElement() => _TheatreElement(this);
  372. @override
  373. _RenderTheatre createRenderObject(BuildContext context) => _RenderTheatre();
  374. }
  375. class _TheatreElement extends RenderObjectElement {
  376. _TheatreElement(_Theatre widget)
  377. : assert(!debugChildrenHaveDuplicateKeys(widget, widget.offstage)),
  378. super(widget);
  379. @override
  380. _Theatre get widget => super.widget as _Theatre;
  381. @override
  382. _RenderTheatre get renderObject => super.renderObject as _RenderTheatre;
  383. Element? _onstage;
  384. static final Object _onstageSlot = Object();
  385. late List<Element> _offstage;
  386. final Set<Element> _forgottenOffstageChildren = HashSet<Element>();
  387. @override
  388. void insertRenderObjectChild(RenderBox child, dynamic slot) {
  389. assert(renderObject.debugValidateChild(child));
  390. if (slot == _onstageSlot) {
  391. assert(child is RenderStack);
  392. renderObject.child = child as RenderStack?;
  393. } else {
  394. assert(slot == null || slot is Element);
  395. renderObject.insert(child, after: slot?.renderObject);
  396. }
  397. }
  398. @override
  399. void moveRenderObjectChild(RenderBox child, dynamic oldSlot, dynamic slot) {
  400. if (slot == _onstageSlot) {
  401. renderObject.remove(child);
  402. assert(child is RenderStack);
  403. renderObject.child = child as RenderStack?;
  404. } else {
  405. assert(slot == null || slot is Element);
  406. if (renderObject.child == child) {
  407. renderObject.child = null;
  408. renderObject.insert(child, after: slot?.renderObject);
  409. } else {
  410. renderObject.move(child, after: slot?.renderObject);
  411. }
  412. }
  413. }
  414. @override
  415. void removeRenderObjectChild(RenderBox child, dynamic slot) {
  416. if (renderObject.child == child) {
  417. renderObject.child = null;
  418. } else {
  419. renderObject.remove(child);
  420. }
  421. }
  422. @override
  423. void visitChildren(ElementVisitor visitor) {
  424. if (_onstage != null) visitor(_onstage!);
  425. for (Element child in _offstage) {
  426. if (!_forgottenOffstageChildren.contains(child)) visitor(child);
  427. }
  428. }
  429. @override
  430. void debugVisitOnstageChildren(ElementVisitor visitor) {
  431. if (_onstage != null) visitor(_onstage!);
  432. }
  433. @override
  434. void forgetChild(Element child) {
  435. if (child == _onstage) {
  436. _onstage = null;
  437. } else {
  438. assert(_offstage.contains(child));
  439. assert(!_forgottenOffstageChildren.contains(child));
  440. _forgottenOffstageChildren.add(child);
  441. }
  442. super.forgetChild(child);
  443. }
  444. @override
  445. void mount(Element? parent, dynamic newSlot) {
  446. super.mount(parent, newSlot);
  447. _onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
  448. _offstage = [];
  449. Element? previousChild;
  450. for (int i = 0; i < _offstage.length; i += 1) {
  451. final Element newChild = inflateWidget(widget.offstage[i], previousChild);
  452. _offstage[i] = newChild;
  453. previousChild = newChild;
  454. }
  455. }
  456. @override
  457. void update(_Theatre newWidget) {
  458. super.update(newWidget);
  459. assert(widget == newWidget);
  460. _onstage = updateChild(_onstage, widget.onstage, _onstageSlot);
  461. _offstage = updateChildren(_offstage, widget.offstage,
  462. forgottenChildren: _forgottenOffstageChildren);
  463. _forgottenOffstageChildren.clear();
  464. }
  465. }
  466. // A render object which lays out and paints one subtree while keeping a list
  467. // of other subtrees alive but not laid out or painted (the "zombie" children).
  468. //
  469. // The subtree that is laid out and painted must be a [RenderStack].
  470. //
  471. // This class uses [StackParentData] objects for its parent data so that the
  472. // children of its primary subtree's stack can be moved to this object's list
  473. // of zombie children without changing their parent data objects.
  474. class _RenderTheatre extends RenderBox
  475. with
  476. RenderObjectWithChildMixin<RenderStack>,
  477. RenderProxyBoxMixin<RenderStack>,
  478. ContainerRenderObjectMixin<RenderBox, StackParentData> {
  479. @override
  480. void setupParentData(RenderObject child) {
  481. if (child.parentData is! StackParentData)
  482. child.parentData = StackParentData();
  483. }
  484. // Because both RenderObjectWithChildMixin and ContainerRenderObjectMixin
  485. // define redepthChildren, visitChildren and debugDescribeChildren and don't
  486. // call super, we have to define them again here to make sure the work of both
  487. // is done.
  488. //
  489. // We chose to put ContainerRenderObjectMixin last in the inheritance chain so
  490. // that we can call super to hit its more complex definitions of
  491. // redepthChildren and visitChildren, and then duplicate the more trivial
  492. // definition from RenderObjectWithChildMixin inline in our version here.
  493. //
  494. // This code duplication is suboptimal.
  495. // TODO(ianh): Replace this with a better solution once https://github.com/dart-lang/sdk/issues/27100 is fixed
  496. //
  497. // For debugDescribeChildren we just roll our own because otherwise the line
  498. // drawings won't really work as well.
  499. @override
  500. void redepthChildren() {
  501. if (child != null) redepthChild(child!);
  502. super.redepthChildren();
  503. }
  504. @override
  505. void visitChildren(RenderObjectVisitor visitor) {
  506. if (child != null) visitor(child!);
  507. super.visitChildren(visitor);
  508. }
  509. @override
  510. List<DiagnosticsNode> debugDescribeChildren() {
  511. final List<DiagnosticsNode> children = <DiagnosticsNode>[];
  512. if (child != null) children.add(child!.toDiagnosticsNode(name: 'onstage'));
  513. if (firstChild != null) {
  514. RenderBox child = firstChild!;
  515. int count = 1;
  516. while (true) {
  517. children.add(
  518. child.toDiagnosticsNode(
  519. name: 'offstage $count',
  520. style: DiagnosticsTreeStyle.offstage,
  521. ),
  522. );
  523. if (child == lastChild) break;
  524. final StackParentData childParentData =
  525. child.parentData! as StackParentData;
  526. child = childParentData.nextSibling!;
  527. count += 1;
  528. }
  529. } else {
  530. children.add(
  531. DiagnosticsNode.message(
  532. 'no offstage children',
  533. style: DiagnosticsTreeStyle.offstage,
  534. ),
  535. );
  536. }
  537. return children;
  538. }
  539. @override
  540. void visitChildrenForSemantics(RenderObjectVisitor visitor) {
  541. if (child != null) visitor(child!);
  542. }
  543. }