如今在移动设备领域,消费者随身携带的显示器呈现出了多样化的设计,不再限制于简单的矩形块。面对尺寸不一、形状各异的设备,开发者们如何确保提供给用户的内容和交互不受影响呢?
由 iPhone X 说起
Apple 推出了以 iPhone X 为代表的异形屏方案后,提供了相应的视觉设计指南。其中提到了 Safe Area 的定义,我们用一张图片来演示 Safe Area 在 iPhone 上的应用:
其实 Safe Area 不是一个新鲜的概念,它最早源自于电视生产领域,用于描述在电视屏幕上看到的区域。
简单地说,安全区域 ≈ 主要视觉区域 + 实际交互区域。如 iPhone X 的 Home 指示条的设计,虽然在视觉上是隐藏的,但由于它会响应系统的滑动手势,不能把控制功能放在此处。
结论
这给我们的启示是:除了极少数的场景,我们应该把渲染出来的内容放在一个名为 SafeArea 的组件内。
类似在 Flutter 中,
Widget build(BuildContext context) {
return SafeArea(child: Container());
}
就确保内容 Container()
放置在了 Safe Area 内。
构建 <SafeArea />
构建 <SafeArea />
组件的原理其实很简单:通过操作系统提供的接口获取显示边距。
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final MediaQueryData data = MediaQuery.of(context);
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);
return Padding(
padding: EdgeInsets.only(
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),
),
child: MediaQuery.removePadding(
context: context,
removeLeft: left,
removeTop: top,
removeRight: right,
removeBottom: bottom,
child: child,
),
);
}
一个例子:小程序
当然,我们并不总是在操作系统层面上去实现应用。典型的如小程序运行环境,我们无法直接拿到 ViewInsets 的数据。
其实在 iPhone X 推出前夕,WebKit 就发布了网站的适配指南。
简单的两步:
一、设置视口的布局方式(小程序不需要设置)
<meta name="viewport" content="... viewport-fit=cover">
二、设置主体元素的内边距
.SafeArea {
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
其中 safe-area-inset-*
属性是由系统代理的环境变量,也可以通过加入自定义的 var,
.SafeArea {
padding-top: max(--minimum-top, env(safe-area-inset-top));
padding-right: max(--minimum-right, env(safe-area-inset-right));
padding-bottom: max(--minimum-bottom, env(safe-area-inset-bottom));
padding-left: max(--minimum-left, env(safe-area-inset-left));
}
瞧,是不是跟 Flutter 官方的 SafeArea 组件形式一致了。