
新东西都需要一个记忆的过程,加之 Flutter Weight 设置的东西更繁杂,所以我这里尽量全的列举出 Flutter 常用的 Weight 的所有设置和用法
官方文档:
吐槽下
Flutter 中的 Weight 异常之多,相互之间非常混乱,实现相同功能的 Weight 一般都有好几个,并且 Weight 使用起来一般都比较别扭,android 中一个 layout 能实现的,在 Flutter 里一般你都得套好多层 Weight 才行。这体现出 Flutter Weight 设计的草率、随性和经验不足,生搬硬套 web 端,但是现在 Flutter 逗正式版 1.2 了,也没法改了,大家适应吧
另外 Flutter 把 android 中大多数 view 的限制属性全部提升到 Weight 的级别,作为容器使用,这样进一步加剧了 Flutter Weight 编写的复杂读,大家先心里有个底,Flutter 注定是像 web 一样了一层 dvi 套一层 dvi
为了便于学习、记忆、查找,我把 Flutter Weight 按照功能分成几种:
- 完成布局任务的 -
布局 Weight
- 负责具体绘制的 -
内容 Weight
- 实现控制属性的 -
单容器 weight
- 设置尺寸大小的 -
大小 weight
对于内容较多的 Weight 我开单章了,介绍里面方链接,要不看起来太费劲了,剩下的那些内容少的就写在本文里了
涉及 Weight 如下:
由 Container 拓展出来的单个属性的容器 Weight (比如 padding,sizebox)都比较简单,除了需要特殊说明的,剩下的我都没列在这里
布局 Weight:
Row
Column
- 一行,一列,只能是一行或者一列,不能换行,换列,具体的请看:Flutter - Row、Column / 一行、一列Stack
- 堆叠布局,允许子 weight 相互重叠现实,相当于 Android 的 FrameLayoutIndexedStack
- 指定显示第几个子 view 的 Stack,Stack 大小占位不会因此变化Wrap
、Flow
- 都是流式布局,区别是 Flow 性能相对好一些,但是得自己写换行得代码,Wrap 使用起来最方便,具体请看:Flutter UI - Wrap、Flow 实现流式布局Flexible
、Expanded
- 权重比 Weight,一般配合 row、column 使用
实现尺寸设置的容器 Weight:
FractionallySizedBox
- 可以让其中包裹的子 view 的宽高是外层父容器宽高的倍数AspectRatio
- 设置子view宽高比例FittedBox
- 根据父容器的大小和预设模式自动缩放子viewConstrainedBox
- 约束子控件,可以设置宽高的最大最小值
单容器 Weight:
Container
- 综合修饰容器,view 的边距,背景,旋转,位移都是设置在 Container 上的,具体的请看:Flutter - ContainerTransform
- 相当于 android 的 tween 动画,作用与draw
阶段,weight 位置不会随着动画变动RotatedBox
- 作用与layout
阶段,weight 位置会随着动画变化Opacity
- 控制子 widget 透明度,在 0-1 之间变化Baseline
- 容器,子 view 文字基线对齐,一般都是配合 row 这种布局 weight 使用得Offstage
- 控制子view 显示与否
内容 Weight:
Decoration
- Flutter 版的 shape,用于绘制各种背景,边框,因为东西比较多,不合适放在这里,专门单开一章,详细请看:Flutter - DecorationChip
- 简单的小标签Divider
- 分割线
基础设置
1. Flutter 单位
Flutter 里没有单位一说了,直接写数值,比如设置 width
Widget build(BuildContext context) {
return Container(
width: 50,
color: Colors.lightBlueAccent,
child: oneRow(),
);
}
既然没有单位了,那么这个 50 表示什么,表示 50个逻辑单位,Flutter 的逻辑单位其实就是 android 的像素密度
dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
density = dm.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0 )
- density 1.0,1dp 此时 = 1px
- density 2.0,1dp 此时 = 2px
然后我们再回过头来看 Flutter 的这个逻辑密度,其实还是 dp,只不过不写 dp Z合格单位了,换汤不换药。为了匹配 android 和 ios 端,Flutter 统一把单位称为几X
,和 UI 给我们切的 IOS 那个几X
的切图的几X
是一个意思
android | ios |
---|---|
ldpi | 0.75x |
mdpi | 1.0x |
hdpi | 1.5x |
xhdpi | 2.0x |
xxhdpi | 3.0x |
xxxhdpi | 4.0x |
我想很自然的大家都会想到 Flutter 屏幕适配怎么做对吧,这个问题不再本文之列,以后研究...
2. EdgeInsets
Flutter 里面很多时候不能直接写数值的,这点让人大感意外,比如 padding、maiger 就不能直接写数字,必须借助 EdgeInsets,EdgeInsets 的初衷是为了省大家事
EdgeInsets 的使用如下:
ALT + 回车
快捷键可以自动提示,不用手打
Widget build(BuildContext context) {
// TODO: implement build
return Container(
padding: EdgeInsets.all(10),
width: 50,
color: Colors.lightBlueAccent,
child: oneRow(),
);
}
EdgeInsets 是一个类,提供了设置 LTRB 4个方向的方法:
EdgeInsets.all()
= 全方向统一值EdgeInsets.fromLTRB()
= 自定义4个方向,4个方向值必须写EdgeInsets.only(left,top,right,bottom)
= 自定义单个方向的值EdgeInsets.symmetric(vertical, horizontal)
= 自定义垂直,水平对称边距EdgeInsets.fromWindowPadding(ui.WindowPadding padding, double devicePixelRatio)
= 根据机型屏幕尺寸定义,这个我不知道啥意思
3. 颜色值设置
我不详细写 weight,大家看设置应该能明白
1. Colors 配置好的颜色,包括透明度
color: Colors.grey
Colors.black12 //这种就是带透明度的,12=12%不透明
2. 自己加透明度
数值从 100 到 900,增量为 100,数字越小,颜色越浅,数字越大,颜色越深
color:Colors.green[200],
color: Colors.green.shade200,
3. ARGB 16进制
Color.fromARGB(0xFF, 0xEE, 0x50, 0x48)
4. ARGB 10进制
Color.fromARGB(255, 240, 80, 72)
5. RGBO 16进制:最后一个参数是透明度
Color.fromRGBO(0xEE, 0x50, 0x48, 1.0)
6. RGBO 10进制
Color.fromRGBO(240, 80, 72, 1.0)
7. 直接写16进制
Color(0xFFEE5048)
注意:
- fromARGB 含义: A = Alpha or opacity, R = Red, G = Green, B = Blue
- fromRGBO 含义: R = Red, G = Green, B = Blue, O = Opacity
插段吐槽~
Flutter 的 UI 设计太过于依赖 Weight 了,什么逗靠 Weight 实现,搞得 Flutter 的 Weight 数量非常多,实现相同功能的 Weight 有好几个,学习起来很花时间,有的还要仔细思考其差异,到底那不一样...
但这就是 Flutter 的特色,瑕不掩瑜 Flutter 本身是非常 Nice,在 Ui 层面复杂一些是可以接收的,那个语言能没有不被人吐槽的地方呢~。Flutter 目前都是正式版了,以后这种 UI风格也肯定是固定的了,大家学就行了。有网友猜测 Flutter 这样设计是为了实现统一渲染
,目的是提升渲染效率,我想了好久也没想到性能上能有多少优势,也许有但是不是我能以下就理解的,还得大N多来解惑啊
日常中用的多的,也就是 Container、Padding、Center、Align、Row、Column、Stack、ListView、image、icon、text 等也十来种,大家用心学就行了,其实也没麻烦多少,代码上结构上比 android 可是方便多了,差距那是一天一地啊...
Transform tween 动画 Weight
android 中我们再 xml 里是写不了动画变换的,Flutter 中就省事了,没那么复杂了,直接就有一个操作 view 属性的 Weight:Transform
,借助 setValue,我们可实现动画效果
- 旋转 - 正数是顺时针旋转,左边翘起;负数是逆时针旋转,右边翘起
class oneContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Transform.rotate(
angle: -math.pi / 9,
child: Container(
width: 100,
height: 40,
child: Text("AAAAAA"),
alignment: Alignment.center,
color: Colors.lightBlueAccent,
),
);
}
}

