美文网首页
Flutter 各种布局方式汇总

Flutter 各种布局方式汇总

作者: JeffreyWorld | 来源:发表于2023-08-09 18:25 被阅读0次

    本文会列举一组 Flutter 布局代码示例。因为 Flutter 相对于 Android 原生的 layout 布局或者是 Compose 布局,还是不太一样。如果不了解到全部的布局方式,在一些应用场景,就不能选择到最佳的布局方式来实现当前的需求。

    目录:

    • Row and Column
    • IntrinsicWidth and IntrinsicHeight
    • Stack
    • Expanded
    • ConstrainedBox
    • Align
    • Container
      decoration: BoxDecoration
      • image: DecorationImage
      • border: Border
      • borderRadius: BorderRadius
      • shape: BoxShape
      • boxShadow: List<BoxShadow>
      • gradient: RadialGradient
      • backgroundBlendMode: BlendMode
    • Material
      • shape: BeveledRectangleBorder
    • Slivers
      • SliverFillRemaining
    • SizedBox
    • SafeArea

    Row and Column

    MainAxisAlignment

    void main() => runApp(const MaterialApp(home: TestDemo1()));
    
    class TestDemo1 extends StatelessWidget {
    
      const TestDemo1({super.key});
    
      @override
      Widget build(context) => Scaffold(
        appBar: AppBar(
            title: const Text('TestDemo1')
        ),
        body: Container(
            color: Colors.pink,
            width: double.infinity,
            height: 300,
            child: const Row(
              mainAxisAlignment: MainAxisAlignment.start,    <-- 替换此处
              children: <Widget>[
                Icon(Icons.star, size: 50),
                Icon(Icons.star, size: 50),
                Icon(Icons.star, size: 50),
              ],
            )
        ),
      );
    }
    

    可替换如果 mainAxisAlignment 的设置:

    mainAxisAlignment: MainAxisAlignment.center,
    
    mainAxisAlignment: MainAxisAlignment.end,
    
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    
    mainAxisAlignment: MainAxisAlignment.spaceAround,
    

    如果需要对齐不同文本的基线,则应使用 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 /*or Column*/( 
      crossAxisAlignment: CrossAxisAlignment.start,  <-- 替换此处
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 200),
        Icon(Icons.star, size: 50),
      ],
    ),
    

    可替换如果 crossAxisAlignment 的设置:

    crossAxisAlignment: CrossAxisAlignment.end,
    
    crossAxisAlignment: CrossAxisAlignment.stretch,
    
    mainAxisSize: MainAxisSize.max,
    
    mainAxisSize: MainAxisSize.min,
    

    IntrinsicWidth and IntrinsicHeight

    希望行或列中的所有小部件与最高/最宽的小部件一样高/宽?

    如果你有这种布局:


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

    但是您希望所有按钮都与最宽一样宽,只需使用 IntrinsicWidth

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

    如果您遇到类似的问题,但希望所有小部件与最高的小部件一样高,只需使用 IntrinsicHeightRow 小部件的组合即可。


    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,
          ),
        ],
      );
    }
    

    使用你自己的 Widget,你需要将它们放置在 Positioned Widget

    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),
            ),
          ],
        ),
      );
    }
    

    如果你不想猜测顶部/底部值,你可以使用 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: 50),
                ),
                Positioned(
                  top: constraints.maxHeight - iconSize,
                  left: constraints.maxWidth - iconSize,
                  child: Icon(Icons.call, size: 50),
                ),
              ],
            ),
        ),
      );
    }
    

    Expanded

    Expanded 适用于 Flex\Flexbox 布局,非常适合在多个项目之间分配空间。

    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,您可以指定小部件可以拥有多少空间 - 您可以指定高度/宽度的最小/最大。

    除非指定,否则 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,
      ),
    ),
    

    Align

    有时你很难将我们的小部件设置为适当的大小 - 例如,当你不想时,它会不断拉伸:


    例如,当你有一个带有 CrossAxisAlignment.stretchColumn 并且你只希望按钮不被拉伸时,就会发生上述情况:
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Align: without Align')),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Align(
              child: RaisedButton(
                onPressed: () {},
                child: const Text('Button'),
              ),
            ),
          ],
        ),
      );
    }
    

    总是当你的小部件不听从你尝试设置的约束时,首先尝试用 Align 包装它。


    Container

    最常用的小部件之一 - 并且有充分的理由:

    Container as a layout tool

    当您不指定 Container 的高度和宽度时,它将与其子容器的大小相匹配

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

    如果要拉伸 Container 以匹配其父级,请使用 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 as decoration

    您可以使用 color 属性来影响 Container 的背景、装饰和前景装饰。 (有了这两个属性,你可以完全改变 Container 的外观,但我稍后会讨论不同的装饰,因为这是一个很大的话题)
    Decoration 始终放置在 child 的后面,而 foregroundDecoration 则放置在 child 的顶部

    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 as Transform

    如果你不想使用 Transform 小部件来更改布局,你可以直接从 Container 使用 Transform 属性

    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://img.alicdn.com/imgextra/i3/O1CN01cc4qI41HXKUftY8IO_!!6000000000767-2-tps-200-80.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

    使边框角变圆。
    如果装饰的形状是 BoxShape.circleborderRadius 不起作用

    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 中最复杂的属性。
    它负责将 BoxDecorationBoxDecoration 之上的任何内容的颜色/渐变混合在一起。

    通过 backgroundBlendMode,您可以使用 BlendMode 枚举中指定的一长串算法。

    首先,我们将 BoxDecoration 设置为 foregroundDecoration,它绘制在 Container 的子项之上(而装饰则绘制在子项后面)。

    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://img.alicdn.com/imgextra/i3/O1CN01cc4qI41HXKUftY8IO_!!6000000000767-2-tps-200-80.png',
          ),
        ),
      ),
    );
    

    backgroundBlendMode 不仅仅影响它所在的容器。

    backgroundBlendMode 更改容器中小部件树上任何内容的颜色。
    以下代码有一个绘制图像的父容器和使用 backgroundBlendMode 的子容器。 尽管如此,您仍然会得到与以前相同的效果。

    Scaffold(
      appBar: AppBar(title: Text('backgroundBlendMode')),
      body: Center(
        child: Container(
          decoration: BoxDecoration(
            image: DecorationImage(
              image: NetworkImage(
                'https://img.alicdn.com/imgextra/i3/O1CN01cc4qI41HXKUftY8IO_!!6000000000767-2-tps-200-80.png',
              ),
            ),
          ),
          child: Container(
            height: 200,
            width: 200,
            foregroundDecoration: BoxDecoration(
              backgroundBlendMode: BlendMode.exclusion,
              gradient: LinearGradient(
                colors: const [
                  Colors.red,
                  Colors.blue,
                ],
              ),
            ),
          ),
        ),
      ),
    );
    

    Material

    带有切角的边框


    Scaffold(
      appBar: AppBar(title: Text('shape: BeveledRectangleBorder')),
      body: Center(
        child: Material(
          shape: const BeveledRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(20)),
            side: BorderSide(color: Colors.black, width: 4),
          ),
          color: Colors.yellow,
          child: Container(
            height: 200,
            width: 200,
          ),
        ),
      ),
    );
    

    Slivers

    SliverFillRemaining

    当你想要将内容居中时,即使没有足够的空间,此小部件也是不可替代的。


    Scaffold(
      appBar: AppBar(title: Text('SliverFillRemaining')),
      body: CustomScrollView(
        slivers: [
          SliverFillRemaining(
            hasScrollBody: false,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                FlutterLogo(size: 200),
                Text(
                  'This is some longest text that should be centered'
                  'together with the logo',
                  textAlign: TextAlign.center,
                ),
              ],
            ),
          ),
        ],
      ),
    );
    

    如果没有足够的空间容纳居中的内容,SliverFillRemaining 将变为可滚动:


    如果没有 SliverFillRemaining ,内容会溢出,如下所示:

    填充剩余空间

    除了有助于将内容居中之外,SliverFillRemaining 还将填充剩余视口的可用空间。 为此,这个小部件必须放置在 CustomScrollView 中,并且需要是最后一个条子


    如果没有足够的空间,小部件将变为可滚动:
    Scaffold(
      appBar: AppBar(title: Text('SliverFillRemaining')),
      body: CustomScrollView(
        slivers: [
          SliverList(
            delegate: SliverChildListDelegate(const [
              ListTile(title: Text('First item')),
              ListTile(title: Text('Second item')),
              ListTile(title: Text('Third item')),
              ListTile(title: Text('Fourth item')),
            ]),
          ),
          SliverFillRemaining(
            hasScrollBody: false,
            child: Container(
              color: Colors.yellowAccent,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: const [
                  FlutterLogo(size: 200),
                  Text(
                    'This is some longest text that should be centered'
                    'together with the logo',
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    

    SizedBox

    它是最简单但最有用的小部件之一

    SizedBox as ConstrainedBox

    SizedBox 的工作方式与 ConstrainedBox 类似

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

    SizedBox as padding

    当需要添加填充或边距时,您可以选择填充或容器小部件。 但它们可能比添加 Sizedbox 更冗长且可读性更差

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

    SizedBox as an Invisible Object

    很多时候你想根据布尔值隐藏/显示小部件

    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 小部件并将不透明度值更改为 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),
          ),
        ),
      );
    }
    

    目前使用 Flutter 来布局还不是很熟练的朋友,希望看完本文后对你有一定的帮助。

    相关文章

      网友评论

          本文标题:Flutter 各种布局方式汇总

          本文链接:https://www.haomeiwen.com/subject/vtxapdtx.html