[译] Flutter 布局备忘录

你须要了解 Flutter 的简单布局模版吗? 如今我将展现给你我总结的一系列 Flutter 布局代码片断。我会尽可能保证代码简短易懂,而且会给出效果图。 可是咱们仍旧须要按部就班 —— 模版目录将会随之逐步深刻。我将会将更多的篇幅集中于 Flutter 部件的应用,而不是单纯陈列组件(Flutter Gallery 在这一点作的很好!) 若是你对于 Flutter 布局还有其余疑问,或者想要分享你的代码,请留言给我!css


目录

  • Row 和 Column
  • IntrinsicWidth 和 IntrinsicHeight
  • Stack
  • Expanded
  • ConstrainedBox
  • Container
    • 装饰(decoration):BoxDecoration
    • 图片(image):DecorationImage
    • 边框(border):Border
    • 边框半径(borderRadius):BorderRadius
    • 形状(shape):BoxShape
    • 阴影(boxShadow):List<BoxShadow>
    • 渐变(gradient):RadialGradient
    • 背景混合模式(backgroundBlendMode):BlendMode
  • SizedBox
  • SafeArea

Row 和 Column

MainAxisAlignment

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

若是你想要不一样字符的基线对齐,你应该使用 CrossAxisAlignment.baseline前端

Row(
  crossAxisAlignment: CrossAxisAlignment.baseline,
  textBaseline: TextBaseline.alphabetic,
  children: <Widget>[
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.display3,
    ),
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.body1,
    ),
  ],
),
复制代码

CrossAxisAlignment

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

MainAxisSize

Row /*或 Column*/( 
  mainAxisSize: MainAxisSize.max,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

Row /*或 Column*/( 
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

IntrinsicWidth 和 IntrinsicHeight

想要某行或列中全部部件和最高/最宽的部件同样高/宽?不要乱找了,答案在这里!android

当你有这种样式的布局:ios

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('IntrinsicWidth')),
    body: Center(
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {},
            child: Text('Short'),
          ),
          RaisedButton(
            onPressed: () {},
            child: Text('A bit Longer'),
          ),
          RaisedButton(
            onPressed: () {},
            child: Text('The Longest text button'),
          ),
        ],
      ),
    ),
  );
}
复制代码

可是你但愿全部的按钮都和最宽的按钮等,只须要使用 IntrinsicWidthgit

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('IntrinsicWidth')),
    body: Center(
      child: IntrinsicWidth(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            RaisedButton(
              onPressed: () {},
              child: Text('Short'),
            ),
            RaisedButton(
              onPressed: () {},
              child: Text('A bit Longer'),
            ),
            RaisedButton(
              onPressed: () {},
              child: Text('The Longest text button'),
            ),
          ],
        ),
      ),
    ),
  );
}
复制代码

若是你须要的是让全部部件和最高的部件,能够结合使用 IntrinsicHeightRow 部件。github


Stack

很是适用于将部件叠加在一块儿算法

@override
Widget build(BuildContext context) {
  Widget main = Scaffold(
    appBar: AppBar(title: Text('Stack')),
  );

  return Stack(
    fit: StackFit.expand,
    children: <Widget>[
      main,
      Banner(
        message: "Top Start",
        location: BannerLocation.topStart,
      ),
      Banner(
        message: "Top End",
        location: BannerLocation.topEnd,
      ),
      Banner(
        message: "Bottom Start",
        location: BannerLocation.bottomStart,
      ),
      Banner(
        message: "Bottom End",
        location: BannerLocation.bottomEnd,
      ),
    ],
  );
}
复制代码

若是想使用本身的部件,须要将它们放置在 Positioned 里面后端

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Stack')),
    body: Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Material(color: Colors.yellowAccent),
        Positioned(
          top: 0,
          left: 0,
          child: Icon(Icons.star, size: 50),
        ),
        Positioned(
          top: 340,
          left: 250,
          child: Icon(Icons.call, size: 50),
        ),
      ],
    ),
  );
}
复制代码

若是你不想去猜想 top 或 bottom 的值,你可使用 LayoutBuilder 来检索它们数组

Widget build(BuildContext context) {
  const iconSize = 50;
  return Scaffold(
    appBar: AppBar(title: Text('Stack with LayoutBuilder')),
    body: LayoutBuilder(
      builder: (context, constraints) =>
        Stack(
          fit: StackFit.expand,
          children: <Widget>[
            Material(color: Colors.yellowAccent),
            Positioned(
              top: 0,
              child: Icon(Icons.star, size: iconSize),
            ),
            Positioned(
              top: constraints.maxHeight - iconSize,
              left: constraints.maxWidth - iconSize,
              child: Icon(Icons.call, size: iconSize),
            ),
          ],
        ),
    ),
  );
}
复制代码

Expanded

Expanded 能够和 Flex\Flexbox 布局一块儿应用,而且很是适用于分配多元素的空间。app

