safe_area.dart 7.68 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

Ian Hickson's avatar
Ian Hickson committed
7 8 9 10 11 12 13
import 'package:flutter/foundation.dart';

import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'media_query.dart';

14 15
/// A widget that insets its child by sufficient padding to avoid intrusions by
/// the operating system.
Ian Hickson's avatar
Ian Hickson committed
16 17 18 19 20 21 22
///
/// For example, this will indent the child by enough to avoid the status bar at
/// the top of the screen.
///
/// It will also indent the child by the amount necessary to avoid The Notch on
/// the iPhone X, or other similar creative physical features of the display.
///
23 24 25
/// When a [minimum] padding is specified, the greater of the minimum padding
/// or the safe area padding will be applied.
///
26 27
/// {@youtube 560 315 https://www.youtube.com/watch?v=lkF0TQJO0bA}
///
Ian Hickson's avatar
Ian Hickson committed
28 29
/// See also:
///
30 31
///  * [SliverSafeArea], for insetting slivers to avoid operating system
///    intrusions.
Ian Hickson's avatar
Ian Hickson committed
32 33
///  * [Padding], for insetting widgets in general.
///  * [MediaQuery], from which the window padding is obtained.
34
///  * [dart:ui.FlutterView.padding], which reports the padding from the operating
Ian Hickson's avatar
Ian Hickson committed
35 36 37 38
///    system.
class SafeArea extends StatelessWidget {
  /// Creates a widget that avoids operating system interfaces.
  ///
39 40
  /// The [left], [top], [right], [bottom], and [minimum] arguments must not be
  /// null.
Ian Hickson's avatar
Ian Hickson committed
41
  const SafeArea({
42
    super.key,
43 44 45 46 47
    this.left = true,
    this.top = true,
    this.right = true,
    this.bottom = true,
    this.minimum = EdgeInsets.zero,
48
    this.maintainBottomViewPadding = false,
49
    required this.child,
Ian Hickson's avatar
Ian Hickson committed
50 51 52
  }) : assert(left != null),
       assert(top != null),
       assert(right != null),
53
       assert(bottom != null);
Ian Hickson's avatar
Ian Hickson committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67

  /// Whether to avoid system intrusions on the left.
  final bool left;

  /// Whether to avoid system intrusions at the top of the screen, typically the
  /// system status bar.
  final bool top;

  /// Whether to avoid system intrusions on the right.
  final bool right;

  /// Whether to avoid system intrusions on the bottom side of the screen.
  final bool bottom;

68 69 70 71 72
  /// This minimum padding to apply.
  ///
  /// The greater of the minimum insets and the media padding will be applied.
  final EdgeInsets minimum;

73 74 75
  /// Specifies whether the [SafeArea] should maintain the bottom
  /// [MediaQueryData.viewPadding] instead of the bottom [MediaQueryData.padding],
  /// defaults to false.
76 77 78 79 80 81 82 83 84
  ///
  /// For example, if there is an onscreen keyboard displayed above the
  /// SafeArea, the padding can be maintained below the obstruction rather than
  /// being consumed. This can be helpful in cases where your layout contains
  /// flexible widgets, which could visibly move when opening a software
  /// keyboard due to the change in the padding value. Setting this to true will
  /// avoid the UI shift.
  final bool maintainBottomViewPadding;

Ian Hickson's avatar
Ian Hickson committed
85 86 87 88
  /// The widget below this widget in the tree.
  ///
  /// The padding on the [MediaQuery] for the [child] will be suitably adjusted
  /// to zero out any sides that were avoided by this widget.
89
  ///
90
  /// {@macro flutter.widgets.ProxyWidget.child}
Ian Hickson's avatar
Ian Hickson committed
91 92 93 94 95
  final Widget child;

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMediaQuery(context));
96
    EdgeInsets padding = MediaQuery.paddingOf(context);
97
    // Bottom padding has been consumed - i.e. by the keyboard
98
    if (maintainBottomViewPadding) {
99
      padding = padding.copyWith(bottom: MediaQuery.viewPaddingOf(context).bottom);
100
    }
