Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flutter 布局备忘录 -- 多图警告,干货建议收藏 #148

Open
reng99 opened this issue Jun 13, 2022 · 0 comments
Open

Flutter 布局备忘录 -- 多图警告,干货建议收藏 #148

reng99 opened this issue Jun 13, 2022 · 0 comments
Labels
blog a single blog flutter

Comments

@reng99
Copy link
Owner

reng99 commented Jun 13, 2022

banner

你是否需要了解 Flutter 布局的案例?

这里我将展示我在使用 Flutter 布局的代码片段。我将通过精美的代码片段结合可视化的图形来举例。

本文注重 Flutter 部件中比较有用的一些来展示,而不是走马观花展示一大推的部件内容。

本文是翻译的文章,采用意译的方式

Row and Column

行(Row)和列(Column)的布局

MainAxisAlignment

Row Column
MainAxisAlignment_row.png MainAxisAlignment_column.png
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
MainAxisAlignment_center_row.png MainAxisAlignment_center_column.png
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
MainAxisAlignment_end_row.png MainAxisAlignment_end_column.png
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
MainAxisAlignment_spaceBetween_row.png MainAxisAlignment_spaceBetween_column.png
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
MainAxisAlignment_spaceEvenly_row.png MainAxisAlignment_spaceEvenly_column.png
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
MainAxisAlignment_spaceAround_row.png MainAxisAlignment_spaceAround_column.png
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

CrossAxisAlignment

CrossAxisAlignment.baseline.png

如果你需要文本是针对基线对齐,那么你应该使用 CrossAxisAlignment.baseline

Row(
  crossAxisAlignment: CrossAxisAlignment.baseline,
  textBaseline: TextBaseline.alphabetic,
  children: <Widget>[
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.headline2,
    ),
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.bodyText2,
    ),
  ],
),
Row Column
CrossAxisAlignment.start_row.png CrossAxisAlignment.start_column.png
Row /*or Column*/(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
CrossAxisAlignment.center_row.png CrossAxisAlignment.center_column.png
Row /*or Column*/(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
CrossAxisAlignment.end_row.png CrossAxisAlignment.end_column.png
Row /*or Column*/(
  crossAxisAlignment: CrossAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
CrossAxisAlignment.stretch_row.png CrossAxisAlignment.stretch_column.png
Row /*or 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.max_row.png MainAxisSize.max_column.png
Row /*or Column*/(
  mainAxisSize: MainAxisSize.max,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
Row Column
MainAxisSize.min_row.png MainAxisSize.min_column.png
Row /*or Column*/(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

IntrinsicWidth and IntrinsicHeight

在行列布局中,如何使得所有的部件跟宽度/高度最大的部件同宽/同高呢?如下:

我们假有下面的布局:

IntrinsicWidth_init.png

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

那么,你想所有的按钮的宽度都跟最宽的按钮那么宽,那就使用 IntrinsicWidth

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 来实现。

Stack

Stack 很适合小部件相互叠加。

Stack_01.png

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 部件中。

Stack_02.png

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 部件来检索它们的值。

Stack_03.png

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 布局实现,它对于多项目分配空间很棒。

Expanded.png

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

默认的,很多部件多尽量使用小空间,比如:

no_constraints.png

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

ConstrainedBox 允许小部件根据需要使用剩下的空间。

BoxConstraints.expand.png

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

使用 BoxConstraints,你可以指定一个小部件可以有多少空间,你可以指定高度/宽度的最小/最大值。

除非指定值,否则 BoxConstraints.expand 使用无限的空间量(也就是使用剩下的所有空间):

expand_height_specify.png

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

有时候,我们很难设置我们的小部件到正确的大小 -- 比如,它们自由伸展,但是这不是你想要的。

without_align.png

当你在 Column 中使用 CrossAxisAlignment.stretch 的时候,上面的现象就会发生,而你想要的是这个按钮不伸展。

with_align.png

Column(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    Align(
      child: RaisedButton(
        onPressed: () {},
        child: const Text('Button'),
      ),
    ),
  ],
),

当你的小部件并不受限你设定的约束时,那么你可以尝试使用 Align 部件包裹它。

Container

Container 是最常用的部件之一 -- 有如下的好处:

Container as a layout tool

当你没有指定 Container 的高度 height 或者宽度 width 的时候,它会自动适配 child 子部件的大小。

container_as_a_layout.png

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

如果你想伸展 Container 来适配它的父部件,请为属性高度 height 或宽度 width 设定值 double.infinity

double_infinity.png

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

你可以使用 Containercolor 属性来更改其背景颜色,但是你也可以使用 decorationforegroundDecoration 来更改。(使用这两个属性,你完全可以更改 Container 的样子,这个我们迟点说)。

decoration 总是在 child 属性的后面,而 foregroundDecoration 总是在 child 属性的后面。(这也不一定)

container_decoration.png

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

container_foreGroundDecoration.png

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 属性。

container_transform.png

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

decoration 通常用于更改 Container 部件的外观。

image: DecorationImage

图片作为背景:

DecorationImage.png

Widget build(BuildContext context) {
  return 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.dev/images/catalog-widget-placeholder.png', // 地址已经无效
            ),
          ),
        ),
      ),
    ),
  );
}

