核心:这里是参照Android xml布局对比学习(web,ios也类似)
具体布局详情可以参照flutter官方
Flutter 布局控件完结篇
Flutter学习指南:UI布局和控件
- 基础布局组件:Container(容器布局),Center(居中布局),Padding(填充布局),Align(对齐布局),Colum(垂直布局),Row(水平布局),Expanded(配合Colum,Row使用),FittedBox(缩放布局),Stack(堆叠布局),overflowBox(溢出父视图容器)。
- 宽高尺寸处理:SizedBox(设置具体尺寸),ConstrainedBox(限定最大最小宽高布局),LimitedBox(限定最大宽高布局),AspectRatio(调整宽高比),FractionallySizedBox(百分比布局)
- 列表和表格处理:ListView(列表),GridView(网格),Table(表格)
-
其它布局处理:Transform(矩阵转换),Baseline(基准线布局),Offstage(控制是否显示组件),Wrap(按宽高自动换行布局)
image.png
整个过程,基本上按照拆解、组件封装、具体布局这三步来
拆解

整体拆解
根据设计图,可以看出整体时分行展示的,因此最外层是一个Column元素
- 第一行为标题,涉及到不对称的布局,可以用一个Stack或者Row来进行,用Row的话,则需要右边填上一个空白的widget占位。也可能会使用AppBar,将底部阴影去掉也能实现相同效果;
- 第二行可以看作一个Row,分两块布局。右边部分,涉及到叠加,会考虑Stack;
- 第三行比较复杂,整体看,也是一行一行进行展示的,因此最外层时一个Column。中间的文本部分需要根据个数自动换行,因此考虑使用Wrap。预习这个地方涉及到叠加,考虑Stack实现;
- 第四行可以看作一个Row,分三块进行布局;
- 第五行可以看作一个Row,分两块布局。
每一行之间的间隔,则可以考虑用Padding或者Container来设置。
通过上面这样一步一步的分析后,基本上对大致的布局有了一个了解,最外层的控件大致选对(只要能实现的话,就是复杂度以及效率的问题),然后一步一步的拆解每一行的元素,如果有重复的或者觉得可以封装出来的部分,则进行下一步。
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'LoginPageState.dart';
class SamplePagebujuchaijie extends State<MyApp1> {
// const SamplePagebujuchaijie();
@override
Widget build(BuildContext context) {
return new Scaffold(
/// Line 1
appBar: new AppBar(
backgroundColor: Colors.white,
elevation: 0.0,
centerTitle: true,
title: new Text(
"发布成功",
style: new TextStyle(
color: Colors.black,
fontSize: 18.0,
fontWeight: FontWeight.normal),
),
leading: IconButton(
icon: new Image.asset("assets/images/nav_close.png"),
onPressed: () {
Navigator.maybePop(context);
}),
),
/// Line 2-5
body: new SamplePageContent(),
);
}
}
class SamplePageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Container(
color: Colors.white,
padding: const EdgeInsets.only(top: 42.0),
child: new Column(
children: <Widget>[
/// Line 2
new Row(
children: <Widget>[
new Container(
width: 52.0,
height: 52.0,
margin: const EdgeInsets.only(left: 16.0),
child: new CircleAvatar(
backgroundImage: new AssetImage("assets/images/avatar2.png"),
),
),
new Container(
margin: const EdgeInsets.only(left: 7.0, right: 15.0),
height: 48.0,
child: new Stack(
children: <Widget>[
new Image.asset("assets/images/publish_chat_box.png"),
new Positioned(
left: 25.0,
top: 14.0,
child: new Text(
"张老师发布了一个任务,请接收~",
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
)),
],
),
)
],
),
/// Line 3
new Container(
margin: const EdgeInsets.fromLTRB(6.0, 24.0, 6.0, 30.0),
child: new RoundInnerSquareBox(
child: new Container(
padding: const EdgeInsets.fromLTRB(24.0, 28.0, 24.0, 12.0),
width: double.infinity,
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
"Unit 1 Lesson 3 About animal",
style: new TextStyle(
fontSize: 20.0,
fontFamily: "Round",
color: Colors.white,
),
),
new Container(
margin: const EdgeInsets.only(top: 5.0, bottom: 13.0),
child: new Image.asset(
"assets/images/publish_work_line.png"),
),
new Wrap(
alignment: WrapAlignment.start,
children: <Widget>[
new WorkTotalItem(
title: "课文跟读 12",
),
new WorkTotalItem(
title: "课文跟读 12",
),
new WorkTotalItem(
title: "课文跟读 12",
),
new WorkTotalItem(
title: "课文跟读 12",
),
],
),
new Container(
margin: const EdgeInsets.only(left: 178.0),
child: new Stack(
children: <Widget>[
new Image.asset(
"assets/images/publish_work_sign.png"),
new Positioned(
left: 4.0,
top: 4.0,
child: new Text(
"预习",
style: new TextStyle(
fontSize: 14.0, color: Colors.white),
),
)
],
),
),
new Container(
alignment: Alignment.topRight,
child: new Text(
"明天12:00截止",
style: new TextStyle(
fontSize: 12.0, color: const Color(0xFFFFC1C1)),
),
),
],
),
),
),
),
/// Line 4
new LineTips(
title: new Text(
"给家长发个通知吧",
style:
new TextStyle(fontSize: 14.0, color: const Color(0xFF757085)),
),
),
/// Line 5
new Container(
margin: const EdgeInsets.only(top: 32.0),
height: 60.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new IconButton(
padding: EdgeInsets.zero,
iconSize: 60.0,
icon: new Image.asset("assets/images/share_wechat.png"),
onPressed: () {
print("share to wechat");
}),
new Container(
margin: const EdgeInsets.only(left: 32.0),
child: new IconButton(
padding: EdgeInsets.zero,
iconSize: 60.0,
icon: new Image.asset("assets/images/share_qq.png"),
onPressed: () {
print("share to qq");
}),
),
],
),
),
],
),
);
}
}
class RoundInnerSquareBox extends StatelessWidget {
static const double gap = 12.0;
RoundInnerSquareBox({
required this.child,
});
final Widget child;
@override
Widget build(BuildContext context) {
return new ClipRRect(
borderRadius: const BorderRadius.all(const Radius.circular(16.0)),
child: new Container(
color: const Color(0xFFF0D5A9),
padding: const EdgeInsets.all(gap),
child: new Container(
child: new Container(
color: const Color(0xFF3C594E),
child: child,
),
),
),
);
}
}
class LineTips extends StatelessWidget {
static const defaultMargin = const EdgeInsets.only(left: 15.0, right: 15.0);
LineTips({
required this.title,
this.margin = defaultMargin,
});
final Widget title;
final EdgeInsetsGeometry margin;
@override
Widget build(BuildContext context) {
return new Padding(
padding: defaultMargin,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Expanded(
child: new Container(
margin: const EdgeInsets.only(
right: 10.0,
),
color: const Color(0xFFD4CFE4),
height: 1.0,
),
),
title,
new Expanded(
child: new Container(
margin: const EdgeInsets.only(
left: 10.0,
),
color: const Color(0xFFD4CFE4),
height: 1.0,
),
),
],
),
);
}
}
class WorkTotalItem extends StatelessWidget {
WorkTotalItem({
required this.title,
});
final String title;
@override
Widget build(BuildContext context) {
return new Container(
padding: const EdgeInsets.all(6.0),
child: new Text(
"$title",
style: new TextStyle(
fontSize: 14.0,
color: Colors.white,
),
),
);
}
}
注明:所有dart代码层级嵌套和Android均保持一致
原文参照:
网友评论