Row(
  children: <Widget>[
    Expanded(
      child: Container(
        decoration: const BoxDecoration(color: Colors.red),
      ),
      flex: 3,
    ),
    Expanded(
      child: Container(
        decoration: const BoxDecoration(color: Colors.green),
      ),
      flex: 2,
    ),
    Expanded(
      child: Container(
        decoration: const BoxDecoration(color: Colors.blue),
      ),
      flex: 1,
    ),
  ],
),
复制代码

ConstrainedBox

默认状况下,大多数组件都会使用尽量小的空间:

Card(child: const Text('Hello World!'), color: Colors.yellow)
复制代码

ConstrainedBox 让部件可使用指望的剩余空间。

ConstrainedBox( 
  constraints: BoxConstraints.expand(),
  child: const Card(
    child: const Text('Hello World!'), 
    color: Colors.yellow,
  ), 
),
复制代码

你可使用 BoxConstraints 指定部件可使用多大的空间 —— 经过指定 height/widthmin/max 属性。

BoxConstraints.expand 将会让组件使用无限制(全部可用)的空间,除非另有指定:

ConstrainedBox(
  constraints: BoxConstraints.expand(height: 300),
  child: const Card(
    child: const Text('Hello World!'), 
    color: Colors.yellow,
  ),
),
复制代码

上面代码和以下代码等效:

ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity,
    maxWidth: double.infinity,
    minHeight: 300,
    maxHeight: 300,
  ),
  child: const Card(
    child: const Text('Hello World!'), 
    color: Colors.yellow,
  ),
),
复制代码

Container

最经常使用的部件之一 —— 而且它之因此这么经常使用是有缘由的:

用于布局工具的 Container

若是你没有指定 Containerheightwidth,它将和 child 的大小相同

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container as a layout')),
    body: Container(
      color: Colors.yellowAccent,
      child: Text("Hi"),
    ),
  );
}
复制代码

若是你想要 Container 扩大到和它的父级元素相等,对 heightwidth 属性使用 double.infinity

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container as a layout')),
    body: Container(
      height: double.infinity,
      width: double.infinity,
      color: Colors.yellowAccent,
      child: Text("Hi"),
    ),
  );
}
复制代码

Container 的装饰

你可使用 color 属性来改变 Container 的背景色,可是 decorationforegroundDecoration 则能够作更多。(使用这两个属性,你能够完全改变 Container 的外观,这部分我将在后续讨论,由于这部份内容不少) decoration 总会放置在 child 后面,而 foregroundDecoration 则在 child 的上面。

decoration

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container.decoration')),
    body: Container(
      height: double.infinity,
      width: double.infinity,
      decoration: BoxDecoration(color: Colors.yellowAccent),
      child: Text("Hi"),
    ),
  );
}
复制代码

decoration and foregroundDecoration

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container.foregroundDecoration')),
    body: Container(
      height: double.infinity,
      width: double.infinity,
      decoration: BoxDecoration(color: Colors.yellowAccent),
      foregroundDecoration: BoxDecoration(color: Colors.red.withOpacity(0.5)),
      child: Text("Hi"),
    ),
  );
}
复制代码

Container 的变换

若是你不想使用 Transform 部件来改变你的布局,你可使用 Containertransform 属性

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container.transform')),
    body: Container(
      height: 300,
      width: 300,
      transform: Matrix4.rotationZ(pi / 4),
      decoration: BoxDecoration(color: Colors.yellowAccent),
      child: Text(
        "Hi",
        textAlign: TextAlign.center,
      ),
    ),
  );
}
复制代码

BoxDecoration

装饰效果一般用于容器组件,来改变组件的外观。

图片(image):DecorationImage

将图片做为背景:

Scaffold(
  appBar: AppBar(title: Text('image: DecorationImage')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        image: DecorationImage(
          fit: BoxFit.fitWidth,
          image: NetworkImage(
            'https://flutter.io/images/catalog-widget-placeholder.png',
          ),
        ),
      ),
    ),
  ),
);
复制代码

边框(border):Border

指定容器的边框样式。

Scaffold(
  appBar: AppBar(title: Text('border: Border')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        border: Border.all(color: Colors.black, width: 3),
      ),
    ),
  ),
);
复制代码

边框半径(borderRadius):BorderRadius

让边框能够是圆角。

若是装饰的 shapeBoxShape.circle,那么 borderRadius 将无效

Scaffold(
  appBar: AppBar(title: Text('borderRadius: BorderRadius')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
          color: Colors.yellow,
          border: Border.all(color: Colors.black, width: 3),
          borderRadius: BorderRadius.all(Radius.circular(18))),
    ),
  ),
);
复制代码

形状(shape):BoxShape

盒子的形状能够是长方形、正方形、椭圆或者圆形。

对于其余任意形状,你应该使用 ShapeDecoration 而不是 BoxDecoration

Scaffold(
  appBar: AppBar(title: Text('shape: BoxShape')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        shape: BoxShape.circle,
      ),
    ),
  ),
);
复制代码

阴影(boxShadow):List<BoxShadow>

能够给容器添加阴影。

这个参数是一个列表,这样你就能够定义多种不一样的阴影,而后将它们组合在一块儿。

