懂 Vue、React 就懂 Flutter 页面布局 - 上篇

在学习 Flutter 时一定要记住 在Flutter中,几乎所有的东西都是Widget,这句话很重要,今天聊的布局在 Flutter 中也是通过 Widget 组件来实现的,包括对齐、填充布局,字体都是。这似乎与前端开发中通过 CSS 布局有很大的不同,但是问题不大,和标题一样,懂 Vue、React 就懂 Flutter 页面布局!

先来看个官方的例子

移动端 Web 布局通常使用 Flex 可以很简单的实现这个布局:

css 复制代码
<div style="display: flex; justify-content: space-between;">
  <button>Call</button>
  <button>Route</button>
  <button>Share</button>
</div>

那我们刚才提到,Flutter 布局是通过组件的,那我们直接找到 Flutter 行组件 Row,并且设置它类似 flex 中justify-content:space-between 这样的属性就行

dart 复制代码
Row(
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    RaisedButton(
      onPressed: () {},
      child: Text('Call'),
    ),
    RaisedButton(
      onPressed: () {},
      child: Text('Route'),
    ),
    RaisedButton(
      onPressed: () {},
      child: Text('Share'),
    ),
  ],
)

通过对比CSS 和 Flutter 的布局方式,我们会得到几个核心规律:

  1. CSS 的嵌套关系不需要显示的声明,包裹着就行, Flutter 的嵌套关系需要通过 children 或者 child 来声明
  2. CSS 中布局和样式是添加到 HTML 标签上的,比如给图片设置样式 <img width=xxx>, 将 width 设置在 img 标签上。而在Flutter中是将样式设置到组件上比如 Icon(Icons.star, color:Colors.red)color 设置在 Icon 组件上, 甚至样式都是组件,比如设置 Text 的 style 时, 需要通过TextStyle 组件设置
  3. Flutter 中的布局是通过组件实现的

那检测一下我们的理解,如何写一个居中的 Hello World?

由规律 3 可知: 居中是一个组件,我们找到Center组件 由规律 1 可知: 需要给Center加一个child属性来声明嵌套一个 Text文字组件

Dart 复制代码
Center(
  child: Text('Hello World'),
),

有了基本的布局理解后我们基本可以实现我们想要的任何样式。

盒子布局

在CSS 和 Flutter中,盒子布局是一种常见的布局方式。

在CSS中,每个元素都被视为一个矩形盒子,这个盒子由内容、内边距、边框和外边距组成。这种布局方式允许我们精确地控制每个元素的大小和位置。CSS的盒子模型是网页布局的基础。例如,我们可以使用以下CSS代码来创建一个带有内边距、边框和外边距的盒子:

html 复制代码
<div style="padding:10px; border:1px solid black; margin:10px;">
This is a box.
</div>

在Flutter中,盒子布局是通过Container Widget来实现的。Container Widget可以设置边距、填充、边框和背景颜色等属性,从而创建一个可定制的盒子。以下是在Flutter中创建一个带有内边距、边框和外边距的盒子的示例:

less 复制代码
Container(
  margin: EdgeInsets.all(10.0),
  padding: EdgeInsets.all(10.0),
  decoration: BoxDecoration(
    border: Border.all(color: Colors.black, width: 1.0),
  ),
  child: Text('This is a box.'),
)

在这个例子中,我们创建了一个带有10像素外边距、10像素内边距和1像素黑色边框的盒子,盒子中的文字是'This is a box.'。

稍稍扩展一下,在CSS中,我们可以通过使用flex布局来创建更加复杂的盒子布局。例如,我们可以使用以下CSS代码来创建一个水平排列的盒子布局, 它指定了每个盒子的占比。

html 复制代码
<div style="display: flex;">
  <div style="flex: 1; padding:10px; border:1px solid black; margin:10px;">Box 1</div>
  <div style="flex: 2; padding:10px; border:1px solid black; margin:10px;">Box 2</div>
  <div style="flex: 1; padding:10px; border:1px solid black; margin:10px;">Box 3</div>
