CupertinoPageScaffold和CupertinoN

2019-09-26  本文已影响0人  沉江小鱼
CupertinoPageScaffold

iOS风格的页面基本布局结构,继承自StatefullWidget,包括导航栏和内容栏,介绍如下:

 const CupertinoPageScaffold({
    Key key,
    this.navigationBar, ios风格的顶部导航栏
    this.backgroundColor = CupertinoColors.white,//背景色
    this.resizeToAvoidBottomInset = true,
    @required this.child,//内容栏
 })

下面是_CupertinoPageScaffoldState 的实现:

class _CupertinoPageScaffoldState extends State<CupertinoPageScaffold> {
  final ScrollController _primaryScrollController = ScrollController();

  void _handleStatusBarTap() {
    // Only act on the scroll controller if it has any attached scroll positions.
    if (_primaryScrollController.hasClients) {
      _primaryScrollController.animateTo(
        0.0,
        // Eyeballed from iOS.
        duration: const Duration(milliseconds: 500),
        curve: Curves.linearToEaseOut,
      );
    }
  }

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

    // 获取到child组件
    Widget paddedContent = widget.child;

    final MediaQueryData existingMediaQuery = MediaQuery.of(context);

    // 如果navigationBar 不等于 null
    if (widget.navigationBar != null) {
      // TODO(xster): Use real size after partial layout instead of preferred size.
      // 这里有一行注释,在局部布局后使用实际大小,而不是首选大小。
      // navigationBar.preferredSize.height 是CupertinoNavigationBar的默认大小
      // existingMediaQuery.padding.top 是导航栏到顶部的高度,比如 非齐刘海 的iphone 这里是20pt 齐刘海是 44pt
      final double topPadding =
          widget.navigationBar.preferredSize.height + existingMediaQuery.padding.top;

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

      final EdgeInsets newViewInsets = widget.resizeToAvoidBottomInset
          // The insets are consumed by the scaffolds and no longer exposed to
          // the descendant subtree.
          ? existingMediaQuery.viewInsets.copyWith(bottom: 0.0)
          : existingMediaQuery.viewInsets;

      // 先获取 navigationBar.fullObstruction ,如果navigationBar.fullObstruction 为空,则取CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF
      final bool fullObstruction =
        widget.navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;

      // If navigation bar is opaquely obstructing, directly shift the main content
      // down. If translucent, let main content draw behind navigation bar but hint the
      // obstructed area.
      // 如果导航栏不透明了,将主要内容向下移动,也就是主要内容从导航栏下方开始布局。
      // 如果是半透明的,让主要内容绘制在导航栏后面,但要提示阻塞区域,就是主要内容跟导航栏在同一高度开始。
      if (fullObstruction) {
        paddedContent = MediaQuery(
          data: existingMediaQuery
          // If the navigation bar is opaque, the top media query padding is fully consumed by the navigation bar.
          // 如果导航栏不透明,则导航栏将完全使用顶部的media query padding填充。
          .removePadding(removeTop: true)
          .copyWith(
            viewInsets: newViewInsets,
          ),
          child: Padding(
            padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
            child: paddedContent,
          ),
        );
      } else {
        paddedContent = MediaQuery(
          data: existingMediaQuery.copyWith(
            padding: existingMediaQuery.padding.copyWith(
              top: topPadding,
            ),
            viewInsets: newViewInsets,
          ),
          child: Padding(
            padding: EdgeInsets.only(bottom: bottomPadding),
            child: paddedContent,
          ),
        );
      }
    }

    // The main content being at the bottom is added to the stack first.
    // 位于底部的主要内容首先添加到堆栈中。
    stacked.add(PrimaryScrollController(
      controller: _primaryScrollController,
      child: paddedContent,
    ));

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

    // Add a touch handler the size of the status bar on top of all contents
    // to handle scroll to top by status bar taps.
    // 在所有内容的顶部添加一个状态栏大小的触摸处理程序,以便通过状态栏点击操作滚动到顶部。
    // 就是iOS下,点击导航栏,视图滚动到顶部
    stacked.add(Positioned(
      top: 0.0,
      left: 0.0,
      right: 0.0,
      height: existingMediaQuery.padding.top,
      child: GestureDetector(
          excludeFromSemantics: true,
          onTap: _handleStatusBarTap,
        ),
      ),
    );

    //  最终的小控件
    return DecoratedBox(
      decoration: BoxDecoration(
        color: widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
      ),
      child: Stack(
        children: stacked,
      ),
    );
  }
}

从源码中我们也能看出,主要是布局,尤其是顶部导航栏的布局:

final bool fullObstruction =
        widget.navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;

这一行代码,关乎child的布局是从哪开始的:

关于当前设备信息我们需要另外了解一下 MediaQuery。

CupertinoNavigationBar

iOS风格的导航栏

class CupertinoNavigationBar extends StatefulWidget implements ObstructingPreferredSizeWidget {
  const CupertinoNavigationBar({
    Key key,
    this.leading,
    this.automaticallyImplyLeading = true,
    this.automaticallyImplyMiddle = true,
    this.previousPageTitle,
    this.middle,
    this.trailing,
    this.border = _kDefaultNavBarBorder,
    this.backgroundColor,
    this.padding,
    this.actionsForegroundColor,
    this.transitionBetweenRoutes = true,
    this.heroTag = _defaultHeroTag,

  @override
  bool get fullObstruction => backgroundColor == null ? null : backgroundColor.alpha == 0xFF;

  @override
  Size get preferredSize {
    return const Size.fromHeight(_kNavBarPersistentHeight);
  }
  })

首先我们看到CupertinoNavigationBar继承StatefulWidget,并且实现ObstructingPreferredSizeWidget,那么ObstructingPreferredSizeWidget的定义如下:

// 具有默认大小的小部件,并报告它是否完全遮挡其背后的小部件。
abstract class ObstructingPreferredSizeWidget extends PreferredSizeWidget {
  /// 如果为真,此小部件将以指定的大小完全遮挡其后面的小部件。
  /// 如果为false,此小部件将部分阻塞。
  bool get fullObstruction;
}

我们在看_CupertinoPageScaffoldState代码的时候,用到了fullObstruction这个属性,这个属性可以控制CupertinoPageScaffold的child布局从哪里开始,也就是导航栏是否可以遮挡内容。

ObstructingPreferredSizeWidget 继承于PreferredSizeWidget,我们可以再看下PreferredSizeWidget:

abstract class PreferredSizeWidget implements Widget {
  /// 如果不受约束,则此小部件默认的大小。

  /// 在许多情况下,只需要定义一个默认高度。例如[Scaffold]只取决于它的应用程序栏的默认高度。在这种情况下,该方法的实现可以返回' new Size.fromHeight(myAppBarHeight) ';
  Size get preferredSize;
}

PreferredSizeWidget是一个抽象类,就是定义个首选的默认高度,比如CupertinoNavigationBar的preferredSize 返回的就是Size.fromHeight(myAppBarHeight) 。

我们继续看CupertinoNavigationBar的属性:

上一篇 下一篇

猜你喜欢

热点阅读