safe_area.dart 7.88 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
    Key? 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
  }) : assert(left != null),
       assert(top != null),
       assert(right != null),
       assert(bottom != null),
       super(key: key);

  /// 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;

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

74 75 76 77
  /// Specifies whether the [SafeArea] should maintain the
  /// [MediaQueryData.viewPadding] instead of the [MediaQueryData.padding] when
  /// consumed by the [MediaQueryData.viewInsets] of the current context's
  /// [MediaQuery], defaults to false.
78 79 80 81 82 83 84 85 86
  ///
  /// 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
87 88 89 90
  /// 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.
91
  ///
92
  /// {@macro flutter.widgets.ProxyWidget.child}
Ian Hickson's avatar
Ian Hickson committed
93 94 95 96 97
  final Widget child;

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMediaQuery(context));
98
    final MediaQueryData data = MediaQuery.of(context);
99 100 101 102 103
    EdgeInsets padding = data.padding;
    // Bottom padding has been consumed - i.e. by the keyboard
    if (data.padding.bottom == 0.0 && data.viewInsets.bottom != 0.0 && maintainBottomViewPadding)
      padding = padding.copyWith(bottom: data.viewPadding.bottom);

104 105
    return Padding(
      padding: EdgeInsets.only(
106 107 108 109
        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
110
      ),
111
      child: MediaQuery.removePadding(
Ian Hickson's avatar
Ian Hickson committed
112 113 114 115 116 117 118 119 120 121 122
        context: context,
        removeLeft: left,
        removeTop: top,
        removeRight: right,
        removeBottom: bottom,
        child: child,
      ),
    );
  }

  @override
123 124
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
125
    properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
126 127 128
    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
129 130
  }
}
131 132 133 134 135 136 137 138 139 140 141

/// 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.
///
142 143 144
/// When a [minimum] padding is specified, the greater of the minimum padding
/// or the safe area padding will be applied.
///
145 146 147 148 149
/// 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.
150
///  * [dart:ui.FlutterView.padding], which reports the padding from the operating
151 152 153 154
///    system.
class SliverSafeArea extends StatelessWidget {
  /// Creates a sliver that avoids operating system interfaces.
  ///
155
  /// The [left], [top], [right], [bottom], and [minimum] arguments must not be null.
156
  const SliverSafeArea({
157
    Key? key,
158 159 160 161 162
    this.left = true,
    this.top = true,
    this.right = true,
    this.bottom = true,
    this.minimum = EdgeInsets.zero,
163
    required this.sliver,
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
  }) : assert(left != null),
       assert(top != null),
       assert(right != null),
       assert(bottom != null),
       super(key: key);

  /// 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;

183 184 185 186 187
  /// This minimum padding to apply.
  ///
  /// The greater of the minimum padding and the media padding is be applied.
  final EdgeInsets minimum;

188 189 190 191 192 193 194 195 196
  /// 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));
197
    final EdgeInsets padding = MediaQuery.of(context).padding;
198 199
    return SliverPadding(
      padding: EdgeInsets.only(
200 201 202 203
        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),
204
      ),
205
      sliver: MediaQuery.removePadding(
206 207 208 209 210 211 212 213 214 215 216
        context: context,
        removeLeft: left,
        removeTop: top,
        removeRight: right,
        removeBottom: bottom,
        child: sliver,
      ),
    );
  }

  @override
217 218
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
219
    properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
220 221 222
    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'));
223 224
  }
}