</div>

在Flutter中,我们可以通过使用FlexExpanded Widget来创建更加复杂的盒子布局。Flex Widget可以创建一个线性布局,而Expanded Widget则可以让这个布局中的元素填充可用空间,填充多少可以通过flex属性控制,示例:

dart 复制代码
Row(
  children: <Widget>[
    Expanded(
      flex: 1,
      child: Container(
        margin: EdgeInsets.all(10.0),
        padding: EdgeInsets.all(10.0),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.black, width: 1.0),
        ),
        child: Text('Box 1'),
      ),
    ),
    Expanded(
      flex: 2,
      child: Container(
        margin: EdgeInsets.all(10.0),
        padding: EdgeInsets.all(10.0),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.black, width: 1.0),
        ),
        child: Text('Box 2'),
      ),
    ),
    Expanded(
      flex: 1,
      child: Container(
        margin: EdgeInsets.all(10.0),
        padding: EdgeInsets.all(10.0),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.black, width: 1.0),
        ),
        child: Text('Box 3'),
      ),
    ),
  ],
)

在这个例子中,我们创建了一个水平排列的盒子布局,盒子1和盒子3的宽度是盒子2的宽度的一半。

来做个朋友圈九宫格

效果类似这样(网上截的图。。。)

先来做9宫格图片墙

dart 复制代码
Row(
  children: <Widget>[
    Expanded(
      child: Container(
        margin: EdgeInsets.all(4.0),
        child: Image.asset('images/1.png', height: 100, width: 100, fit:BoxFit.cover),
    )),
    Expanded(
        child: Container(
          margin: EdgeInsets.all(4.0),
          child: Image.asset('images/2.png', height: 100, width: 100, fit:BoxFit.cover),
        )),
    Expanded(
        child: Container(
          margin: EdgeInsets.all(4.0),
          child: Image.asset('images/3.png', height: 100, width: 100, fit:BoxFit.cover),
        )),
  ],
)

Tips: 使用 Image.asset 之前需要先在 pubspec.yaml 中声明静态资源文件路径

yaml 复制代码
flutter:
  assets:
    - images/

显然剩下的 6 张再这么 copy 出来太蠢了,上循环

dart 复制代码
static const List<String> images = [
  'images/1.png',
  'images/2.png',
  'images/3.png',
  'images/4.png',
  'images/5.png',
  'images/6.png',
  'images/7.png',
  'images/8.png',
  'images/9.png'
];

List<Widget> buildRows() {
  List<Widget> rows = [];
  for (int i = 0; i < images.length; i += 3) {
    rows.add(Row(
      children: images
          .sublist(i, i + 3)
          .map((image) => Expanded(
                child: Container(
                  margin: EdgeInsets.all(4.0),
                  child: AspectRatio(
                    aspectRatio: 1,
                    child: Image.asset(image, fit: BoxFit.cover),
                  ),
                ),
              ))
          .toList(),
    ));
  }
  return rows;
}

@override
Widget build(BuildContext context) {
  return MaterialApp(
      home: Scaffold(body: Center(child: Column(children: buildRows()))));
}

在这段代码中,我们先创建一个空的rows列表来存储Row Widget。

然后,通过一个for循环,每次循环从images列表中取出三个图片路径,然后创建一个Row Widget。在这个Row Widget中,每张图片都被封装在一个Expanded Widget中,这样可以确保每张图片都能在水平方向上平均分配空间。每张图片都被包裹在一个Container Widget中,每个Container都设置了4.0的边距。在每个Container中,图片是通过Image.asset方法加载的,image参数是图片的路径,fit: BoxFit.cover是设置图片的填充模式,保证图片在Container中充满,但是可能会被裁剪。

布局是一个很灵活的东西,不止一种解法,对于这个例子我们也可以使用 GridView Widget 来实现 9 宫格