Scaffold(
  appBar: AppBar(title: Text('boxShadow: List<BoxShadow>')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        boxShadow: const [
          BoxShadow(blurRadius: 10),
        ],
      ),
    ),
  ),
);
复制代码

渐变(gradient)

有三种类型的渐变:LinearGradientRadialGradientSweepGradient

`LinearGradient`

Scaffold(
  appBar: AppBar(title: Text('gradient: LinearGradient')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: const [
            Colors.red,
            Colors.blue,
          ],
        ),
      ),
    ),
  ),
);
复制代码

RadialGradient

Scaffold(
  appBar: AppBar(title: Text('gradient: RadialGradient')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        gradient: RadialGradient(
          colors: const [Colors.yellow, Colors.blue],
          stops: const [0.4, 1.0],
        ),
      ),
    ),
  ),
);
复制代码

SweepGradient

Scaffold(
  appBar: AppBar(title: Text('gradient: SweepGradient')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        gradient: SweepGradient(
          colors: const [
            Colors.blue,
            Colors.green,
            Colors.yellow,
            Colors.red,
            Colors.blue,
          ],
          stops: const [0.0, 0.25, 0.5, 0.75, 1.0],
        ),
      ),
    ),
  ),
);
复制代码

背景混合模式(backgroundBlendMode)

backgroundBlendModeBoxDecoration 中最复杂的属性。 它能够混合 BoxDecoration 的颜色和渐变,而且不管 BoxDecoration 在何种元素之上。

有了 backgroundBlendMode,你可使用 BlendMode 枚举类型中的一长串算法。

首先,配置 BoxDecorationforegroundDecoration,它被渲染于 Container 子元素的上方(而 decoration 被渲染于子元素的后面)。

Scaffold(
  appBar: AppBar(title: Text('backgroundBlendMode')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      foregroundDecoration: BoxDecoration(
        backgroundBlendMode: BlendMode.exclusion,
        gradient: LinearGradient(
          colors: const [
            Colors.red,
            Colors.blue,
          ],
        ),
      ),
      child: Image.network(
        'https://flutter.io/images/catalog-widget-placeholder.png',
      ),
    ),
  ),
);
复制代码

backgroundBlendMode 不只影响它所在的 Container

backgroundBlendMode 能改变从 Container 的部件树中任意部件的颜色。 下面这段代码中,有一个做为父级元素的 Container,它渲染了一张图片 image 和一个使用了 backgroundBlendMode 的子元素 Container。你仍旧会获得和前一段代码相同的效果。

Scaffold(
  appBar: AppBar(title: Text('backgroundBlendMode')),
  body: Center(
    child: Container(
      decoration: BoxDecoration(
        image: DecorationImage(
          image: NetworkImage(
            'https://flutter.io/images/catalog-widget-placeholder.png',
          ),
        ),
      ),
      child: Container(
        height: 200,
        width: 200,
        foregroundDecoration: BoxDecoration(
          backgroundBlendMode: BlendMode.exclusion,
          gradient: LinearGradient(
            colors: const [
              Colors.red,
              Colors.blue,
            ],
          ),
        ),
      ),
    ),
  ),
);
复制代码

SizedBox

这是最简单可是最有用的部件

用做 ConstrainedBox 的 SizedBox

SizedBox 能够实现和 ConstrainedBox 类似的效果

SizedBox.expand(
  child: Card(
    child: Text('Hello World!'),
    color: Colors.yellowAccent,
  ),
),
复制代码

用做内边距的 SizedBox

若是你须要添加内边距或者外边距,你能够选择 Padding 或者 Container 部件。可是它们都不如添加 Sizedbox 简单易读

Column(
  children: <Widget>[
    Icon(Icons.star, size: 50),
    const SizedBox(height: 100),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
复制代码

用做不可见对象的 SizedBox

不少时候你但愿经过一个布尔值(bool)来控制组件的显示和隐藏

Widget build(BuildContext context) {
  bool isVisible = ...
  return Scaffold(
    appBar: AppBar(
      title: Text('isVisible = $isVisible'),
    ),
    body: isVisible 
      ? Icon(Icons.star, size: 150) 
      : const SizedBox(),
  );
}
复制代码

因为 SizedBox 有一个 const 构造函数,使用 const SizedBox() 就变得很是简单。

更简单的解决方案是使用 Opacity 部件,而后将 opacity 的值改为 0.0。这个方案的缺点是虽然组件不可见,可是它依旧占据空间。


SafeArea

在不一样的平台上,有不少特殊的位置,好比 Android 系统的状态栏,或者 iPhone X 的“齐刘海”,咱们应该避免在这些位置放置元素。

解决方案就是使用 SafeArea 部件(下面的例子分别是使用和没使用 SafeArea 的效果)

Widget build(BuildContext context) {
  return Material(
    color: Colors.blue,
    child: SafeArea(
      child: SizedBox.expand(
        child: Card(color: Colors.yellowAccent),
      ),
    ),
  );
}
复制代码

更多内容敬请期待

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索