如何在Flutter中构建漂亮的UI布局Widget

2019-06-1117:25:34APP与小程序开发Comments2,234 views字数 8230阅读模式

通过一个简单的例子来逐步为大家介绍如何在Flutter中构建漂亮的布局,通过本文你将会了解到以下几点:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

  • Flutter的布局机制是如何工作的
  • 如何在垂直方向和水平方向布局Widget
  • 如何在Flutter中进行Widget的布局

本文档主要介绍如何在Flutter中进行布局,你将最终会构建一个下图这样的页面:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
The finished app

本文将一步一步带你构建一个像上图那样的页面。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

第0步:创建一个Flutter项目

  1. 创建一个Flutter项目
  2. 将该项目的app bar的标题和app的标题设置为下面这样:
    Widget build(BuildContext context) {            
          return MaterialApp(
                 title: 'Flutter layout demo',
                 home: Scaffold(
                   appBar: AppBar(
                     title: Text('Flutter layout demo'),
                   ),
                   body: Center(
                     child: Text('Hello World'),
                   ),
                 ));
    }
    

设置完成后运行起来效果如下图所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
image-20190610130957545

第1步:分析页面的布局逻辑

第一步是将整个页面布局分解为基本元素,主要从以下几点入手文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

  • 辨识行(Row)和列(Column
  • 布局中是否存在网格(grid)?
  • 是否存在Widget之间的覆盖?
  • 整个UI布局是否需要tab栏?
  • 关注需要对其(alignment)、填充(padding)、边框(border)的区域

那么我们先来看一下整个页面的主要组成部分:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
Column elements

可以看到,整个页面的主要组成部分就是由红色框标注出来的4部分,这4部分位于同一个列(Column),分别是:1个Image、2个Row、1个Text文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

再深入地分析一下每一个行(Row):文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

  • 第1行,也就是标题栏(Title section),它有3列:1个由2行text组成的列(Column)、1个星星的Icon、1个数字:
    如何在Flutter中构建漂亮的UI布局Widget
    Title section

    可以看到,由于第1个列(Column)占据了整个行的大部分空间,所以其应该被Expanded Widget包裹。

  • 第二行,我们也就是按钮列(button section),同样也包含了3个子元素,每一个子元素都是1个行,行里面的内容是1个icon和1个text
    如何在Flutter中构建漂亮的UI布局Widget
    Button section
  • 第三行:第三行没有很复杂的构成,就是单纯的text块。

经过以上的分析,我们将一个复杂的页面分解为了多个简单的组成部分,这样可以简化整个页面的实现,为了避免布局代码的混乱,我们应该利用变量和函数来构建布局的子部分,这一点我会在下面的代码中进行演示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

第2步:实现第一行(title Row)

直接给出代码,具体的解释在代码的注释中,大家注意看注释:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

Widget titleSection = Container(
  //为整个Widget(即这一行)的所有所有方向设置32px的填充
  padding: const EdgeInsets.all(32),
  child: Row(
    children: [
      Expanded(
        /**
        将Column放置在Expanded中,由于Expanded会默认占据所有当前Row的空闲可用空间,所以这个Column也会自然被拉伸的占据完所有当前Row可用的空闲空间。
        */
        child: Column(
          /**将Column的crossAxisAlignment属性设置为CrossAxisAlignment.start以保证这个列中的元素(即children属性中的Widget)在水平方向上排列在当前Column的起始位置
          */
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            /**
            将这个Text放在Container中的目的是通过Container来添加填充(padding)
                        */
            Container(
              padding: const EdgeInsets.only(bottom: 8),
              child: Text(
                'Oeschinen Lake Campground',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            Text(
              'Kandersteg, Switzerland',
              style: TextStyle(
                color: Colors.grey[500],
              ),
            ),
          ],
        ),
      ),
      /**
      最后的2个元素分别是1个Icon和1个Text,分别用来显示星星和数字
      */
      Icon(
        Icons.star,
        color: Colors.red[500],
      ),
      Text('41'),
    ],
  ),
);

直接将上面的变量titleSection放在在app body中即可:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

@override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter layout demo',
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            appBar: AppBar(
              title: Text('Flutter layout demo'),
            ),
            body: Column(
              children: [titleSection],
            )));
  }

注意,这里通过使用变量titleSection来达到简化布局代码的目的,这样就不会导致MaterialApp(..)中的代码太长、太混乱。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

运行起来的效果如下图所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
image-20190610133211088

第3步:实现按钮行(button row)

