page_scaffold.dart 4.63 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';

import 'colors.dart';

/// Implements a single iOS application page's layout.
///
/// The scaffold lays out the navigation bar on top and the content between or
/// behind the navigation bar.
13 14 15
///
/// See also:
///
16 17 18
///  * [CupertinoTabScaffold], a similar widget for tabbed applications.
///  * [CupertinoPageRoute], a modal page route that typically hosts a
///    [CupertinoPageScaffold] with support for iOS-style page transitions.
19
class CupertinoPageScaffold extends StatelessWidget {
20
  /// Creates a layout for pages with a navigation bar at the top.
21 22 23
  const CupertinoPageScaffold({
    Key key,
    this.navigationBar,
24
    this.backgroundColor = CupertinoColors.white,
25
    this.resizeToAvoidBottomInset = true,
26 27
    @required this.child,
  }) : assert(child != null),
28
       assert(resizeToAvoidBottomInset != null),
29 30 31 32 33 34 35
       super(key: key);

  /// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the
  /// top of the screen.
  ///
  /// If translucent, the main content may slide behind it.
  /// Otherwise, the main content's top margin will be offset by its height.
36
  ///
37
  /// The scaffold assumes the navigation bar will consume the [MediaQuery] top padding.
38
  // TODO(xster): document its page transition animation when ready
39
  final ObstructingPreferredSizeWidget navigationBar;
40 41 42

  /// Widget to show in the main content area.
  ///
43
  /// Content can slide under the [navigationBar] when they're translucent.
44 45 46
  /// In that case, the child's [BuildContext]'s [MediaQuery] will have a
  /// top padding indicating the area of obstructing overlap from the
  /// [navigationBar].
47 48
  final Widget child;

49 50 51 52 53
  /// The color of the widget that underlies the entire scaffold.
  ///
  /// By default uses [CupertinoColors.white] color.
  final Color backgroundColor;

54 55 56 57 58 59 60 61 62
  /// Whether the [child] should size itself to avoid the window's bottom inset.
  ///
  /// For example, if there is an onscreen keyboard displayed above the
  /// scaffold, the body can be resized to avoid overlapping the keyboard, which
  /// prevents widgets inside the body from being obscured by the keyboard.
  ///
  /// Defaults to true.
  final bool resizeToAvoidBottomInset;

63 64 65
  @override
  Widget build(BuildContext context) {
    final List<Widget> stacked = <Widget>[];
66

67
    Widget paddedContent = child;
68
    if (navigationBar != null) {
69 70 71 72
      final MediaQueryData existingMediaQuery = MediaQuery.of(context);

      // TODO(xster): Use real size after partial layout instead of preferred size.
      // https://github.com/flutter/flutter/issues/12912
73 74 75 76 77 78 79
      final double topPadding =
          navigationBar.preferredSize.height + existingMediaQuery.padding.top;

      // Propagate bottom padding and include viewInsets if appropriate
      final double bottomPadding = resizeToAvoidBottomInset
          ? existingMediaQuery.viewInsets.bottom
          : 0.0;
80

81 82
      // If navigation bar is opaquely obstructing, directly shift the main content
      // down. If translucent, let main content draw behind navigation bar but hint the
83 84
      // obstructed area.
      if (navigationBar.fullObstruction) {
85 86
        paddedContent = Padding(
          padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
87 88 89
          child: child,
        );
      } else {
90
        paddedContent = MediaQuery(
91 92 93 94 95
          data: existingMediaQuery.copyWith(
            padding: existingMediaQuery.padding.copyWith(
              top: topPadding,
            ),
          ),
96 97
          child: Padding(
            padding: EdgeInsets.only(bottom: bottomPadding),
98 99
            child: child,
          ),
100 101
        );
      }
102 103 104
    }

    // The main content being at the bottom is added to the stack first.
105
    stacked.add(paddedContent);
106 107

    if (navigationBar != null) {
108
      stacked.add(Positioned(
109 110 111 112 113 114 115
        top: 0.0,
        left: 0.0,
        right: 0.0,
        child: navigationBar,
      ));
    }

116 117 118
    return DecoratedBox(
      decoration: BoxDecoration(color: backgroundColor),
      child: Stack(
119 120 121 122
        children: stacked,
      ),
    );
  }
123 124 125 126 127 128 129 130 131 132 133 134 135 136
}

/// Widget that has a preferred size and reports whether it fully obstructs
/// widgets behind it.
///
/// Used by [CupertinoPageScaffold] to either shift away fully obstructed content
/// or provide a padding guide to partially obstructed content.
abstract class ObstructingPreferredSizeWidget extends PreferredSizeWidget {
  /// If true, this widget fully obstructs widgets behind it by the specified
  /// size.
  ///
  /// If false, this widget partially obstructs.
  bool get fullObstruction;
}