Flutter 控件介绍
Flutter控件 就是 Widget 就是 Flutter 封装好的 UI 组件
通过组合这些基础组件 实现交互界面
Flutter界面UI 都是 Widget
有些组件可以包含其他组件起到约束的作用
有些组件只是负责自己的显示
所有的控件都有属性 如 width heigth Alignment 等
Flutter常用的组件
文本 Text 编辑框
TextFiled(相当于 Android 中 EditText)
图片 Image(加载网络图片 本地图片 本地系统文件等)
按钮 Button(分为 RaisedButton FlatButton OutlineButton 和 IconButton)
容器 Container
列布局 Column
行布局 Row
线性布局 List 等
Flutter的 Widget 分为两类
一种无状态组件 StatelessWidget 只用来展示信息 不能有动作(用户交互);
一种是有状态的 StatefulWidget 这种 Widget 通过改变状态使UI 发生变化 包含用户交互
有状态的组件 通过 setState 刷新界面
Flutter组件 Widget 有不同的分类方法
基础类 Widget 如 Text
容器类 Widget 如 Container
布局类 Widget 如Row,Column
滚动布局 Widght 如 ListView
基础类 Widget
常用的且最小的单位
文本
按钮
图片
单选框 复选框
文本
Text 构造函数
const Text( this.data, { Key key, this.style, this.strutStyle, this.textAlign, this.textDirection, this.locale, this.softWrap, this.overflow, this.textScaleFactor, this.maxLines, this.semanticsLabel, }) : assert( data != null, 'A non-null String must be provided to a Text widget.', ), textSpan = null, super(key: key);
常用的属性有 style 指定样式 textAlign 对齐方式 textDirection 文字方向 maxLines 行数
Center( child: Container( color: Colors.blue, width: 720, child: Text( 'Hello World', style: new TextStyle(color: Colors.red,fontSize: 30), textAlign: TextAlign.start, textDirection: TextDirection.ltr, ), ), ),
对齐方式效果
外面加上了一个 Container
指定颜色
Container 指定宽度 否则 Container 的宽度是自适应的 子元素只会居中显示 没有对齐效果
TextDirection.ltr :表示从左到右布局 同样的 rtl 表示从右向左布局
textAlign.start : 表示对齐从 "头" 对齐 也就是 ltr 时 左对齐 rtl 时右对齐
其他属性当有需要的时候查看文档设置即可
按钮
Flutter 中的按钮
RaisedButton
FlatButton
OutlineButton
IconButton
FloatActionButton
RaisedButton 突起的按钮 RaisedButton 通过阴影可以达到立体的效果
const RaisedButton({ Key key, @required VoidCallback onPressed, ValueChanged<bool> onHighlightChanged, ButtonTextTheme textTheme, Color textColor, Color disabledTextColor, Color color, Color disabledColor, Color highlightColor, Color splashColor, Brightness colorBrightness, double elevation, double highlightElevation, double disabledElevation, EdgeInsetsGeometry padding, ShapeBorder shape, Clip clipBehavior = Clip.none, MaterialTapTargetSize materialTapTargetSize, Duration animationDuration, Widget child, }) : assert(elevation == null || elevation >= 0.0), assert(highlightElevation == null || highlightElevation >= 0.0), assert(disabledElevation == null || disabledElevation >= 0.0), super( key: key, onPressed: onPressed, onHighlightChanged: onHighlightChanged, textTheme: textTheme, textColor: textColor, disabledTextColor: disabledTextColor, color: color, disabledColor: disabledColor, highlightColor: highlightColor, splashColor: splashColor, colorBrightness: colorBrightness, elevation: elevation, highlightElevation: highlightElevation, disabledElevation: disabledElevation, padding: padding, shape: shape, clipBehavior: clipBehavior, materialTapTargetSize: materialTapTargetSize, animationDuration: animationDuration, child: child, );
RaisedButton大部分属性都是看到名字就知道什么意思的 如设置颜色相关的属性
textColor 文字颜色
disabledTextColor 按钮禁用文字颜色
highlightColor 高亮颜色 也就是按下的背景色
splashColor 水波纹颜色
colorBrightness 设置主题
其他的属性
elevation: 高度
child:传递 widget 设置子控件的 如 Text
padding:设置填充间距
shape:设置形状
onPressed: 设置按下时的回掉函数
不常用的属性 需要的时候查看文档即可
RaisedButton( child: Text("RaisedButton",style: new TextStyle(fontSize: 30),), textColor: Colors.blue, disabledColor: Colors.grey, highlightColor: Colors.grey, splashColor: Colors.red, colorBrightness: Brightness.light, elevation: 5, highlightElevation: 2, disabledElevation: 2, padding: EdgeInsets.all(2), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), side: BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2)), clipBehavior: Clip.antiAlias, onPressed: () => {print("RaisedButton Pressed")}, ),
FlatButton
扁平按钮 默认背景透明并不带阴影 只有按下之后才会有背景色
const FlatButton({ Key key, @required VoidCallback onPressed, ValueChanged<bool> onHighlightChanged, ButtonTextTheme textTheme, Color textColor, Color disabledTextColor, Color color, Color disabledColor, Color highlightColor, Color splashColor, Brightness colorBrightness, EdgeInsetsGeometry padding, ShapeBorder shape, Clip clipBehavior = Clip.none, MaterialTapTargetSize materialTapTargetSize, @required Widget child, }) : super( key: key, onPressed: onPressed, onHighlightChanged: onHighlightChanged, textTheme: textTheme, textColor: textColor, disabledTextColor: disabledTextColor, color: color, disabledColor: disabledColor, highlightColor: highlightColor, splashColor: splashColor, colorBrightness: colorBrightness, padding: padding, shape: shape, clipBehavior: clipBehavior, materialTapTargetSize: materialTapTargetSize, child: child, );
相比较 RaisedButton,FlatButton 不再有 elevation 等属性
new FlatButton( child: Text("FlatButton",style: new TextStyle(fontSize: 30),), textColor: Colors.blue, disabledColor: Colors.grey, highlightColor: Colors.grey, splashColor: Colors.red, colorBrightness: Brightness.light, padding: EdgeInsets.all(2), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), side: BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2)), clipBehavior: Clip.antiAlias, onPressed: () => {print("FlatButton Pressed")}, ),OutlineButton 按钮默认有一个边框 不带阴影且背景透明
按下后 边框颜色会变亮 同时出现背景和阴影(较弱)
const OutlineButton({ Key key, @required VoidCallback onPressed, ButtonTextTheme textTheme, Color textColor, Color disabledTextColor, Color color, Color highlightColor, Color splashColor, double highlightElevation, this.borderSide, this.disabledBorderColor, this.highlightedBorderColor, EdgeInsetsGeometry padding, ShapeBorder shape, Clip clipBehavior = Clip.none, Widget child, }) : assert(highlightElevation == null || highlightElevation >= 0.0), super( key: key, onPressed: onPressed, textTheme: textTheme, textColor: textColor, disabledTextColor: disabledTextColor, color: color, highlightColor: highlightColor, splashColor: splashColor, highlightElevation: highlightElevation, padding: padding, shape: shape, clipBehavior: clipBehavior, child: child, );
new OutlineButton( child: Text("OutlineButton",style: new TextStyle(fontSize: 30),), textColor: Colors.blue, highlightColor: Colors.grey, splashColor: Colors.red, highlightElevation: 5, borderSide: BorderSide(color:Colors.amberAccent,width:2.0,style:BorderStyle.solid), padding: EdgeInsets.all(2), onPressed: () => {print("OutlineButton Pressed")}, ),
IconButton
包含 Icon 图标的按钮 没有文字 点击会有水波纹效果
const IconButton({ Key key, this.iconSize = 24.0, this.padding = const EdgeInsets.all(8.0), this.alignment = Alignment.center, @required this.icon, this.color, this.highlightColor, this.splashColor, this.disabledColor, @required this.onPressed, this.tooltip, }) : assert(iconSize != null), assert(padding != null), assert(alignment != null), assert(icon != null), super(key: key);
new IconButton( icon: new Icon(Icons.add_a_photo), color: Colors.amber, highlightColor: Colors.red, splashColor: Colors.blue, iconSize: 28, onPressed: () => {print("IconButton Pressed")}, ),
FloatActionButton
浮动按钮 和 Android 里面的 Fab 是一样的 通常显示到屏幕右下角
FloatAction 和 Scaffold 配合使用
FAB 也可以当作一个普通的按钮来使用
const FloatingActionButton({ Key key, this.child, this.tooltip, this.foregroundColor, this.backgroundColor, this.heroTag = const _DefaultHeroTag(), this.elevation, this.highlightElevation, this.disabledElevation, @required this.onPressed, this.mini = false, this.shape, this.clipBehavior = Clip.none, this.materialTapTargetSize, this.isExtended = false, }) : assert(elevation == null || elevation >= 0.0), assert(highlightElevation == null || highlightElevation >= 0.0), assert(disabledElevation == null || disabledElevation >= 0.0), assert(mini != null), assert(isExtended != null), _sizeConstraints = mini ? _kMiniSizeConstraints : _kSizeConstraints, super(key: key);
需要注意的是 Fab 中有一个 heroTag 属性
这个可以理解为 Fab 的一个标识
不同的 Fab 这个值应该是不一样的
否则都是默认值的话 会报 hero 冲突的错误
child: Container( width: 200, child: FloatingActionButton( heroTag: "My_Fab", backgroundColor: Colors.blue, elevation:4, child: Text("拍照",style: new TextStyle(color: Colors.white,fontSize: 20)), shape:RoundedRectangleBorder(borderRadius: BorderRadius.circular(50.0)), onPressed: () { print("Fab pressed"); }, ) ),
效果:

图片
Flutter 图片控件 无论是从本地加载 网络加载 还是文件系统加载 其使用方式 大致一样
Flutter 通过Image 加载并显示图片 Image的数据源可以是asset 文件 内存以及网络
ImageProvider 是一个抽象类 主要定义了图片数据获取的接口load()
从不同的数据源获取图片需要实现不同的ImageProvider
如AssetImage是实现了从Asset中加载图片的ImageProvider
而NetworkImage实现了从网络加载图片的ImageProvider
加载 图片 用到的 widget 是 Image 无论是本地图片 还是网络图片
const Image({ Key key, @required this.image, this.semanticLabel, this.excludeFromSemantics = false, this.width, this.height, this.color, this.colorBlendMode, this.fit, this.alignment = Alignment.center, this.repeat = ImageRepeat.noRepeat, this.centerSlice, this.matchTextDirection = false, this.gaplessPlayback = false, this.filterQuality = FilterQuality.low, }) : assert(image != null), assert(alignment != null), assert(repeat != null), assert(filterQuality != null), assert(matchTextDirection != null), super(key: key);
width 图片的宽
height 图片高度
color 图片的混合色值
colorBlendMode 混合模式
fit 缩放模式
alignment
repeat 重复方式
从 assets 下加载图片
在主工程目录下新建一个 images 文件夹 并将资源文件拷贝到这里
在pubspec.yaml中添加资源依赖 注意缩进格式
assets: - images/flutter.png
new Center( child:Image( image: AssetImage("images/flutter.png"), ), ),

