用Flutter开发app会遇到的问题之一就是适配不同平台,如android、ios、web、macOS和windows和这些设备不同的屏幕分辨率。这就需要响应式布局,同一套代码适应不同设备不同分辨率的法宝。

响应式布局非常重要的一点就是可以做到不同设备统一的用户体验。这样用户可以平滑的从一个设备过度到另外的一个设备上。

本文会把Flutter的各种不同的响应式布局的实现方法都呈现出来,以帮助各位需要这方面知识的开发者。不过在正式开始之前,我们先来看一看android和iOS如何处理不同分辨率下的布局的。

Android和iOS下,Flutter如何处理布局

Android

Android处理响应式布局的方式非常多,其中最被推荐的是ConstraintLayout。这个使用约束(constraints)来决定UI元素在屏幕的位置和大小。这里需要注意:伸展和重置大小UI元素并不总是利用屏幕空间的最好办法。这样做反而可能导致用户体验欠佳。

要保证足够好的用户体验,就是给不同的设备和不同的设备方向提供各自合适的布局。比如,给手机和平板提供不同的布局,不同屏幕分辨率提供不同的布局,等。

向量图形,因为可以显示不同的大小儿不会损失显示质量也是一个很好的选项。还有一个技术就是把UI分成不同的部分,这些不同的部分可以根据不同的分辨率组合成复杂的布局。这一点不容易实现,因为每个布局,还需要代码维护它们的不同的生命周期和数据。

android的布局

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填满一个RowColumn或者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' />

下面还有其他的一些很有用的工具:

  • CustomSingleChildLayoutCustomMultiChildLayout,可以用来为子widget定制位置和大小。
  • FittedBox,可以在保持宽高比的情况下,根据可用的空间大小实现缩放。
  • FractionallySizedBox,根据父widget的空间,按比例显示子widget。
  • OrientationBuilder,根据设备方向新建不同的布局。

Flutter的布局系统完全支持你使用一套代码无缝适配不同的设备,不同的分辨率。你无需为定制不同设备分辨率的不同布局儿担心,使用Flutter可以很容一的构建起响应式布局。


小红星闪啊闪
914 声望1.9k 粉丝

时不我待