dart 复制代码
home: Scaffold(
    body: Center(
        child: GridView.count(
            crossAxisCount: 3,
            padding: EdgeInsets.zero,
            crossAxisSpacing: 0,
            mainAxisSpacing: 0,
            children: List.generate(9, (index) {
              int picName = index + 1;
              return Center(
                child: Image.asset('images/$picName.png',
                    width: 100, height: 100, fit: BoxFit.cover),
              );
            })))));

在这个例子中,我们创建了一个GridView Widget,并设置了crossAxisCount为 3,这意味着每行会显示三个子Widget。然后,我们使用List.generate方法来创建9个Image.asset Widget作为GridView的子Widget。这样就可以实现每行显示三张图片的布局。

同时将 padding 设置为 EdgeInsets.zero 消除 GridView 的默认间距, crossAxisSpacing 是指网格视图中每列之间的间距, mainAxisSpacing 是指网格视图中每行之间的间距。

限制图片尺寸

我们来看一个诡异的事情

dart 复制代码
Widget build(BuildContext context) {
  return MaterialApp(
      // home: Scaffold(body: Center(child:Column(children: buildRows()))) );
      home: Scaffold(
          body: Center(
              child: Row(children: [
                Image.asset('images/1.png')
              ],))));
}

这段代码看起来没什么,但是实际效果竟然:

但如果这个图片不在 Row 中,显示就是正常的

dart 复制代码
Widget build(BuildContext context) {
  return MaterialApp(
      // home: Scaffold(body: Center(child:Column(children: buildRows()))) );
      home: Scaffold(
          body: Center(
              child:
                Image.asset('images/1.png')
              )));
}

原因很简单,就是图片太大导致 Row 溢出了,那这时就有2种解决办法:

  1. 溢出的话就给他加个横向滚动条,不让他溢出
  2. 限制图片填充满 Row,不按照原来图片尺寸

那我们先按照方法1来处理

less 复制代码
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        // home: Scaffold(body: Center(child:Column(children: buildRows()))) );
        home: Scaffold(
        body: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Row(
        children: <Widget>[
          Image.asset('images/1.png')
        ],
    ))
    ));
  }
}

我们将 SingleChildScrollView 的 scrollDirection 属性设置为Axis.horizontal,使得其子组件 Row 可以在水平方向上滚动来避免溢出

接着我们再使用方法2来处理,其实方法 2 我们之前已经用过了,就是使用 Expanded 组件来让图片填充满这个 Row, fit 属性是指填充时该如何适应比如裁切(BoxFit.cover)

dart 复制代码
Widget build(BuildContext context) {
  return MaterialApp(
      // home: Scaffold(body: Center(child:Column(children: buildRows()))) );
      home: Scaffold(
      body: Row(
      children: <Widget>[
        Expanded(child: Image.asset('images/1.png', fit: BoxFit.cover))
      ],
  )));
}
相关推荐
PyAIGCMaster10 分钟前
python环境中,敏感数据的存储与读取问题解决方案
服务器·前端·python
baozhengw12 分钟前
UniAPP快速入门教程(一)
前端·uni-app
nameofworld21 分钟前
前端面试笔试(二)
前端·javascript·面试·学习方法·数组去重
帅比九日41 分钟前
【HarmonyOS NEXT】实战——登录页面
前端·学习·华为·harmonyos
摇光931 小时前
promise
前端·面试·promise
麻花20131 小时前
WPF学习之路,控件的只读、是否可以、是否可见属性控制
服务器·前端·学习
.5481 小时前
提取双栏pdf的文字时 输出文件顺序混乱
前端·pdf
jyl_sh1 小时前
WebKit(适用2024年11月份版本)
前端·浏览器·客户端·webkit
狼叔2 小时前
前端潮流KK:科技达人与多面手,如何找到自己的乐趣?-浪说回顾
前端
zhanghaisong_20152 小时前
Caused by: org.attoparser.ParseException:
前端·javascript·html·thymeleaf