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),)
],
),
 RotatedBox 和 Transform.rotate 对比了一下
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        ]);
      });
    });
  }}

image.png

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