- 位移
class oneContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Transform.translate(
offset: Offset(20, 20),
child: Container(
width: 100,
height: 40,
child: Text("AAAAAA"),
alignment: Alignment.center,
color: Colors.lightBlueAccent,
),
);
}
}
- 缩放
class oneContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Transform.scale(
scale: 0.9,
child: Container(
width: 100,
height: 40,
child: Text("AAAAAA"),
alignment: Alignment.center,
color: Colors.lightBlueAccent,
),
);
}
}
Transform 是应用在绘制阶段,而并不是应用在布局(layout)阶段,所以无论对子组件应用何种变化,其占用空间的大小和在屏幕上的位置都是固定不变的,因为这些是在布局阶段就确定的
相当于 android 的 tween 动画,虽然 view 的样式变了,但是位置不变,比如点击的话还是只能相应原位置,甚至有时 view 密集的话会出问题

想 view 自适应的话就得用专门的动画 Weight
了
RotatedBox
旋转变换 Weight,和 Transform 不同的是 RotatedBox应用与 layout 阶段
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
//将Transform.rotate换成RotatedBox
child: RotatedBox(
// int类型
quarterTurns: 1, //旋转90度(1/4圈)
child: Text("Hello world"),
),
),
Text("你好", style: TextStyle(color: Colors.green, fontSize: 18.0),)
],
),