按钮行(button section)有3个列,每个列都是同样的布局组成:1个Icon、1个Text。这一行中的列之间的间距都是均匀的,由于构建每一列的代码几乎相同,因此创建一个名为buildbuttoncolumn()的私有方法,该方法接收一个颜色(Color)参数、一个图标(Icon)参数和1个文本(Text)参数,并返回一个具有以给定颜色绘制的Widget的列(Column),代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // ···
  }

  Column _buildButtonColumn(Color color, IconData icon, String label) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}

_buildButtonColumn()方法将Icon直接添加到Column中,而Text是先用Container进行包裹,然后再将Container添加到Column中,这样做的目的是借助ContainerText设置顶部填充(padding),这样就不至于TextIcon距离过近。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

完成了_buildButtonColumn()函数之后,我们只需要在需要构建列(Column)的时候调用该函数并传入对应的3个参数即可构建出我们需要的列:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

Color color = Theme.of(context).primaryColor;

Widget buttonSection = Container(
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      _buildButtonColumn(color, Icons.call, 'CALL'),
      _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
      _buildButtonColumn(color, Icons.share, 'SHARE'),
    ],
  ),
);

注意这里设置RowmainAxisAlignment属性值为MainAxisAlignment.spaceEvenly,目的是让Row中的列均匀地占满整个行的可用空间。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

然后将变量buttonSection放在app body中:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

 return MaterialApp(
        title: 'Flutter layout demo',
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            appBar: AppBar(
              title: Text('Flutter layout demo'),
            ),
            body: Column(
              children: [titleSection, buttonSection],
            )));

运行效果图如下图所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
image-20190610140530038

第4步:实现Text部分

Text部分定义为一个变量,然后将Text放置在一个Container中,并为Container设置padding属性:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

Widget textSection = Container(
  padding: const EdgeInsets.all(32),
  child: Text(
    'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
    softWrap: true,
  ),
);

注意这里设置softwrap属性值为true,这样可以保证文字可以在单词分界的地方换行而不是在单词中间换行。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

然后将上面的textSection变量放在app body中:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

 return MaterialApp(
        title: 'Flutter layout demo',
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            appBar: AppBar(
              title: Text('Flutter layout demo'),
            ),
            body: Column(
              children: [titleSection, buttonSection, textSection],
            )));

运行效果图如下图所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
image-20190610141009130

第5步:实现Image部分

到现在为止我们已经完成了4行中的3行,只剩下图像那行还没实现,这一步我们来实现图像的显示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

  • 在项目的顶级目录下创建一个images文件夹
  • 将下图放在刚才创建的images文件夹下,命名为lake.jpg
    如何在Flutter中构建漂亮的UI布局Widget
    img
  • 更新pubspec.yaml文件,添加assets标签,配置图片路径,这样就可以在代码中访问到你存放的图片:
    flutter:            
                  uses-material-design: true            
                assets:            
                  - images/lake.jpg
    

然后我们就可以在代码中引用该图了:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

return MaterialApp(
        title: 'Flutter layout demo',
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            appBar: AppBar(
              title: Text('Flutter layout demo'),
            ),
            body: Column(
              children: [
                Image.asset(
                  'images/lake.jpg',
                  width: 600,
                  height: 240,
                  fit: BoxFit.cover,
                ),
                titleSection,
                buttonSection,
                textSection
              ],
            )));

运行起来效果如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
image-20190610144829576

第6步:另一种尝试

我们来看前几步完成之后的最终代码:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

return MaterialApp(
        title: 'Flutter layout demo',
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            appBar: AppBar(
              title: Text('Flutter layout demo'),
            ),
            body: Column(
              children: [
                Image.asset(
                  'images/lake.jpg',
                  width: 600,
                  height: 240,
                  fit: BoxFit.cover,
                ),
                titleSection,
                buttonSection,
                textSection
              ],
            )));

注意,我们将body的属性值设置为了Column,然后在Column中放置了我们实现的几个子Widget,现在我们换种方式,使用ListView来取代这个Column文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

return MaterialApp(
        title: 'Flutter layout demo',
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            appBar: AppBar(
              title: Text('Flutter layout demo'),
            ),
            body: ListView(
              children: [
                Image.asset(
                  'images/lake.jpg',
                  width: 600,
                  height: 240,
                  fit: BoxFit.cover,
                ),
                titleSection,
                buttonSection,
                textSection
              ],
            )));

效果如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
image-20190610145151596

可以看到,使用Column和使用ListView的静态视觉效果是一样的,那我们到底应该使用Column还是ListView呢?二者的区别在哪里呢?答案就在"动态",二者在动态下的效果是不一样的,再具体点,使用Column构建的Widget是不支持滚动的,即不支持进行上下或者左右的滚动事件,而使用ListView会支持滚动事件,就像下面这样:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html

如何在Flutter中构建漂亮的UI布局Widget
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/13573.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/xcx/13573.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定