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 'basic.dart';
8import 'framework.dart';
9import 'sliver.dart';
10import 'ticker_provider.dart';
11
12/// Whether to show or hide a child.
13///
14/// By default, the [visible] property controls whether the [child] is included
15/// in the subtree or not; when it is not [visible], the [replacement] child
16/// (typically a zero-sized box) is included instead.
17///
18/// A variety of flags can be used to tweak exactly how the child is hidden.
19/// (Changing the flags dynamically is discouraged, as it can cause the [child]
20/// subtree to be rebuilt, with any state in the subtree being discarded.
21/// Typically, only the [visible] flag is changed dynamically.)
22///
23/// These widgets provide some of the facets of this one:
24///
25/// * [Opacity], which can stop its child from being painted.
26/// * [Offstage], which can stop its child from being laid out or painted.
27/// * [TickerMode], which can stop its child from being animated.
28/// * [ExcludeSemantics], which can hide the child from accessibility tools.
29/// * [IgnorePointer], which can disable touch interactions with the child.
30///
31/// Using this widget is not necessary to hide children. The simplest way to
32/// hide a child is just to not include it, or, if a child _must_ be given (e.g.
33/// because the parent is a [StatelessWidget]) then to use [SizedBox.shrink]
34/// instead of the child that would otherwise be included.
35///
36/// See also:
37///
38/// * [AnimatedSwitcher], which can fade from one child to the next as the
39/// subtree changes.
40/// * [AnimatedCrossFade], which can fade between two specific children.
41/// * [SliverVisibility], the sliver equivalent of this widget.
42class Visibility extends StatelessWidget {
43 /// Control whether the given [child] is [visible].
44 ///
45 /// The [maintainSemantics] and [maintainInteractivity] arguments can only be
46 /// set if [maintainSize] is set.
47 ///
48 /// The [maintainSize] argument can only be set if [maintainAnimation] is set.
49 ///
50 /// The [maintainAnimation] argument can only be set if [maintainState] is
51 /// set.
52 const Visibility({
53 super.key,
54 required this.child,
55 this.replacement = const SizedBox.shrink(),
56 this.visible = true,
57 this.maintainState = false,
58 this.maintainAnimation = false,
59 this.maintainSize = false,
60 this.maintainSemantics = false,
61 this.maintainInteractivity = false,
62 }) : assert(
63 maintainState || !maintainAnimation,
64 'Cannot maintain animations if the state is not also maintained.',
65 ),
66 assert(
67 maintainAnimation || !maintainSize,
68 'Cannot maintain size if animations are not maintained.',
69 ),
70 assert(
71 maintainSize || !maintainSemantics,
72 'Cannot maintain semantics if size is not maintained.',
73 ),
74 assert(
75 maintainSize || !maintainInteractivity,
76 'Cannot maintain interactivity if size is not maintained.',
77 );
78
79 /// Control whether the given [child] is [visible].
80 ///
81 /// This is equivalent to the default [Visibility] constructor with all
82 /// "maintain" fields set to true. This constructor should be used in place of
83 /// an [Opacity] widget that only takes on values of `0.0` or `1.0`, as it
84 /// avoids extra compositing when fully opaque.
85 const Visibility.maintain({
86 super.key,
87 required this.child,
88 this.visible = true,
89 }) : maintainState = true,
90 maintainAnimation = true,
91 maintainSize = true,
92 maintainSemantics = true,
93 maintainInteractivity = true,
94 replacement = const SizedBox.shrink(); // Unused since maintainState is always true.
95
96 /// The widget to show or hide, as controlled by [visible].
97 ///
98 /// {@macro flutter.widgets.ProxyWidget.child}
99 final Widget child;
100
101 /// The widget to use when the child is not [visible], assuming that none of
102 /// the `maintain` flags (in particular, [maintainState]) are set.
103 ///
104 /// The normal behavior is to replace the widget with a zero by zero box
105 /// ([SizedBox.shrink]).
106 ///
107 /// See also:
108 ///
109 /// * [AnimatedCrossFade], which can animate between two children.
110 final Widget replacement;
111
112 /// Switches between showing the [child] or hiding it.
113 ///
114 /// The `maintain` flags should be set to the same values regardless of the
115 /// state of the [visible] property, otherwise they will not operate correctly
116 /// (specifically, the state will be lost regardless of the state of
117 /// [maintainState] whenever any of the `maintain` flags are changed, since
118 /// doing so will result in a subtree shape change).
119 ///
120 /// Unless [maintainState] is set, the [child] subtree will be disposed
121 /// (removed from the tree) while hidden.
122 final bool visible;
123
124 /// Whether to maintain the [State] objects of the [child] subtree when it is
125 /// not [visible].
126 ///
127 /// Keeping the state of the subtree is potentially expensive (because it
128 /// means all the objects are still in memory; their resources are not
129 /// released). It should only be maintained if it cannot be recreated on
130 /// demand. One example of when the state would be maintained is if the child
131 /// subtree contains a [Navigator], since that widget maintains elaborate
132 /// state that cannot be recreated on the fly.
133 ///
134 /// If this property is true, an [Offstage] widget is used to hide the child
135 /// instead of replacing it with [replacement].
136 ///
137 /// If this property is false, then [maintainAnimation] must also be false.
138 ///
139 /// Dynamically changing this value may cause the current state of the
140 /// subtree to be lost (and a new instance of the subtree, with new [State]
141 /// objects, to be immediately created if [visible] is true).
142 final bool maintainState;
143
144 /// Whether to maintain animations within the [child] subtree when it is
145 /// not [visible].
146 ///
147 /// To set this, [maintainState] must also be set.
148 ///
149 /// Keeping animations active when the widget is not visible is even more
150 /// expensive than only maintaining the state.
151 ///
152 /// One example when this might be useful is if the subtree is animating its
153 /// layout in time with an [AnimationController], and the result of that
154 /// layout is being used to influence some other logic. If this flag is false,
155 /// then any [AnimationController]s hosted inside the [child] subtree will be
156 /// muted while the [visible] flag is false.
157 ///
158 /// If this property is true, no [TickerMode] widget is used.
159 ///
160 /// If this property is false, then [maintainSize] must also be false.
161 ///
162 /// Dynamically changing this value may cause the current state of the
163 /// subtree to be lost (and a new instance of the subtree, with new [State]
164 /// objects, to be immediately created if [visible] is true).
165 final bool maintainAnimation;
166
167 /// Whether to maintain space for where the widget would have been.
168 ///
169 /// To set this, [maintainAnimation] and [maintainState] must also be set.
170 ///
171 /// Maintaining the size when the widget is not [visible] is not notably more
172 /// expensive than just keeping animations running without maintaining the
173 /// size, and may in some circumstances be slightly cheaper if the subtree is
174 /// simple and the [visible] property is frequently toggled, since it avoids
175 /// triggering a layout change when the [visible] property is toggled. If the
176 /// [child] subtree is not trivial then it is significantly cheaper to not
177 /// even keep the state (see [maintainState]).
178 ///
179 /// If this property is false, [Offstage] is used.
180 ///
181 /// If this property is false, then [maintainSemantics] and
182 /// [maintainInteractivity] must also be false.
183 ///
184 /// Dynamically changing this value may cause the current state of the
185 /// subtree to be lost (and a new instance of the subtree, with new [State]
186 /// objects, to be immediately created if [visible] is true).
187 ///
188 /// See also:
189 ///
190 /// * [AnimatedOpacity] and [FadeTransition], which apply animations to the
191 /// opacity for a more subtle effect.
192 final bool maintainSize;
193
194 /// Whether to maintain the semantics for the widget when it is hidden (e.g.
195 /// for accessibility).
196 ///
197 /// To set this, [maintainSize] must also be set.
198 ///
199 /// By default, with [maintainSemantics] set to false, the [child] is not
200 /// visible to accessibility tools when it is hidden from the user. If this
201 /// flag is set to true, then accessibility tools will report the widget as if
202 /// it was present.
203 final bool maintainSemantics;
204
205 /// Whether to allow the widget to be interactive when hidden.
206 ///
207 /// To set this, [maintainSize] must also be set.
208 ///
209 /// By default, with [maintainInteractivity] set to false, touch events cannot
210 /// reach the [child] when it is hidden from the user. If this flag is set to
211 /// true, then touch events will nonetheless be passed through.
212 final bool maintainInteractivity;
213
214 /// Tells the visibility state of an element in the tree based off its
215 /// ancestor [Visibility] elements.
216 ///
217 /// If there's one or more [Visibility] widgets in the ancestor tree, this
218 /// will return true if and only if all of those widgets have [visible] set
219 /// to true. If there is no [Visibility] widget in the ancestor tree of the
220 /// specified build context, this will return true.
221 ///
222 /// This will register a dependency from the specified context on any
223 /// [Visibility] elements in the ancestor tree, such that if any of their
224 /// visibilities changes, the specified context will be rebuilt.
225 static bool of(BuildContext context) {
226 bool isVisible = true;
227 BuildContext ancestorContext = context;
228 InheritedElement? ancestor = ancestorContext.getElementForInheritedWidgetOfExactType<_VisibilityScope>();
229 while (isVisible && ancestor != null) {
230 final _VisibilityScope scope = context.dependOnInheritedElement(ancestor) as _VisibilityScope;
231 isVisible = scope.isVisible;
232 ancestor.visitAncestorElements((Element parent) {
233 ancestorContext = parent;
234 return false;
235 });
236 ancestor = ancestorContext.getElementForInheritedWidgetOfExactType<_VisibilityScope>();
237 }
238 return isVisible;
239 }
240
241 @override
242 Widget build(BuildContext context) {
243 Widget result = child;
244 if (maintainSize) {
245 result = _Visibility(
246 visible: visible,
247 maintainSemantics: maintainSemantics,
248 child: IgnorePointer(
249 ignoring: !visible && !maintainInteractivity,
250 child: result,
251 ),
252 );
253 } else {
254 assert(!maintainInteractivity);
255 assert(!maintainSemantics);
256 assert(!maintainSize);
257 if (maintainState) {
258 if (!maintainAnimation) {
259 result = TickerMode(enabled: visible, child: result);
260 }
261 result = Offstage(
262 offstage: !visible,
263 child: result,
264 );
265 } else {
266 assert(!maintainAnimation);
267 assert(!maintainState);
268 result = visible ? child : replacement;
269 }
270 }
271 return _VisibilityScope(isVisible: visible, child: result);
272 }
273
274 @override
275 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
276 super.debugFillProperties(properties);
277 properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden', ifTrue: 'visible'));
278 properties.add(FlagProperty('maintainState', value: maintainState, ifFalse: 'maintainState'));
279 properties.add(FlagProperty('maintainAnimation', value: maintainAnimation, ifFalse: 'maintainAnimation'));
280 properties.add(FlagProperty('maintainSize', value: maintainSize, ifFalse: 'maintainSize'));
281 properties.add(FlagProperty('maintainSemantics', value: maintainSemantics, ifFalse: 'maintainSemantics'));
282 properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity'));
283 }
284}
285
286/// Inherited widget that allows descendants to find their visibility status.
287class _VisibilityScope extends InheritedWidget {
288 const _VisibilityScope({required this.isVisible, required super.child});
289
290 final bool isVisible;
291
292 @override
293 bool updateShouldNotify(_VisibilityScope old) {
294 return isVisible != old.isVisible;
295 }
296}
297
298/// Whether to show or hide a sliver child.
299///
300/// By default, the [visible] property controls whether the [sliver] is included
301/// in the subtree or not; when it is not [visible], the [replacementSliver] is
302/// included instead.
303///
304/// A variety of flags can be used to tweak exactly how the sliver is hidden.
305/// (Changing the flags dynamically is discouraged, as it can cause the [sliver]
306/// subtree to be rebuilt, with any state in the subtree being discarded.
307/// Typically, only the [visible] flag is changed dynamically.)
308///
309/// These widgets provide some of the facets of this one:
310///
311/// * [SliverOpacity], which can stop its sliver child from being painted.
312/// * [SliverOffstage], which can stop its sliver child from being laid out or
313/// painted.
314/// * [TickerMode], which can stop its child from being animated.
315/// * [ExcludeSemantics], which can hide the child from accessibility tools.
316/// * [SliverIgnorePointer], which can disable touch interactions with the
317/// sliver child.
318///
319/// Using this widget is not necessary to hide children. The simplest way to
320/// hide a child is just to not include it. If a child _must_ be given (e.g.
321/// because the parent is a [StatelessWidget]), then including a childless
322/// [SliverToBoxAdapter] instead of the child that would otherwise be included
323/// is typically more efficient than using [SliverVisibility].
324///
325/// See also:
326///
327/// * [Visibility], the equivalent widget for boxes.
328class SliverVisibility extends StatelessWidget {
329 /// Control whether the given [sliver] is [visible].
330 ///
331 /// The [maintainSemantics] and [maintainInteractivity] arguments can only be
332 /// set if [maintainSize] is set.
333 ///
334 /// The [maintainSize] argument can only be set if [maintainAnimation] is set.
335 ///
336 /// The [maintainAnimation] argument can only be set if [maintainState] is
337 /// set.
338 const SliverVisibility({
339 super.key,
340 required this.sliver,
341 this.replacementSliver = const SliverToBoxAdapter(),
342 this.visible = true,
343 this.maintainState = false,
344 this.maintainAnimation = false,
345 this.maintainSize = false,
346 this.maintainSemantics = false,
347 this.maintainInteractivity = false,
348 }) : assert(
349 maintainState || !maintainAnimation,
350 'Cannot maintain animations if the state is not also maintained.',
351 ),
352 assert(
353 maintainAnimation || !maintainSize,
354 'Cannot maintain size if animations are not maintained.',
355 ),
356 assert(
357 maintainSize || !maintainSemantics,
358 'Cannot maintain semantics if size is not maintained.',
359 ),
360 assert(
361 maintainSize || !maintainInteractivity,
362 'Cannot maintain interactivity if size is not maintained.',
363 );
364
365 /// Control whether the given [sliver] is [visible].
366 ///
367 /// This is equivalent to the default [SliverVisibility] constructor with all
368 /// "maintain" fields set to true. This constructor should be used in place of
369 /// a [SliverOpacity] widget that only takes on values of `0.0` or `1.0`, as it
370 /// avoids extra compositing when fully opaque.
371 const SliverVisibility.maintain({
372 super.key,
373 required this.sliver,
374 this.replacementSliver = const SliverToBoxAdapter(),
375 this.visible = true,
376 }) : maintainState = true,
377 maintainAnimation = true,
378 maintainSize = true,
379 maintainSemantics = true,
380 maintainInteractivity = true;
381
382 /// The sliver to show or hide, as controlled by [visible].
383 final Widget sliver;
384
385 /// The widget to use when the sliver child is not [visible], assuming that
386 /// none of the `maintain` flags (in particular, [maintainState]) are set.
387 ///
388 /// The normal behavior is to replace the widget with a childless
389 /// [SliverToBoxAdapter], which by default has a geometry of
390 /// [SliverGeometry.zero].
391 final Widget replacementSliver;
392
393 /// Switches between showing the [sliver] or hiding it.
394 ///
395 /// The `maintain` flags should be set to the same values regardless of the
396 /// state of the [visible] property, otherwise they will not operate correctly
397 /// (specifically, the state will be lost regardless of the state of
398 /// [maintainState] whenever any of the `maintain` flags are changed, since
399 /// doing so will result in a subtree shape change).
400 ///
401 /// Unless [maintainState] is set, the [sliver] subtree will be disposed
402 /// (removed from the tree) while hidden.
403 final bool visible;
404
405 /// Whether to maintain the [State] objects of the [sliver] subtree when it is
406 /// not [visible].
407 ///
408 /// Keeping the state of the subtree is potentially expensive (because it
409 /// means all the objects are still in memory; their resources are not
410 /// released). It should only be maintained if it cannot be recreated on
411 /// demand. One example of when the state would be maintained is if the sliver
412 /// subtree contains a [Navigator], since that widget maintains elaborate
413 /// state that cannot be recreated on the fly.
414 ///
415 /// If this property is true, a [SliverOffstage] widget is used to hide the
416 /// sliver instead of replacing it with [replacementSliver].
417 ///
418 /// If this property is false, then [maintainAnimation] must also be false.
419 ///
420 /// Dynamically changing this value may cause the current state of the
421 /// subtree to be lost (and a new instance of the subtree, with new [State]
422 /// objects, to be immediately created if [visible] is true).
423 final bool maintainState;
424
425 /// Whether to maintain animations within the [sliver] subtree when it is
426 /// not [visible].
427 ///
428 /// To set this, [maintainState] must also be set.
429 ///
430 /// Keeping animations active when the widget is not visible is even more
431 /// expensive than only maintaining the state.
432 ///
433 /// One example when this might be useful is if the subtree is animating its
434 /// layout in time with an [AnimationController], and the result of that
435 /// layout is being used to influence some other logic. If this flag is false,
436 /// then any [AnimationController]s hosted inside the [sliver] subtree will be
437 /// muted while the [visible] flag is false.
438 ///
439 /// If this property is true, no [TickerMode] widget is used.
440 ///
441 /// If this property is false, then [maintainSize] must also be false.
442 ///
443 /// Dynamically changing this value may cause the current state of the
444 /// subtree to be lost (and a new instance of the subtree, with new [State]
445 /// objects, to be immediately created if [visible] is true).
446 final bool maintainAnimation;
447
448 /// Whether to maintain space for where the sliver would have been.
449 ///
450 /// To set this, [maintainAnimation] must also be set.
451 ///
452 /// Maintaining the size when the sliver is not [visible] is not notably more
453 /// expensive than just keeping animations running without maintaining the
454 /// size, and may in some circumstances be slightly cheaper if the subtree is
455 /// simple and the [visible] property is frequently toggled, since it avoids
456 /// triggering a layout change when the [visible] property is toggled. If the
457 /// [sliver] subtree is not trivial then it is significantly cheaper to not
458 /// even keep the state (see [maintainState]).
459 ///
460 /// If this property is false, [SliverOffstage] is used.
461 ///
462 /// If this property is false, then [maintainSemantics] and
463 /// [maintainInteractivity] must also be false.
464 ///
465 /// Dynamically changing this value may cause the current state of the
466 /// subtree to be lost (and a new instance of the subtree, with new [State]
467 /// objects, to be immediately created if [visible] is true).
468 final bool maintainSize;
469
470 /// Whether to maintain the semantics for the sliver when it is hidden (e.g.
471 /// for accessibility).
472 ///
473 /// To set this, [maintainSize] must also be set.
474 ///
475 /// By default, with [maintainSemantics] set to false, the [sliver] is not
476 /// visible to accessibility tools when it is hidden from the user. If this
477 /// flag is set to true, then accessibility tools will report the widget as if
478 /// it was present.
479 final bool maintainSemantics;
480
481 /// Whether to allow the sliver to be interactive when hidden.
482 ///
483 /// To set this, [maintainSize] must also be set.
484 ///
485 /// By default, with [maintainInteractivity] set to false, touch events cannot
486 /// reach the [sliver] when it is hidden from the user. If this flag is set to
487 /// true, then touch events will nonetheless be passed through.
488 final bool maintainInteractivity;
489
490 @override
491 Widget build(BuildContext context) {
492 if (maintainSize) {
493 Widget result = sliver;
494 result = SliverIgnorePointer(
495 ignoring: !visible && !maintainInteractivity,
496 sliver: result,
497 );
498 return _SliverVisibility(
499 visible: visible,
500 maintainSemantics: maintainSemantics,
501 sliver: result,
502 );
503 }
504 assert(!maintainInteractivity);
505 assert(!maintainSemantics);
506 assert(!maintainSize);
507 if (maintainState) {
508 Widget result = sliver;
509 if (!maintainAnimation) {
510 result = TickerMode(enabled: visible, child: sliver);
511 }
512 return SliverOffstage(
513 sliver: result,
514 offstage: !visible,
515 );
516 }
517 assert(!maintainAnimation);
518 assert(!maintainState);
519 return visible ? sliver : replacementSliver;
520 }
521
522 @override
523 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
524 super.debugFillProperties(properties);
525 properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden', ifTrue: 'visible'));
526 properties.add(FlagProperty('maintainState', value: maintainState, ifFalse: 'maintainState'));
527 properties.add(FlagProperty('maintainAnimation', value: maintainAnimation, ifFalse: 'maintainAnimation'));
528 properties.add(FlagProperty('maintainSize', value: maintainSize, ifFalse: 'maintainSize'));
529 properties.add(FlagProperty('maintainSemantics', value: maintainSemantics, ifFalse: 'maintainSemantics'));
530 properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity'));
531 }
532}
533
534// A widget that conditionally hides its child, but without the forced compositing of `Opacity`.
535//
536// A fully opaque `Opacity` widget is required to leave its opacity layer in the layer tree. This
537// forces all parent render objects to also composite, which can break a simple scene into many
538// different layers. This can be significantly more expensive, so the issue is avoided by a
539// specialized render object that does not ever force compositing.
540class _Visibility extends SingleChildRenderObjectWidget {
541 const _Visibility({ required this.visible, required this.maintainSemantics, super.child });
542
543 final bool visible;
544 final bool maintainSemantics;
545
546 @override
547 _RenderVisibility createRenderObject(BuildContext context) {
548 return _RenderVisibility(visible, maintainSemantics);
549 }
550
551 @override
552 void updateRenderObject(BuildContext context, _RenderVisibility renderObject) {
553 renderObject
554 ..visible = visible
555 ..maintainSemantics = maintainSemantics;
556 }
557}
558
559class _RenderVisibility extends RenderProxyBox {
560 _RenderVisibility(this._visible, this._maintainSemantics);
561
562 bool get visible => _visible;
563 bool _visible;
564 set visible(bool value) {
565 if (value == visible) {
566 return;
567 }
568 _visible = value;
569 markNeedsPaint();
570 }
571
572 bool get maintainSemantics => _maintainSemantics;
573 bool _maintainSemantics;
574 set maintainSemantics(bool value) {
575 if (value == maintainSemantics) {
576 return;
577 }
578 _maintainSemantics = value;
579 markNeedsSemanticsUpdate();
580 }
581
582 @override
583 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
584 if (maintainSemantics || visible) {
585 super.visitChildrenForSemantics(visitor);
586 }
587 }
588
589 @override
590 void paint(PaintingContext context, Offset offset) {
591 if (!visible) {
592 return;
593 }
594 super.paint(context, offset);
595 }
596}
597
598// A widget that conditionally hides its child, but without the forced compositing of `SliverOpacity`.
599//
600// A fully opaque `SliverOpacity` widget is required to leave its opacity layer in the layer tree.
601// This forces all parent render objects to also composite, which can break a simple scene into many
602// different layers. This can be significantly more expensive, so the issue is avoided by a
603// specialized render object that does not ever force compositing.
604class _SliverVisibility extends SingleChildRenderObjectWidget {
605 const _SliverVisibility({ required this.visible, required this.maintainSemantics, Widget? sliver })
606 : super(child: sliver);
607
608 final bool visible;
609 final bool maintainSemantics;
610
611 @override
612 RenderObject createRenderObject(BuildContext context) {
613 return _RenderSliverVisibility(visible, maintainSemantics);
614 }
615
616 @override
617 void updateRenderObject(BuildContext context, _RenderSliverVisibility renderObject) {
618 renderObject
619 ..visible = visible
620 ..maintainSemantics = maintainSemantics;
621 }
622}
623
624class _RenderSliverVisibility extends RenderProxySliver {
625 _RenderSliverVisibility(this._visible, this._maintainSemantics);
626
627 bool get visible => _visible;
628 bool _visible;
629 set visible(bool value) {
630 if (value == visible) {
631 return;
632 }
633 _visible = value;
634 markNeedsPaint();
635 }
636
637 bool get maintainSemantics => _maintainSemantics;
638 bool _maintainSemantics;
639 set maintainSemantics(bool value) {
640 if (value == maintainSemantics) {
641 return;
642 }
643 _maintainSemantics = value;
644 markNeedsSemanticsUpdate();
645 }
646
647 @override
648 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
649 if (maintainSemantics || visible) {
650 super.visitChildrenForSemantics(visitor);
651 }
652 }
653
654 @override
655 void paint(PaintingContext context, Offset offset) {
656 if (!visible) {
657 return;
658 }
659 super.paint(context, offset);
660 }
661}
662