Opacity
Opacity
控制透明度变化,很简单的

class EE extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Opacity(
opacity: 0.2,
child: Container(
width: 350,
height: 350,
alignment: Alignment.center,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/icons/icon_3.png"),
fit: BoxFit.cover),
borderRadius: BorderRadius.circular(1000)),
child: Text(
"AAAAAAAAAA",
style: TextStyle(
fontSize: 30,
color: Colors.lightGreenAccent,
),
),
),
);
}
}
Opacity
的特点,即便完全透明不可见了,依然不会失去位置:

FittedBox
FittedBox 没别的属性,就是一个 fit 缩放模式,和 android imageview scaleType 异曲同工啊,不过FittedBox 的应用场景我也只知道啊
new Container(
color: Colors.amberAccent,
width: 300.0,
height: 300.0,
child: new FittedBox(
fit: BoxFit.contain,
alignment: Alignment.topLeft,
child: new Container(
color: Colors.red,
child: new Text("FittedBox"),
),
),
)

这里面也就有限的2-3个实用,none
是没有缩放,contain
能保持子view 自身比例的前提下缩放,scaleDown
在父容器不够大时缩放子view也是保持子view比例的。有一点要说一下,文字大小也是能同时缩放的,这点很不错的
ConstrainedBox
可以设置宽高的最大值,最小值
return new ConstrainedBox(
constraints: BoxConstraints(
minHeight: 80,
maxHeight: 100
),
child: Container(
width: 100,
color: Colors.red,
),
);
AspectRatio
AspectRatio 可以设置子 view 的宽高比例,我们有时使用图片时是有设置宽高比例的需求的,另外摄像笔视频这块也是有需求的
没别的属性,就 aspectRatio 这一个
class AA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 200.0,
child: new AspectRatio(
aspectRatio: 3 / 4,
child: new Container(
color: Colors.red,
),
),
);
}
}
注意这里的比例是:宽:长
,比如例子里面 aspectRatio: 3 / 4,那么宽占3份,长占4份,长比宽多,例子里提供了具体的 width值,那么就会按照这个比例就去计算 height 的值
别跟手机屏幕比例搞混了,手机屏幕比例是:长:宽
,比如 16:9。但是把图片的分辨率又是:宽*长
表示的,所以大家一定要搞清楚,要不就容易出问题
AspectRatio 的宽高比其实和 ConstraintLayout 约束布局的 view 宽高比一样,搞不懂的可以去参考那个