101

102 103
    return Padding(
      padding: EdgeInsets.only(
104 105 106 107
        left: math.max(left ? padding.left : 0.0, minimum.left),
        top: math.max(top ? padding.top : 0.0, minimum.top),
        right: math.max(right ? padding.right : 0.0, minimum.right),
        bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
Ian Hickson's avatar
Ian Hickson committed
108
      ),
109
      child: MediaQuery.removePadding(
Ian Hickson's avatar
Ian Hickson committed
110 111 112 113 114 115 116 117 118 119 120
        context: context,
        removeLeft: left,
        removeTop: top,
        removeRight: right,
        removeBottom: bottom,
        child: child,
      ),
    );
  }

  @override
121 122
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
123
    properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
124 125 126
    properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
    properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
    properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
Ian Hickson's avatar
Ian Hickson committed
127 128
  }
}
129 130 131 132 133 134 135 136 137 138 139

/// A sliver that insets another sliver by sufficient padding to avoid
/// intrusions by the operating system.
///
/// For example, this will indent the sliver by enough to avoid the status bar
/// at the top of the screen.
///
/// It will also indent the sliver by the amount necessary to avoid The Notch
/// on the iPhone X, or other similar creative physical features of the
/// display.
///
140 141 142
/// When a [minimum] padding is specified, the greater of the minimum padding
/// or the safe area padding will be applied.
///
143 144 145 146 147
/// See also:
///
///  * [SafeArea], for insetting widgets to avoid operating system intrusions.
///  * [SliverPadding], for insetting slivers in general.
///  * [MediaQuery], from which the window padding is obtained.
148
///  * [dart:ui.FlutterView.padding], which reports the padding from the operating
149 150 151 152
///    system.
class SliverSafeArea extends StatelessWidget {
  /// Creates a sliver that avoids operating system interfaces.
  ///
153
  /// The [left], [top], [right], [bottom], and [minimum] arguments must not be null.
154
  const SliverSafeArea({
155
    super.key,
156 157 158 159 160
    this.left = true,
    this.top = true,
    this.right = true,
    this.bottom = true,
    this.minimum = EdgeInsets.zero,
161
    required this.sliver,
162 163 164
  }) : assert(left != null),
       assert(top != null),
       assert(right != null),
165
       assert(bottom != null);
166 167 168 169 170 171 172 173 174 175 176 177 178 179

  /// Whether to avoid system intrusions on the left.
  final bool left;

  /// Whether to avoid system intrusions at the top of the screen, typically the
  /// system status bar.
  final bool top;

  /// Whether to avoid system intrusions on the right.
  final bool right;

  /// Whether to avoid system intrusions on the bottom side of the screen.
  final bool bottom;

180 181 182 183 184
  /// This minimum padding to apply.
  ///
  /// The greater of the minimum padding and the media padding is be applied.
  final EdgeInsets minimum;

185 186 187 188 189 190 191 192 193
  /// The sliver below this sliver in the tree.
  ///
  /// The padding on the [MediaQuery] for the [sliver] will be suitably adjusted
  /// to zero out any sides that were avoided by this sliver.
  final Widget sliver;

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMediaQuery(context));
194
    final EdgeInsets padding = MediaQuery.paddingOf(context);
195 196
    return SliverPadding(
      padding: EdgeInsets.only(
197 198 199 200
        left: math.max(left ? padding.left : 0.0, minimum.left),
        top: math.max(top ? padding.top : 0.0, minimum.top),
        right: math.max(right ? padding.right : 0.0, minimum.right),
        bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
201
      ),
202
      sliver: MediaQuery.removePadding(
203 204 205 206 207 208 209 210 211 212 213
        context: context,
        removeLeft: left,
        removeTop: top,
        removeRight: right,
        removeBottom: bottom,
        child: sliver,
      ),
    );
  }

  @override
214 215
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
216
    properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
217 218 219
    properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
    properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
    properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
220 221
  }
}