Flutter:3分钟教你构建漂亮的UI界面

摘要

本文通过一个简单的实例来逐步为大家介绍如何在Flutter中构建漂亮的布局,通过本文你将会了解到以下几点:

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

本文档主要介绍如何在Flutter中进行布局,你将最终会构建一个下图这样的页面:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

本文将一步一步带你构建一个像上图那样的页面。

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

设置完成后运行起来效果如下图所示:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

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

第一步是将整个页面布局分解为基本元素,主要从以下几点入手

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

那么我们先来看一下整个页面的主要组成部分:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

可以看到,整个页面的主要组成部分就是由红色框标注出来的4部分,这4部分位于同一个列(Column),分别是:1个Image、2个Row、1个Text。

再深入地分析一下每一个行(Row):

  • 第1行,也就是标题栏(Title section),它有3列:1个由2行text组成的列(Column)、1个星星的Icon、1个数字:
一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

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

  • 第二行,我们也就是按钮列(button section),同样也包含了3个子元素,每一个子元素都是1个行,行里面的内容是1个icon和1个text:
一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面
  • 第三行:第三行没有很复杂的构成,就是单纯的text块。

经过以上的分析,我们将一个复杂的页面分解为了多个简单的组成部分,这样可以简化整个页面的实现,为了避免布局代码的混乱,我们应该利用变量和函数来构建布局的子部分,这一点我会在下面的代码中进行演示。

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

直接给出代码,具体的解释在代码的注释中,大家注意看注释:

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中即可:

@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: <Widget>[titleSection],
 )));
 }

注意,这里通过使用变量titleSection来达到简化布局代码的目的,这样就不会导致MaterialApp(..)中的代码太长、太混乱。

运行起来的效果如下图所示:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

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

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

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中,这样做的目的是借助Container为Text设置顶部填充(padding),这样就不至于Text和Icon距离过近。

完成了_buildButtonColumn()函数之后,我们只需要在需要构建列(Column)的时候调用该函数并传入对应的3个参数即可构建出我们需要的列:

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

注意这里设置Row的mainAxisAlignment属性值为MainAxisAlignment.spaceEvenly,目的是让Row中的列均匀地占满整个行的可用空间。

然后将变量buttonSection放在app body中:

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

运行效果图如下图所示:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

第4步:实现Text 部分

将Text部分定义为一个变量,然后将Text放置在一个Container中,并为Container设置padding属性:

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,这样可以保证文字可以在单词分界的地方换行而不是在单词中间换行。

然后将上面的textSection变量放在app body中:

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

运行效果图如下图所示:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

第5步:实现Image部分

到现在为止我们已经完成了4行中的3行,只剩下图像那行还没实现,这一步我们来实现图像的显示:

  • 在项目的顶级目录下创建一个images文件夹
  • 将下图放在刚才创建的images文件夹下,命名为lake.jpg
一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面
  • 更新pubspec.yaml文件,添加assets标签,配置图片路径,这样就可以在代码中访问到你存放的图片:
flutter: 
 uses-material-design: true 
 assets: 
 - images/lake.jpg

然后我们就可以在代码中引用该图了:

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

运行起来效果如下:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

第6步:另一种尝试

我们来看前几步完成之后的最终代码:

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

注意,我们将body的属性值设置为了Column,然后在Column中放置了我们实现的几个子Widget,现在我们换种方式,使用ListView来取代这个Column:

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

效果如下:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

可以看到,使用Column和使用ListView的静态视觉效果是一样的,那我们到底应该使用Column还是ListView呢?二者的区别在哪里呢?答案就在”动态”,二者在动态下的效果是不一样的,再具体点,使用Column构建的Widget是不支持滚动的,即不支持进行上下或者左右的滚动事件,而使用ListView会支持滚动事件,就像下面这样:

一个月带你入门Flutter:UI篇(3)—3分钟教你构建漂亮的UI界面

最后给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

需要高清架构图以及图中视频资料的可以加入我的技术交流群:457848807私聊群主小姐姐免费获取

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容