Stack 堆叠布局
Stack 和 Android 的 FrameLayout 很像,并且另外有些扩展。Flutter 里面只要有 weight 重叠的布局基本都要用 Stack,当然用其他更多的 weight 组合也能实现 Stack 的效果,但是想要最简单的实现 weigjt 的堆叠还是要用 Stack 的
Stack 属性如下:
Stack({
Key key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
alignment
- 是 Stack 里面所有子 view 的对齐方式,只有被 Positioned 包裹的子 view 除外,一般多用在 Stack 只有 2个子 view 时,超过2个子 view 就要考虑 Positioned 了textDirection
- 子view 的排列顺序,TextDirection.ltr
- 从左往右,TextDirection.rtl
-从右往左overflow
- 超出部分的处理方式,有Overflow.clip
和Overflow.visible
fit
- Stack 大小的判定,StackFit.loose
- Stack 大小取子view 的最大值;StackFit.expand
- Stack 大小占父控件的最大值;StackFit.passthrough
- 这个不知道啥意思。Positioned 包裹的子 view 不再考虑之列Positioned
- 用于包裹子 view,让该子 view 脱离 Stack alignment 属性的约束,内部使用具体是指指定子view 到 Stack 4个边距的距离
class AA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional(0, 0),
fit: StackFit.loose,
children: <Widget>[
Container(
color: Colors.blueAccent,
width: 200,
height: 200,
),
Container(
color: Colors.red,
child: Text("AAAAAA"),
),
Positioned(
left: 20,
bottom: 20,
child: Container(
color: Colors.yellowAccent,
child: Text("BBBBBB"),
),
),
],
);
}
}

IndexedStack
和 stack 一样,区别是可以控制显示 stack 里面的第几个子 view,就是通过index
这个属性操作的,特点是虽然只能显示一个子 view,但是整个 stack 的大小占位并不会因为 index 的变化而改变
class AA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
height: 50,
color: Colors.deepOrange,
),
IndexedStack(
alignment: AlignmentDirectional(0, 0),
index: 0,
children: <Widget>[
Container(
color: Colors.blueAccent,
width: 200,
height: 200,
),
Container(
color: Colors.red,
child: Text("AAAAAA"),
),
Positioned(
left: 20,
bottom: 20,
child: Container(
color: Colors.yellowAccent,
child: Text("BBBBBB"),
),
),
],
),
],
);
}
}


Baseline 文字对齐
Baseline 作为一个容器存在,重点在于向 row 这样得布局 weight 里对齐子 view,Baseline控件布局行为分为两种情况:
- 如果child有baseline,则根据child的baseline属性,调整child的位置
- 如果child没有baseline,则根据child的bottom,来调整child的位置
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Text(
'TjTjTj',
style: new TextStyle(
fontSize: 20.0,
textBaseline: TextBaseline.alphabetic,
),
),
),
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Container(
width: 30.0,
height: 30.0,
color: Colors.red,
),
),
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Text(
'RyRyRy',
style: new TextStyle(
fontSize: 35.0,
textBaseline: TextBaseline.alphabetic,
),
),
),
],
)

FractionallySizedBox 宽高倍数容器
FractionallySizedBox 可以让期中包裹得子 view 得宽高是外层父容器宽高得倍数,我不用知道有什么常用会用到这个,但是要小心,一旦外层父容器没有使用具体是指指定宽高大小,使用 FractionallySizedBox 得话很容易就占满全屏了。另外 FractionallySizedBox 允许子 view 超出父容器
FractionallySizedBox 依靠其中得 widthFactor、heightFactor 设置宽高比例倍数,当设置 null 的时候会使用其中包裹得子 view 得具体宽高值,若是子 view 提供不了宽高值,那么侯就会占满父容器
class BB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topLeft,
width: 200,
height: 200,
color: Colors.blueAccent,
child: FractionallySizedBox(
widthFactor: 1.5,
heightFactor: 0.5,
child: Container(
width: 100,
height: 100,
color: Colors.yellow,
),
),
);
}
}