body: new Center( child:Image.asset("images/flutter.png") ),
从 网络加载图片
new Center( child:Image( image: NetworkImage("http://img3.imgtn.bdimg.com/it/u=1303636189,2885099528&fm=26&gp=0.jpg"), ) ),
同样的 快捷用法:
Image.network( "http://img3.imgtn.bdimg.com/it/u=1303636189,2885099528&fm=26&gp=0.jpg", )
从文件系统加载图片
Image 还可以根据本地图片存储的路径 来加载图片:
new Center( child:Image( image: FileImage(new File("imgPath")), ) ),
快捷用法
new Center( child:Image.file(new File("filePath")) ),
由于缓存的原因 当图片路径和图片名称没有发生变化的时候 这样即使图片内容发生了变化(如 图片覆盖) flutter 也不会更新界面
因为 Image.file 实际上会将 image 设置为 FileImage 这个 ImageProvider
FileImage 只判断了文件路径和缩放比例,因此文件路径和缩放比例不变时 图片不会重新加载
@override bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; final FileImage typedOther = other; return file?.path == typedOther.file?.path && scale == typedOther.scale; }
因此要解决这个问题 就要重写这个方法
class FileImageEx extends FileImage { int fileSize; FileImageEx(File file, { double scale = 1.0 }) : assert(file != null), assert(scale != null), super(file, scale: scale) { fileSize = file.lengthSync(); } @override bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; final FileImageEx typedOther = other; return file?.path == typedOther.file?.path && scale == typedOther.scale && fileSize == typedOther.fileSize; }}
Image(image: FileImageEx(File("imgPath")));
单选复选
Flutter 单选是 Switch 复选框是 CheckBox 组件
import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); }}class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> { bool _switchSelected=true; //switch 状态 bool _checkboxSelected=true;//CheckBox 状态 String showText = ""; //显示的内容 @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: new Column( children: <Widget>[ new Text(showText), new Switch(value: _switchSelected, onChanged:(value){ _switchSelected = value; if(value){ showText = "开启"; }else{ showText ="关闭"; } setState(() { }); }), Checkbox( value: _checkboxSelected, activeColor: Colors.red, //选中时的颜色 onChanged:(value){ _checkboxSelected=value; if(value){ showText = "选中"; }else{ showText ="取消选择"; } setState(() { }); } , ) ], ), ), ); }}
容器类 Widget
可以容其他控件
通过对子空间的约束和修饰达到附加的 ui 显示效果
常用的容器类 Widget
Padding
ConstrainedBox
DecoratedBox
Transform
RotatedBox
Container
Padding 是设置子节点边距的
const Padding({ Key key, @required this.padding, Widget child, }) : assert(padding != null), super(key: key, child: child); /// The amount of space by which to inset the child. final EdgeInsetsGeometry padding;
padding 是 EdgeInsetsGeometry 类型的 可以设置上下左右边距
实际使用都是用 EgeInsetsGeometry 的子类 EdgeInsets 来设置
EdgeInsets 提供了四个方法来提供 快捷的设置边距
fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的补白
all(double value) : 所有方向均使用相同数值的补白
only({left, top, right ,bottom }):可以设置具体某个方向的补白(可以同时指定多个方向)
symmetric({ vertical, horizontal }):用于设置对称方向的补白 vertical指top和bottom horizontal指left和right
new Center( child: new Column( children: <Widget>[ Column( //显式指定对齐方式为左对齐 排除对齐干扰 crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding(//左边添加8像素补白 padding: const EdgeInsets.only(left: 8.0), child: Text(" only "), ), Padding( //上下各添加8像素补白 padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text("symmetric "), ), Padding( // 分别指定四个方向的补白 padding: const EdgeInsets.fromLTRB(20.0,.0,20.0,20.0), child: Text(" fromLTRB "), ) ], ), ], ), )
ConstrainedBox
用于对子widget添加额外的约束
ConstrainedBox({ Key key, @required this.constraints, Widget child, }) : assert(constraints != null), assert(constraints.debugAssertIsValid()), super(key: key, child: child); /// The additional constraints to impose on the child. final BoxConstraints constraints;
constraints 也就是约束条件 是一个 BoxConstraints 类型的参数:
const BoxConstraints({ this.minWidth = 0.0, this.maxWidth = double.infinity, this.minHeight = 0.0, this.maxHeight = double.infinity, });
可以看到 BoxConstraints 可以指定宽高的最小最大值
new Center( child: ConstrainedBox( constraints: BoxConstraints( minWidth: double.infinity, //宽度尽可能大 minHeight: 50.0 //最小高度为50像素 ), child: Container( height: 5.0, child: DecoratedBox( decoration: BoxDecoration(color: Colors.red), ), ), ), ),
SizedBox
只是ConstrainedBox的一个定制 用于给子widget指定固定的宽高 如:
SizedBox( width: 80.0, height: 80.0, child: Container( height: 5.0, child: DecoratedBox( decoration: BoxDecoration(color: Colors.red), ), ), )
DecoratedBox
其子widget绘制前(或后)绘制一个装饰Decoration(如背景 边框 渐变等)
const DecoratedBox({ Key key, @required this.decoration, this.position = DecorationPosition.background, Widget child, }) : assert(decoration != null), assert(position != null), super(key: key, child: child); /// What decoration to paint. /// /// Commonly a [BoxDecoration]. final Decoration decoration; /// Whether to paint the box decoration behind or in front of the child. final DecorationPosition position;
decoration:代表将要绘制的装饰 它类型为Decoration Decoration是一个抽象类 它定义了一个接口 createBoxPainter() 子类的主要职责是需要通过实现它来创建一个画笔 该画笔用于绘制装饰
position:此属性决定在哪里绘制Decoration 它接收DecorationPosition的枚举类型 该枚举类两个值:
background:在子widget之后绘制 即背景装饰
foreground:在子widget之上绘制 即前景
对于 decoration 通常都使用 Decoration 的子类 BoxDecoration
BoxDecoration({ Color color, //颜色 DecorationImage image,//图片 BoxBorder border, //边框 BorderRadiusGeometry borderRadius, //圆角 List<BoxShadow> boxShadow, //阴影,可以指定多个 Gradient gradient, //渐变 BlendMode backgroundBlendMode, //背景混合模式 BoxShape shape = BoxShape.rectangle, //形状})
DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient(colors:[Colors.blue,Colors.red]), //背景渐变 borderRadius: BorderRadius.circular(3.0), //3像素圆角 boxShadow: [ //阴影 BoxShadow( color:Colors.black54, offset: Offset(2.0,2.0), blurRadius: 4.0 ) ] ), child: Padding(padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0), child: Text("拍照", style: TextStyle(color: Colors.white),), ) )

Transform 变换操作 在 Widget 绘制时 指定一个变换效果 如平移旋转等
平移
translate,Transform.translate接收一个offset参数 可以在绘制时沿x y轴对子widget平移指定的距离
DecoratedBox( decoration:BoxDecoration(color: Colors.red), //默认原点为左上角 左移20像素 向上平移5像素 child: Transform.translate(offset: Offset(-20.0, -5.0), child: Text("Hello world"), ), )
旋转
Transform.rotate可以对子widget进行旋转变换 如:
DecoratedBox( decoration:BoxDecoration(color: Colors.red), child: Transform.rotate( //旋转90度 angle:math.pi/2 , child: Text("Hello world"), ), )
用到了 math.pi 需要导入包:
import 'dart:math' as math;
缩放scale:
DecoratedBox( decoration:BoxDecoration(color: Colors.red), child: Transform.scale( scale: 1.5, //放大到1.5倍 child: Text("Hello world") ) ),
RotatedBox (布局阶段)
Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ DecoratedBox( decoration: BoxDecoration(color: Colors.red), //将Transform.rotate换成RotatedBox child: /* Transform.rotate(//旋转90度 angle:math.pi/2 , child:RaisedButton(child : Text("Hello world"),onPressed: (){print(" rotate button");}) ),*/ RotatedBox( quarterTurns: 1, //旋转90度(1/4圈) child:RaisedButton(child : Text("Hello world"),onPressed: (){print("button");}) ), ), Text("你好", style: TextStyle(color: Colors.green, fontSize: 18.0),) ], ),

Rotated 旋转之后 其修饰背景也跟着旋转了
而使用 Transform.rotate 只是将这个控件进行了旋转 其背景是不会跟着旋转的
Container
是DecoratedBox ConstrainedBox Transform Padding Align等widget的一个组合widget
只需通过一个Container可以实现同时需要装饰 变换 限制的场景
Container({ this.alignment, this.padding, //容器内补白 属于decoration的装饰范围 Color color, // 背景色 Decoration decoration, // 背景装饰 Decoration foregroundDecoration, //前景装饰 double width,//容器的宽度 double height, //容器的高度 BoxConstraints constraints, //容器大小的限制条件 this.margin,//容器外补白 不属于decoration的装饰范围 this.transform, //变换 this.child, })
常用padding color width height margin属性
Container( margin: EdgeInsets.only(top: 50.0, left: 120.0), //容器外补白 constraints: BoxConstraints.tightFor(width: 200.0, height: 200.0), //卡片大小 decoration: BoxDecoration( //背景装饰 gradient: RadialGradient( //背景径向渐变 colors: [Colors.blue,Colors.red], center: Alignment.topLeft, radius: 2 ), boxShadow: [ //卡片阴影 BoxShadow( color: Colors.black54, offset: Offset(2.0, 2.0), blurRadius: 4.0 ) ] ), transform: Matrix4.rotationZ(0.5), //卡片倾斜变换 alignment: Alignment.center, //卡片内文字居中 child: Text(DateTime.now().toString(), style: TextStyle(color: Colors.white, fontSize: 40.0), ), ),

布局类 Widget
布局类 Widget 可以对比 Android 里面的布局 如线性布局和相对布局等
在 Flutter 里 布局类 Widget 主要有:
线性布局 Row Column
弹性布局 Flex
流式布局 Wrap
层叠布局 Stack Positioned
线性布局 Row,Column
Flutter 中 Row 表示行布局 Column 表示列布局
Row({ Key key, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, MainAxisSize mainAxisSize = MainAxisSize.max, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, TextDirection textDirection, VerticalDirection verticalDirection = VerticalDirection.down, TextBaseline textBaseline, List<Widget> children = const <Widget>[], }) : super( children: children, key: key, direction: Axis.horizontal, mainAxisAlignment: mainAxisAlignment, mainAxisSize: mainAxisSize, crossAxisAlignment: crossAxisAlignment, textDirection: textDirection, verticalDirection: verticalDirection, textBaseline: textBaseline, );
MainAxisAlignment 与 CrossAxisAlignment 分别代表主轴对齐与纵轴对齐
对于 Row 来说 主轴就是水平的轴 纵轴就是垂直的轴
对于 Column 来说 主轴就是垂直的 纵轴就是水平的
Row 和 Column 的属性是一样的 统一说明一下:
textDirection: 水平方向文字顺序
MainAxisSize : Row 在水平方向占用的空间大小 (MainAxisSize.max: 最大 MainAxisSize.min 最小)
MainAxisAlignment: 子 widget 水平方向对齐方式 左对齐 右对齐 还是居中对齐
VerticalDirection: 对齐方式 VerticalDirection.down表示 下对齐
CrossAxisAlignment:表示子Widgets在纵轴方向的对齐方式 VerticalDirection.down时crossAxisAlignment.start指顶部对齐 verticalDirection值为VerticalDirection.up时 crossAxisAlignment.start指底部对齐;
children :子Widgets数组
new Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text("text 1",style: TextStyle(color: Colors.blue),), Text("text 2",style: TextStyle(color:Colors.red),), Text("text 3",style: TextStyle(color: Colors.orange),), ], ), ),
mainAxisAlignment: MainAxisAlignment.center,就表示居中对齐
mainAxisAlignment: MainAxisAlignment.start 表示左对齐
mainAxisAlignment: MainAxisAlignment.end 表示右对齐
new Center( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ RaisedButton(child: new Text("One"),onPressed: (){},), RaisedButton(child: new Text("Two"),onPressed: (){},), RaisedButton(child: new Text("Three"),onPressed: (){},), ], ), ),

弹性布局 Flex
Flex 参数和 Column 与 Row 基本相同 其他参数:
direction, //弹性布局的方向, Row默认为水平方向 Column默认为垂直方向
Flex 中一般包含 Expanded 来进行比例设置
new Center( child: Flex( direction: Axis.vertical, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton(child: new Text("One"),onPressed: (){},), RaisedButton(child: new Text("Two"),onPressed: (){},), RaisedButton(child: new Text("Three"),onPressed: (){},), ], ), ),

使用 Expanded 设置比例
new Center( child: Flex( direction: Axis.vertical, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Expanded( child: RaisedButton(child: new Text("One"), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), side: BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2)), onPressed: (){},), flex: 1, ), Expanded( child: RaisedButton(child: new Text("Two"), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), side: BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2)), onPressed: (){},), flex: 1, ), Expanded( child: RaisedButton(child: new Text("Three"), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), side: BorderSide(color: Color(0xFFFFFFFF), style: BorderStyle.solid, width: 2)), onPressed: (){},), flex: 1, ), ], ), ),

流式布局 Wrap
Wrap: 可以看做是可以换行的 row
spacing:主轴方向子widget的间距
runSpacing:纵轴方向的间距
runAlignment:纵轴方向的对齐方式
new Center( child:Wrap( spacing: 8.0, // 主轴(水平)方向间距 runSpacing: 4.0, // 纵轴(垂直)方向间距 alignment: WrapAlignment.center, //沿主轴方向居中 children: <Widget>[ new Chip( avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('A')), label: new Text('Hamilton'), ), new Chip( avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('M')), label: new Text('Lafayette'), ), new Chip( avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('H')), label: new Text('Mulligan'), ), new Chip( avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('J')), label: new Text('Laurens'), ), ], ), ),

层叠布局 Stack,Positioned
Flutter中使用Stack和Positioned来实现绝对定位
Stack允许子widget堆叠 而Positioned可以给子widget定位(根据Stack的四个角)
new Center( child:new Container( color: Colors.greenAccent, width: 250, height: 250, child: Stack( alignment: Alignment.center, children: <Widget>[ new Text("I am center"), Positioned( top: 10, right: 10, child:Image.asset("images/flutter.png",width: 100,fit: BoxFit.fitWidth,) , ), ], ), ) ),
这里 Positioned 通过设置上边距和右边距来设置图片显示到右上角

Align 一般都是当做控件的属性来用
设置child的对齐方式
例如居中 居左居右等 并根据child尺寸调节自身尺寸
继承关系
new Align( alignment: Alignment.center, widthFactor: 2.0, heightFactor: 2.0, child: new Text("Align"), )
设置一个宽高为child两倍区域的Align 其child处在正中间
FittedBox
主要做了两件事情 缩放(Scale)以及位置调整(Position)
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"), ), ), ),
AspectRatio的作用是调整child到设置的宽高比
new Container( height: 200.0, child: new AspectRatio( aspectRatio: 2, child: new Container( color: Colors.red, ), ), ),Baseline
主要用来文字排版
Baseline控件布局行为分为两种情况:
如果child有baseline 则根据child的baseline属性 调整child的位置;
如果child没有baseline 则根据child的bottom 来调整child的位置
new Center( 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
控件会根据现有空间 来调整child的尺寸 所以说child就算设置了具体的尺寸数值 也不起作用
布局行为:
当设置了具体的宽高因子 具体的宽高则根据现有空间宽高 * 因子 有可能会超出父控件的范围 当宽高因子大于1的时候;
当没有设置宽高因子 则填满可用区域;
new Container( color: Colors.blue, height: 150.0, width: 150.0, padding: const EdgeInsets.all(10.0), child: new FractionallySizedBox( alignment: Alignment.topLeft, widthFactor: 1.5, heightFactor: 1.5, child: new Container( color: Colors.red, ), ), ),

LimitedBox是将child限制在其设定的最大宽高.
Row( children: <Widget>[ Container( color: Colors.red, width: 100.0, ), LimitedBox( maxWidth: 150.0, child: Container( color: Colors.blue, width: 250.0, ), ), ], ),

Offstage
通过一个参数决定一个控件是否显示
布局行为:
Offstage的布局行为完全取决于其offstage参数
当offstage为true 当前控件不会被绘制在屏幕上 不会响应点击事件 也不会占用空间;
当offstage为false 当前控件则跟平常用的控件一样渲染绘制;
Column( children: <Widget>[ new Offstage( offstage: offstage, child: Container(color: Colors.blue, height: 100.0), ), new CupertinoButton( child: Text("点击切换显示"), onPressed: () { setState(() { offstage = !offstage; }); }, ), ], )
OverflowBox 允许child超出parent的范围显示
布局行为
当OverflowBox的最大尺寸大于child的时候 child可以完整显示 当其小于child的时候 则以最大尺寸为基准 当然 这个尺寸都是可以突破父节点的
最后加上对齐方式 完成布局
Container( color: Colors.green, width: 200.0, height: 200.0, padding: const EdgeInsets.all(5.0), child: OverflowBox( alignment: Alignment.topLeft, maxWidth: 500.0, maxHeight: 500.0, minWidth: 250.0, minHeight: 250.0, child: Container( color: Color(0x33FF00FF), width: 500.0, height: 500.0, ), ), );SizedOverflowBox
是SizedBox与OverflowBox的结合体
布局行为:
尺寸部分
通过将自身的固定尺寸 传递给child 来达到控制child尺寸的目的;超出部分
可以突破父节点尺寸的限制 超出部分也可以被渲染显示 与OverflowBox类似
Container( color: Colors.green, alignment: Alignment.topRight, width: 200.0, height: 200.0, padding: EdgeInsets.all(5.0), child: SizedOverflowBox( size: Size(20.0, 300.0), child: Container(color: Colors.red, width: 200.0, height: 200.0), ), );
滚动布局
滚动布局平时用到的也比较多 滚动类的 Widget 可以分为:
SingleChildScrollView
ListView
GridView
CustomScrollView
SingleChildScroollView
只能有一个可滚动 widget
class SingleChildScrollViewTestRoute extends StatelessWidget { @override Widget build(BuildContext context) { String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; return Scrollbar( child: SingleChildScrollView( padding: EdgeInsets.all(16.0), child: Center( child: Column( //动态创建一个List<Widget> children: str.split("") //每一个字母都用一个Text显示,字体为原来的两倍 .map((c) => Text(c, textScaleFactor: 2.0,)) .toList(), ), ), ), ); } }
ListView
ListView 是沿线性方向排列所有 widget
ListView({ //可滚动widget公共参数 Axis scrollDirection = Axis.vertical, bool reverse = false, ScrollController controller, bool primary, ScrollPhysics physics, EdgeInsetsGeometry padding, //ListView各个构造函数的共同参数 double itemExtent, bool shrinkWrap = false, bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, double cacheExtent, //子widget列表 List<Widget> children = const <Widget>[], })
ListView 两种使用方式
直接传递 view 数组
ew ListView( itemExtent: 200, children: <Widget>[ new Text("a"), new Text("aa"), new Text("aaa"), new Text("aaaa"), new Text("aaaaa"), new Text("aaaaaa"), ], )
当数据源不规律 或者数据很少时 可以直接指定 Widget 数组
当数据量太大时 就该考虑使用 ListView.builder 来实现了
new ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return new ListTile( title: new Text('${items[index]}'), ); }, ),
在 Flutter 中 所有的滚动的控件 当滚动到顶 或底部继续滚动时
和系统的 behavior 有关如果需要去掉这个效果 就需要自定义一个 behavior 在使用滚动布局时 指定使用自定义的 behavior就可以去掉这个效果
自定义 behavior:
class MyBehavior extends ScrollBehavior { @override Widget buildViewportChrome( BuildContext context, Widget child, AxisDirection axisDirection) { return child; }}
ScrollConfiguration( behavior: MyBehavior(), child: ListView.separated( itemCount: 100, //列表项构造器 itemBuilder: (BuildContext context, int index) { return ListTile(title: Text("$index")); }, //分割器构造器 separatorBuilder: (BuildContext context, int index) { return index%2==0?divider1:divider2; }, ), )
GridView
GridView 也就是网格布局
GridView({ Axis scrollDirection = Axis.vertical, bool reverse = false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap = false, EdgeInsetsGeometry padding, @required SliverGridDelegate gridDelegate, //控制子widget layout的委托 bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, double cacheExtent, List<Widget> children = const <Widget>[],})
同样 GridView 也有两种基本的使用方式 第一种是直接指定 widget 数组:
GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, //横轴四个子widget childAspectRatio: 1.0 //宽高比为1时 子widget ), children:<Widget>[ Icon(Icons.ac_unit), Icon(Icons.airport_shuttle), Icon(Icons.all_inclusive), Icon(Icons.beach_access), Icon(Icons.cake), Icon(Icons.free_breakfast) ] ),第二种方式是通过 GridView.build 来实现
class _MyHomePageState extends State<MyHomePage> { @override void initState() { // 初始化数据 _retrieveIcons(); } List<IconData> _icons = []; //保存Icon数据 @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, //每行三列 childAspectRatio: 1.0 //显示区域宽高相等 ), itemCount: _icons.length, itemBuilder: (context, index) { //如果显示到最后一个并且Icon总数小于200时继续获取数据 if (index == _icons.length - 1 && _icons.length < 200) { _retrieveIcons(); } return Icon(_icons[index]); } ), // This trailing comma makes auto-formatting nicer for build methods. ); } //模拟异步获取数据 void _retrieveIcons() { Future.delayed(Duration(milliseconds: 200)).then((e) { setState(() { _icons.addAll([ Icons.ac_unit, Icons.airport_shuttle, Icons.all_inclusive, Icons.beach_access, Icons.cake, Icons.free_breakfast ]); }); }); }}

4 CustomScrollView
CustomScrollView 使用sliver来自定义滚动模型(效果)的widget
它可以包含多种滚动模型 举个例子 假设有一个页面 顶部需要一个GridView 底部需要一个ListView
而要求整个页面的滑动效果是统一的 即它们看起来是一个整体
如果使用GridView+ListView来实现的话 就不能保证一致的滑动效果
因为它们的滚动效果是分离的 所以这时就需要一个"胶水" 把这些彼此独立的可滚动widget(Sliver)"粘"起来
而CustomScrollView的功能就相当于“胶水”
Sliver有细片 小片之意 在Flutter中 Sliver通常指具有特定滚动效果的可滚动块
可滚动widget 如ListView GridView等都有对应的Sliver实现如SliverList SliverGrid等
对于大多数Sliver来说 它们和可滚动Widget最主要的区别是Sliver不会包含Scrollable Widget 也就是说Sliver本身不包含滚动交互模型 正因如此 CustomScrollView才可以将多个Sliver"粘"在一起 这些Sliver共用CustomScrollView的Scrollable 最终实现统一的滑动效果
import 'package:flutter/material.dart';class CustomScrollViewTestRoute extends StatelessWidget { @override Widget build(BuildContext context) { //因为本路由没有使用Scaffold 为了让子级Widget(如Text)使用 //Material Design 默认的样式风格,使用Material作为本路由的根 return Material( child: CustomScrollView( slivers: <Widget>[ //AppBar 包含一个导航栏 SliverAppBar( pinned: true, expandedHeight: 250.0, flexibleSpace: FlexibleSpaceBar( title: const Text('Demo'), background: Image.asset( "./images/avatar.png", fit: BoxFit.cover,), ), ), SliverPadding( padding: const EdgeInsets.all(8.0), sliver: new SliverGrid( //Grid gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, //Grid按两列显示 mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, childAspectRatio: 4.0, ), delegate: new SliverChildBuilderDelegate( (BuildContext context, int index) { //创建子widget return new Container( alignment: Alignment.center, color: Colors.cyan[100 * (index % 9)], child: new Text('grid item $index'), ); }, childCount: 20, ), ), ), //List new SliverFixedExtentList( itemExtent: 50.0, delegate: new SliverChildBuilderDelegate( (BuildContext context, int index) { //创建列表项 return new Container( alignment: Alignment.center, color: Colors.lightBlue[100 * (index % 9)], child: new Text('list item $index'), ); }, childCount: 50 //50个列表项 ), ), ], ), ); }}

总结
Flutter 中的 widget 非常多
学习起来会繁琐
但上手很容易 写几个 demo 运行几次就能掌握基本控件的使用
再熟悉了 Flutter 的控件之后
用 Flutter 来实现一些复杂的布局要比原生还要简单
尊贵的董事大人
英文标题不为空时 视为本栏投稿
需要关键字 描述 英文标题