page_scaffold.dart 3.45 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// 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/foundation.dart';
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.
14 15 16
///
/// See also:
///
17 18 19
///  * [CupertinoTabScaffold], a similar widget for tabbed applications.
///  * [CupertinoPageRoute], a modal page route that typically hosts a
///    [CupertinoPageScaffold] with support for iOS-style page transitions.
20
class CupertinoPageScaffold extends StatelessWidget {
21
  /// Creates a layout for pages with a navigation bar at the top.
22 23 24 25 26 27 28 29 30 31 32 33
  const CupertinoPageScaffold({
    Key key,
    this.navigationBar,
    @required this.child,
  }) : assert(child != null),
       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.
34 35
  ///
  /// The scaffold assumes the nav bar will consume the [MediaQuery] top padding.
36
  // TODO(xster): document its page transition animation when ready
37
  final ObstructingPreferredSizeWidget navigationBar;
38 39 40

  /// Widget to show in the main content area.
  ///
41 42
  /// Content can slide under the [navigationBar] when they're translucent with
  /// a [MediaQuery] padding hinting the top obstructed area.
43 44 45 46 47
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final List<Widget> stacked = <Widget>[];
48

49
    Widget paddedContent = child;
50
    if (navigationBar != null) {
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
      final MediaQueryData existingMediaQuery = MediaQuery.of(context);

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

      // If nav bar is opaquely obstructing, directly shift the main content
      // down. If translucent, let main content draw behind nav bar but hint the
      // obstructed area.
      if (navigationBar.fullObstruction) {
        paddedContent = new Padding(
          padding: new EdgeInsets.only(top: topPadding),
          child: child,
        );
      } else {
        paddedContent = new MediaQuery(
          data: existingMediaQuery.copyWith(
            padding: existingMediaQuery.padding.copyWith(
              top: topPadding,
            ),
          ),
73 74 75
          child: child,
        );
      }
76 77 78
    }

    // The main content being at the bottom is added to the stack first.
79
    stacked.add(paddedContent);
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

    if (navigationBar != null) {
      stacked.add(new Positioned(
        top: 0.0,
        left: 0.0,
        right: 0.0,
        child: navigationBar,
      ));
    }

    return new DecoratedBox(
      decoration: const BoxDecoration(color: CupertinoColors.white),
      child: new Stack(
        children: stacked,
      ),
    );
  }
97 98 99 100 101 102 103 104 105 106 107
}

/// Widget that has a preferred size and reports whether it fully obstructs
/// widgets behind it.
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;
}