CupertinoPageScaffold
iOS风格的页面基本布局结构,继承自StatefullWidget,包括导航栏和内容栏,介绍如下:
const CupertinoPageScaffold({
Key key,
this.navigationBar, ios风格的顶部导航栏
this.backgroundColor = CupertinoColors.white,//背景色
this.resizeToAvoidBottomInset = true,
@required this.child,//内容栏
})
- navigationBar
iOS的风格的导航栏,通常是是ObstructingPreferredSizeWidget(抽象类)的子类的实例对象。
比如通常都使用CupertinoNavigationBar的实例对象。 - child
内容 - backgroundColor
背景颜色,默认情况下使用[CupertinoTheme]的'scaffoldBackgroundColor'。 - resizeToAvoidBottomInset
[child]是否应该调整自身大小以避免窗口的底部装饰,默认是true
例如,如果在scaffold上方有一个屏幕上显示的键盘,则可以调整body的大小,以避免键盘重叠,从而防止body内的小部件被键盘遮挡。
下面是_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的布局是从哪开始的:
- widget.navigationBar.fullObstruction为true
child布局从设备顶部开始 - widget.navigationBar.fullObstruction为false
child布局从导航栏下面开始 - CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF
navigationBar.fullObstruction 为null时,barBackgroundColor.alpha 透明则从设备顶部开始,否则从导航栏下面,开始。
关于当前设备信息我们需要另外了解一下 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的属性:
- this.leading,
通常为左边的返回按钮,如果为null,并且automaticallyImplyLeading为true,则会自动设置为返回或关闭按钮 - this.automaticallyImplyLeading = true,
如果this.leading为null,并且automaticallyImplyLeading为true,则会自动设置为返回或关闭按钮 - this.automaticallyImplyMiddle = true,
如果middle为null,并且this.automaticallyImplyMiddle为true,则会自动设置为Text组件,显示页面标题 - this.previousPageTitle,
导航栏左侧组件的右边的文本 - this.middle,
中间的组件 - this.trailing,
小部件放置在导航栏的末尾。通常在页面上执行的附加操作,如搜索或编辑功能。 - this.border = _kDefaultNavBarBorder,
导航栏的边框,默认情况下底部有一个像素的边框,如果边框为空,导航栏将不显示边框。 - this.backgroundColor,
背景颜色 - this.padding,
导航栏内容的填充。如果为空,导航栏将采用以下默认值:
在垂直方向上,内容的大小将与导航栏本身减去状态栏的高度相同。
在水平方向上,根据iOS规范,填充将为16像素,除非主要部件是一个自动插入的后退按钮,在这种情况下,填充将为0。
垂直填充不会改变导航栏的高度。 - this.actionsForegroundColor,
用于导航栏中[leading]和[trailing]小部件的文本和图标的默认颜色。当为空时,默认为[CupertinoTheme]的' primaryColor '。根据iOS的标准设计,[middle]中的文本的默认颜色总是黑色。 - this.transitionBetweenRoutes = true
是否在导航栏之间切换。当[transitionBetweenRoutes]为真时,如果被转换为的路由有[CupertinoNavigationBar]或[CupertinoSliverNavigationBar], [transitionBetweenRoutes]设置为真,那么这个导航条将在路由的顶部转换,而不是在内部。
这种转变也会发生在边缘后退滑动手势,就像在iOS上一样,但只有当下一页的“maintainState”设置为真[PageRoute]。
当设置为true时,每个路由只能显示一个导航栏,除非也设置了[heroTag]。
该值默认为true,不能为空。(待确定) - this.heroTag = _defaultHeroTag,
如果[transitionBetweenRoutes]为真,则为导航栏的Hero小部件添加标记。默认为同一[Navigator]的所有[CupertinoNavigationBar]和[CupertinoSliverNavigationBar]实例之间的公共标记。 使用默认标记,同一导航器的所有导航栏可以相互转换,只要每个路由只有一个导航栏。可以重写这个[heroTag]来手动处理每个路由有多个导航条,或者在多个[Navigator]之间进行转换。不能为空。要禁用此导航栏的Hero转换,请将[transitionBetweenRoutes]设置为false。 - fullObstruction
父类的属性,这里实现,根据背景颜色是否透明决定。 - preferredSize
首选高度,这里是44pt。
网友评论