Offstage
Offstage 可以控制 view 的显示与否,view 不显示的话是不占位的,但是若是动画的话,view 在不显示的同时要手动停止动画
class CC extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return CCState();
}
}
class CCState extends State<CC> {
var isCanShow = true;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: EdgeInsets.all(10),
color: Colors.red,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Offstage(
offstage: !isCanShow,
child: Container(
width: 100,
height: 50,
color: Colors.lightBlueAccent,
),
),
Container(
width: 100,
height: 50,
color: Colors.yellowAccent,
child: RaisedButton(
child: Text("AAAAAA"),
onPressed: () {
setState(() {
isCanShow = !isCanShow;
});
}),
),
],
),
),
);
}
}

OverflowBox
子 view 可以超出父控件
const OverflowBox({
Key key,
this.alignment = Alignment.center,
this.minWidth,
this.maxWidth,
this.minHeight,
this.maxHeight,
Widget child,
})
OverflowBox 其实并不像想象的那么好,OverflowBox 要是不设置最大,最小宽高,仅仅靠子 view 确定宽高的话是没有超过父控件的效果的,并且子 view 超过父控件还不能随心所遇,必须是子 view 宽高有一个比父控件大才行
- 子 view 的宽高比 OverflowBox 限定的大的话,子 view 的宽高受OverflowBox 限定限制
class DD extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topLeft,
width: 200,
height: 200,
color: Colors.redAccent,
child: OverflowBox(
// maxWidth: 300,
alignment: Alignment.topLeft,
child: Container(
width: 300,
height: 100,
color: Colors.blueAccent,
),
),
);
}
}


Flexible、Expanded
Flexible
、Expanded
权重比 Weight,一般配合 row、column 使用,他俩的区别是 Expanded
默认会占据父控件的全部控件,Flexible
不会,并且 Expanded
是 Flexible
的子类,一般 Expanded
使用的最多,他俩通过设置flex
来设置权重比,和 android 的线性布局的权重比一样

class FF extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
Expanded(
child: new Container(
alignment: Alignment(0, 0),
color: Colors.redAccent,
height: 80,
child: Text(
"AAAAA",
style: TextStyle(fontSize: 30),
),
),
flex: 2,
),
Expanded(
child: new Container(
alignment: Alignment(0, 0),
color: Colors.blueAccent,
height: 80,
child: Text(
"BBBBB",
style: TextStyle(fontSize: 30),
),
),
flex: 3,
),
],
);
}
}
Chip
Chip
- 简单的小标签,就是文本可以添加左边右边图标,参考理解 android 的 textview
属性如下:
Chip({
this.avatar,//左侧小图标
this.label,//中间文版
this.labelStyle,
this.labelPadding,
this.deleteIcon//删除图标
this.onDeleted//删除回调,为空时不显示删除图标,
this.deleteIconColor//删除图标的颜色
this.shape//形状
this.backgroundColor//背景颜色
this.padding
})

class EE extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 200,
alignment: Alignment.center,
child: Chip(
avatar: Icon(Icons.access_alarm),
padding: EdgeInsets.all(10),
label: Text(
"AAA",
style: TextStyle(fontSize: 20),
),
backgroundColor: Colors.blueAccent,
deleteIcon: Icon(Icons.clear),
deleteIconColor: Colors.white,
elevation: 10,
onDeleted: () {
print("AAA");
}),
);
}
}
注意:不设置删除事件,右侧图标不会显示的,因为没用呀~
Chip
是典型的应用就是配合 warp
了

Divider
Divider
大家在熟悉不过啦,分割线嘛
属性如下:
const Divider({
this.height, //距 top 的距离
this.indent, //距 left 的距离
this.endIndent, //距 right 的距离
this.color,
})

注意:若是居中布局的话,height
是不起作用的
class EE extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Divider(
endIndent: 100,
indent: 100,
height: 100,
color: Colors.blueAccent,
);
}
}