border: Border

指定 Container 的边框看起来该怎样。

border_Border.png

Widget build(BuildContext context) {
  return 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

使得边框角变圆。

如果装饰中 shape 属性的值是 BoxShape.circle,那么 borderRadius 不会起作用。

border_radius.png

Widget build(BuildContext context) {
  return 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

BoxDecoration 可以是矩形/正方形或者椭圆/圆形。

对于其他形状,你可以使用 ShapeDecoration 代替 BoxDecoration

Border_Radius_borderRadius.png

Widget build(BuildContext context) {
  return 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

Container 添加阴影。

这个参数是一个列表,你可以指定多个不同的阴影并将它们合并在一起。

boxShadow_list.png

Widget build(BuildContext context) {
  return 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.png

Widget build(BuildContext context) {
  return 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.png

Widget build(BuildContext context) {
  return 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.png

Widget build(BuildContext context) {
  return 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 枚举中指定的一长串算法。

首先,让我们将 BoxDecoration 设置为 foregroundDecoration,它被绘制在 Container 子部件之上(而 decoration 会绘制在子部件之后)。

backgroundBlendMode01.png

Widget build(BuildContext context) {
  return 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', // 图片 404
        ),
      ),
    ),
  );
}

backgroundBlendMode 不仅仅影响它所在的 Container

backgroundBlendMode 改变其所在 Container 及其一下部件树的内容的颜色。

下面的代码又一个父部件 Container 来绘制一个 image,然后有一个子部件 Container 来使用 backgroundBlendMode,但是你还是获取到和之前的效果。

backgroundBlendMode02.png

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('backgroundBlendMode')),
    body: Center(
      child: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: NetworkImage(
              'https://flutter.io/images/catalog-widget-placeholder.png', // 404
            ),
          ),
        ),
        child: Container(
          height: 200,
          width: 200,
          foregroundDecoration: BoxDecoration(
            backgroundBlendMode: BlendMode.exclusion,
            gradient: LinearGradient(
              colors: const [
                Colors.red,
                Colors.blue,
              ],
            ),
          ),
        ),
      ),
    ),
  );
}

Material

有切角的边框。

BeveledRectangleBorder.png

Widget build(BuildContext context) {
  return 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

即便没有足够的空间,当你想要将内容居中,这个部件也是不可替代的。

SliverFillRemaining.png

Widget build(BuildContext context) {
  return 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 将变为可滚动。

with_SliverFillRemaining.png

如果没使用 SliverFillRemaining,内容将会像下面这样溢出:

without_SliverFillRemaining.png

Filling the remaining space

除了对内容居中有用之外,SliverFillRemaining 还会填充剩余视口的可用空间。为此,此部件必须放置在 CustomScrollView 中,并且必须是最后一个 sliver

fill_remianing_space01.png

如果没有足够的空间,部件将变为可滚动。

fill_remianing_space02.png

Widget build(BuildContext context) {
  return 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 是最简单但是最常用的小部件之一。

SizedBox as ConstrainedBox

SizedBox 工作方式跟 ConstrainedBox 有些类似。

sizebox_expand.png

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

SizedBox as padding

当需要添加内边距和外边距的时候,你可以选择 PaddingContainer 小部件。但是,它们可以比添加 Sizedbox 更冗长且可读性更低。

sizebox_as_padding.png

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

很多时候,你想通过设置一个 bool 值来隐藏/展示小部件。

Show Hide
is_visible_true.png is_visible_false.png
Widget build(BuildContext context) {
  bool isVisible = true; // true or false
  return Scaffold(
    appBar: AppBar(
      title: Text('isVisible = $isVisible'),
    ),
    body: isVisible 
      ? Icon(Icons.star, size: 150) 
      : const SizedBox(),
  );
}

因为 SizedBox 有一个 const 构造函数,所以使用 const SizeBox() 真的很便宜。一种更便宜的解决方案是使用 Opacity 小部件,将其 opacity 值更改为 0.0。这种解决方案的缺点是给定的小部件只是不可见,但是还是占用空间。

SafeArea

在不同的平台,有些特定的区域,比如安卓的状态栏或者 iPhone X 的刘海区块,我们不应该在其下面绘制内容。

解决方案是使用 SafeArea 小部件。(下面截图是没使用/使用 SafeArea

without with
without_safeArea.png with_safeArea.png
Widget build(BuildContext context) {
  return Material(
    color: Colors.blue,
    child: SafeArea(
      child: SizedBox.expand(
        child: Card(color: Colors.yellowAccent),
      ),
    ),
  );
}

本文翻译自 Flutter Layout Cheat Sheet。代码已经验证,需要留意 RaisedButton 已经被 ElevatedButton 替代,在现实使用中需要留意。本文重点是其布局思路和技巧。

@reng99 reng99 added flutter blog a single blog labels Jun 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blog a single blog flutter
Projects
None yet
Development

No branches or pull requests

1 participant