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 来实现一些复杂的布局要比原生还要简单

尊贵的董事大人
英文标题不为空时 视为本栏投稿
需要关键字 描述 英文标题