用Flutter开发app会遇到的问题之一就是适配不同平台,如android、ios、web、macOS和windows和这些设备不同的屏幕分辨率。这就需要响应式布局,同一套代码适应不同设备不同分辨率的法宝。
响应式布局非常重要的一点就是可以做到不同设备统一的用户体验。这样用户可以平滑的从一个设备过度到另外的一个设备上。
本文会把Flutter的各种不同的响应式布局的实现方法都呈现出来,以帮助各位需要这方面知识的开发者。不过在正式开始之前,我们先来看一看android和iOS如何处理不同分辨率下的布局的。
Android和iOS下,Flutter如何处理布局
Android
Android处理响应式布局的方式非常多,其中最被推荐的是ConstraintLayout
。这个使用约束(constraints)来决定UI元素在屏幕的位置和大小。这里需要注意:伸展和重置大小UI元素并不总是利用屏幕空间的最好办法。这样做反而可能导致用户体验欠佳。
要保证足够好的用户体验,就是给不同的设备和不同的设备方向提供各自合适的布局。比如,给手机和平板提供不同的布局,不同屏幕分辨率提供不同的布局,等。
向量图形,因为可以显示不同的大小儿不会损失显示质量也是一个很好的选项。还有一个技术就是把UI分成不同的部分,这些不同的部分可以根据不同的分辨率组合成复杂的布局。这一点不容易实现,因为每个布局,还需要代码维护它们的不同的生命周期和数据。
iOS
iOS实现响应式布局就不可能少了Auto Layout,这个还有必不可少的Size Class。有这两者就可以使用约束来处理不同的屏幕分辨率和设备的方向。
两一个工具就是UISplitViewController
,它可以在一个screen下管理多个View。它可以控制什么View显示,并把这些View用一种特定结构显示。
这两者相比于Flutter,你需要注意的
Flutter和原生的Android、iOS相比使用了一种不同的开发方式。Flutter使用了可重用的Widget来构建界面。这些Widget可以定制、可以组合在一起组成复杂的UI。这让开发者可以使用一套代码开发出夸平台的响应式布局。我们来研究一下Flutter都提供了那些技术来开发响应式布局的。
1. 灵活的布局Widget
使用Expanded
Widget可以让一个子widget填满一个Row
,Column
或者Flex
的剩余空间。如果要控制剩余空间占用多少,可以适合用flex
属性。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Responsive Layout"),
),
body: Row(
children: [
Expanded(
child: Container(
color: Colors.yellowAccent,
)),
Expanded(
child: Container(
color: Colors.orangeAccent,
)),
Expanded(
child: Container(
color: Colors.pinkAccent,
)),
],
),
);
}
效果是这样的:
<img src='./responsive_horizontal.png' width='600' alt='横向布局' />
如果给Expanded
设定不同的flex
值的话,来看看效果。
代码:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Responsive Layout"),
),
body: Row(
children: [
Expanded(
flex: 1,
child: Container(
color: Colors.yellowAccent,
)),
Expanded(
flex: 2,
child: Container(
color: Colors.orangeAccent,
)),
Expanded(
flex: 4,
child: Container(
color: Colors.pinkAccent,
)),
],
),
);
}
效果:
<img src='./responsive_layout_flex.png' width='600' alt='flex值' />
MediaQuery
MediaQuery
可以获得设备信息,比如屏幕大小、分辨率、方向和像素密度等。这些信息可以用来调整app的布局来适配屏幕大小、分辨率。比如,你可以这么做:
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final height = size.height;
final width = size.width;
return Scaffold(
appBar: AppBar(
title: const Text("Responsive Layout"),
),
body: Container(
color: Colors.pinkAccent,
child: Center(
child: Container(
color: Colors.yellow,
height: height / 2, // half of the screen height size
width: width / 2, // half of the screen width size
),
),
),
}
效果:
<img src='./media_query.png' alt='mediaquery' width='300' />
LayoutBuilder
LayoutBuilder
非常灵活。你可以根据父Widget可用的空间来调整返回的Widget大小。在下面的例子中,LayoutBuilder
用来决定最大的屏幕可用空间。如果值大于480,我们返回一个抽屉式导航和一个Expanded
。如果没有大于480,就直接返回一个列表视图。这样就实现了一个响应式布局。
代码:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Responsive Layout"),
),
body: LayoutBuilder(builder: (context, constraints) {
if (constraints.maxWidth > 480) {
return Row(
children: [
Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: const [
ListTile(title: Text('Menu Item 1')),
ListTile(title: Text('Menu Item 2')),
ListTile(title: Text('Menu Item 3')),
],
),
),
const Expanded(
child: Center(
child: Text('Content Area'),
),
),
],
);
} else {
return ListView(
children: const [
ListTile(title: Text('Menu Item 1')),
ListTile(title: Text('Menu Item 2')),
ListTile(title: Text('Menu Item 3')),
],
);
}
}),
}
<img src='./builder_1.png' width='300' alt="vertical" />
<img src='./builder_2.png' width='600' alt="vertical" />
AspectRatio
The AspectRatio widget allows you to set a fixed aspect ratio for a widget. This can be useful for maintaining a consistent layout across different screen sizes. The example below uses an aspect ratio of 3 /2 to display the Flutter logo.
AspectRatio
可以设置一个widget的宽高比。这可以让一个widget在不同的屏幕大小下显示固定的宽高比。下面的例子设定一个3/2的比例Flutter logo。
代码:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Responsive Layout"),
),
body: AspectRatio(
aspectRatio: 3 / 2,
child: Container(
color: Colors.amber,
child: const Center(
child: FlutterLogo(
size: 100,
),
),
),
),
}
效果:
<img src='./aspect_ratio_phone.png' alt='Phone' width='300' />
<img src='./aspect_ratio_tablet.png' alt='Tablet' width='400' />
下面还有其他的一些很有用的工具:
CustomSingleChildLayout
和CustomMultiChildLayout
,可以用来为子widget定制位置和大小。FittedBox
,可以在保持宽高比的情况下,根据可用的空间大小实现缩放。FractionallySizedBox
,根据父widget的空间,按比例显示子widget。OrientationBuilder
,根据设备方向新建不同的布局。
Flutter的布局系统完全支持你使用一套代码无缝适配不同的设备,不同的分辨率。你无需为定制不同设备分辨率的不同布局儿担心,使用Flutter可以很容一的构建起响应式布局。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。