App的页面框架
Material Design是由Google推出的全新设计语言,这种设计语言旨在为手机、平板电脑等平台提供更一致、更广泛的外观和感觉。Material Design是一种有质感的设计风格,还会提供一些默认的交互动画。
对于没有相关基础的人,在正式学习App的UI之前,建议先了解Material Design相关的知识
MaterialApp
MaterialApp代表使用Material设计风格的应用,里面包含了其所需要的基本控件。一个完整的Flutter项目是由这个主组件开始的。
MaterialApp属性详解
| 属性 | 类型 | 简述 |
|---|---|---|
| home | Widget | 主页。用于指定当前App打开时显示的页面 |
| routes | Map<String, WidgetBuilder> | 路由表,定义页面跳转规则 |
| initialRoute | String | 初始路由名称 |
| onGenerateRoute | RouteFactory | 通过pushNamed跳转路由页面时,在routes查找不到时回调 |
| onUnknownRoute | RouteFactory | onGenerateRoute 无法生成路由时调用 |
| navigatorObservers | List<NavigatorObserver> | 导航的监听器列表 |
| builder | TransitionBuilder | 构建Widget前调用, 一般做字体大小,方向,主题颜色等配置 |
| title | String | 应用标题。出现在Android任务管理器的程序快照之上 ,或iOS的程序切换管理器中 |
| onGenerateTitle | GenerateAppTitle | 与title一样,但含有一个context参数用于做本地化 |
| theme | ThemeData | 应用程序的主题,各种的定制颜色都可以设置,用于程序主题切换 |
| darkTheme | ThemeData | 深色模式下的主题 |
| themeMode | ThemeMode | 用于设定主题模式 |
| color | Color | 应用的主颜色值 |
| locale | Locale | 用于本地化。如果为null则使用当前系统区域 |
| localizationsDelegates | Iterable<LocalizationsDelegate<dynamic>> | 本地化委托,用于更改Widget默认的提示语,按钮text等 |
| localeListResolutionCallback | LocaleListResolutionCallback | 该回调负责在应用启动时以及用户更改设备的区域设置时选择应用的区域设置 |
| localeResolutionCallback | LocaleResolutionCallback | 当传入的是不支持的语种,可通过该回调做相应处理 |
| supportedLocales | Iterable<Locale> | 传入支持的语种列表 |
| showPerformanceOverlay | bool | 用于性能测试。 |
| checkerboardRasterCacheImages | bool | 为true时,打开光栅缓存图像的棋盘格 |
| checkerboardOffscreenLayers | bool | 为true时,打开棋盘格层 |
| showSemanticsDebugger | bool | 为true时,打开Widget边框,显示布局边界 |
| debugShowCheckedModeBanner | bool | 为true时,在debug模式下显示右上角的debug横幅 |
| debugShowMaterialGrid | bool | debug模式下是否显示Material网格 |
// 在构建UI前,设置一些属性
MaterialApp(
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor: 1.4,
),
child: child,
);
},
);
Scaffold
Scaffold是App的页面框架,将整个页面分为如下的几个部分
Scaffold属性
| 属性 | 类型 | 简述 |
|---|---|---|
| appBar | PreferredSizeWidget | 界面顶部的一栏控件,相当于 Android 中的 ActionBar |
| body | Widget | 当前页面所显示的主要内容 |
| floatingActionButton | Widget | Material中所定义的FAB,是一个悬浮的功能按钮 |
| floatingActionButtonLocation | FloatingActionButtonLocation | 设定悬浮按钮的位置 |
| floatingActionButtonAnimator | FloatingActionButtonAnimator | 悬浮按钮动画 |
| persistentFooterButtons | List<Widget> | 在底部显示的一组按钮 |
| drawer | Widget | 开始部分的(左边)抽屉菜单 |
| endDrawer | Widget | 结束部分的(右边)抽屉菜单 |
| drawerScrimColor | Color | 打开侧滑菜单时遮盖在主要内容区的蒙层颜色 |
| backgroundColor | Color | 内容的背景颜色。默认为 ThemeData.scaffoldBackgroundColor |
| bottomNavigationBar | Widget | 显示在底部的导航栏 |
| bottomSheet | Widget | 底部永久性显示的提示框 |
| resizeToAvoidBottomInset | bool | 页面浮动控件部分自动调整,以避免被弹出键盘所遮盖,默认为true |
| primary | bool | 是否填充顶部栏,默认为true |
| drawerDragStartBehavior | DragStartBehavior | 处理拖动开始行为的方式 |
| drawerEdgeDragWidth | double | 水平滑动将要打开侧滑菜单的区域的宽度 |
| extendBody | bool | 若为true且指定了bottomNavigationBar或者persistentFooterButtons则body将延伸到Scaffold的底部 |
| extendBodyBehindAppBar | bool | 作用类似extendBody,但延伸的位置是AppBar |
AppBar
AppBar可以显示顶部leading、title和actions等内容。底部通常为选项卡TabBar。flexibleSpace显示在AppBar的下方,高度和AppBar高度一样,可以实现一些特殊的效果
BottomAppBar
BottomAppBar 是一个不规则底部工具栏,它比BottomNavigationBar 灵活,可以放置文字和图标等等控件。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: Center(
child: Text('You have pressed the button'),
),
bottomNavigationBar: BottomAppBar(
shape: const CircularNotchedRectangle(),
child: Container(height: 50.0,),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
基础控件
在Flutter中,UI小控件有两种设计风格,一种是
Material设计,这是安卓的官方设计风格,另一种则是Cupertino风格,是iOS的官方设计风格。因此,当遇到带有这两个单词开头的控件时,我们应该明确他们表达的意思。
| 控件 | 简述 |
|---|---|
Text | 文本控件 |
Image | 图片控件 |
Icon | 图标控件 |
SelectableText | 可选文本控件 |
TextField | 文本输入框 |
MaterialButton | Material(安卓)风格按钮 |
RaisedButton | 凸起的按钮 |
FlatButton | 扁平的按钮 |
IconButton | 图标按钮 |
CupertinoButton | Cupertino(iOS)风格按钮 |
OutlineButton | 线框按钮 |
Radio | 单选框 |
Checkbox | 多选框 |
Chip | 碎片控件 |
Slider | 滑块控件 |
CupertinoSlider | iOS 风格滑块控件 |
Switch | 开关控件 |
CupertinoSwitch | iOS 风格开关控件 |
Placeholder | 占位控件 |
Text
| 属性名 | 类型 | 简述 |
|---|---|---|
| data | String | 需要显示的文本字符串 |
| style | TextStyle | 文本显示的样式 |
| textAlign | TextAlign | 文本对齐方式 |
| textDirection | TextDirection | 文本显示方向 |
| softWrap | bool | 是否自动换行 |
| overflow | TextOverflow | 溢出处理。clip:剪辑溢出的文本;fade:将溢出的文本淡化为透明;ellipsis:用省略号表示溢出;visible:在容器之外显示溢出的文本 |
| textScaleFactor | double | 每个逻辑像素的字体像素值。简单说就是字体缩放系数 |
| maxLines | int | 文本最多可显示的行数。如果文本超过给定的行数,则根据溢出规则截断 |
| textSpan | TextSpan | 以TextSpan方式显示文本。需使用Text.rich构造方法创建 |
Image
构造方法
- Image : 从
ImageProvider中获取图片 - Image.asset :加载资源目录中的图片
- Image.network:加载网络图片
- Image.file:加载本地图片文件
- Image.memory:加载
Uint8List资源图片
| 属性名 | 类型 | 简述 |
|---|---|---|
| image | ImageProvider | 用于自定义图片控件的情况 |
| width/height | double | 设置Image控件自身的宽高 |
| fit | BoxFit | 图片的填充模式 |
| color | Color | 图片颜色 |
| colorBlendMode | BlendMode | 对图片进行混合颜色处理,有多种值可选 |
| alignment | Alignment | 设置图片的对齐位置 |
| repeat | ImageRepeat | 设置图片的重复填充方式 |
| centerSlice | Rect | 类似与Android中的点9处理,在图片上定义某个矩形区域用于拉伸,这9个点其实就是八个方向加上正中 |
| gaplessPlayback | bool | 当ImageProvider发生变化时,显示新图片的过程中,如果值为true则保留旧图片直至显示出新图片为止;如果false,则不保留旧图片,直接空白等待下一张图片的加载 |
// 直接构造
Image(
image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
)
// 调用相应的命名构造方法
Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg')
BoxFit 填充模式
| 取值 | 简述 |
|---|---|
BoxFit.contain | 显示整张图片,按照原始比例缩放显示 |
BoxFit.fill | 显示整张图片,拉伸填充全部可显示区域 |
BoxFit.cover | 按照原始比例缩放,可能裁剪,填满可显示区域 |
BoxFit.fitHeight | 按照原始比例缩放,可能裁剪,高度优先填满 |
BoxFit.fitWidth | 按照原始比例缩放,可能裁剪宽度优先填满 |
BoxFit.none | 图片居中显示,不缩放原图 |
BoxFit.scaleDown | 显示整张图片,只能缩小或者原图显示 |
TextField
| 属性名 | 类型 | 简述 |
|---|---|---|
| controller | TextEditingController | 输入框的控制器,通常用于获取输入的内容 |
| focusNode | FocusNode | 用于输入框的焦点管理和监听 |
| decoration | InputDecoration | 输入框的装饰器,用于修改外观 |
| keyboardType | TextInputType | 设置输入类型,不同的输入类型键盘会不一样 |
| textInputAction | TextInputAction | 用于设置键盘动作(一般位于右下角,默认是完成) |
| textCapitalization | TextCapitalization | 配置平台键盘如何选择大写或小写键盘。 |
| style | TextStyle | 文本样式 |
| textAlign | TextAlign | 文本位置 |
| textDirection | TextDirection | 文本显示方向 |
| autofocus | bool | 是否自动获取焦点 |
| obscureText | bool | 是否隐藏输入的文字,通常用于密码框 |
| autocorrect | bool | 是否自动校验 |
| maxLines | int | 最大行数 |
| maxLength | int | 输入的最大字符数 |
| maxLengthEnforced | bool | 配合maxLength使用,达到最大长度时是否阻止输入 |
| onChanged | ValueChanged<String> | 输入文本发生变化时回调 |
| onEditingComplete | VoidCallback | 点击键盘完成按钮时触发的回调,无参数 |
| onSubmitted | ValueChanged<String> | 点击完成按钮时触发的回调,该回调有参数,参数即为输入的值 |
| inputFormatters | List<TextInputFormatter> | 对输入文本的校验 |
| cursorWidth | double | 光标的宽度 |
| cursorRadius | Radius | 光标的圆角 |
| cursorColor | Color | 光标的颜色 |
| keyboardAppearance | Brightness | 键盘的外观,仅在iOS设备上支持 |
| onTap | GestureTapCallback | 点击输入框时的回调 |
| enabled | bool | 输入框是否可用 |
| readOnly | bool | 是否只读 |
装饰器 InputDecoration
| 属性名 | 类型 | 简述 |
|---|---|---|
| icon | Widget | 设置位于输入框前的图标 |
| labelText | String | 设置描述输入框的标签 |
| labelStyle | TextStyle | 设置labelText的样式 |
| helperText | String | 帮助文本,位于输入框下方,如果errorText为空则不会显示 |
| helperStyle | TextStyle | 设置helperText的样式 |
| hintText | String | 提示文本,位于输入框内部 |
| hintStyle | TextStyle | hintText的样式 |
| hintMaxLines | int | 提示文本最大行数 |
| errorText | String | 错误文本信息提示 |
| errorStyle | TextStyle | errorText的样式 |
| hasFloatingPlaceholder | bool | labelText是否浮动,默认为true |
| isDense | bool | 输入框是否为密集型,默认为false,为true时,图标及间距会变小 |
| contentPadding | EdgeInsetsGeometry | 内间距 |
| isCollapsed | bool | 是否装饰的大小与输入字段的大小相同。 |
| prefixIcon | Widget | 位于输入框内部起始位置的图标 |
| prefix | Widget | 预先填充的Widget,跟prefixText只能同时出现一个 |
| prefixText | String | 预填充的文本,例如手机号前面预先加上区号等 |
| prefixStyle | TextStyle | prefixText的样式 |
| suffixIcon | Widget | 位于输入框尾部的图标 |
| suffix | Widget | 位于输入框尾部的控件 |
| suffixText | String | 位于尾部的填充文字 |
| suffixStyle | TextStyle | suffixText的样式 |
| counter | Widget | 输入框右下方的计数小控件,不能和counterText同时使用 |
| counterText | String | 右下方显示的文本,常用于显示输入的字符数量 |
| counterStyle | TextStyle | counterText的样式 |
| filled | bool | 如果为true,则使用fillColor指定的颜色填充 |
| fillColor | Color | 输入框的背景颜色 |
| errorBorder | InputBorder | errorText不为空,且输入框没有焦点时要显示的边框 |
| focusedBorder | InputBorder | 输入框有焦点时的边框,errorText必须为空 |
| focusedErrorBorder | InputBorder | errorText不为空时,输入框有焦点时的边框 |
| disabledBorder | InputBorder | 输入框禁用时显示的边框,errorText必须为空 |
| enabledBorder | InputBorder | 输入框可用时显示的边框,errorText必须为空 |
| border | InputBorder | 正常情况下的边框 |
| enabled | bool | 输入框是否可用 |
border的三种值
InputBorder.none没有边框OutlineInputBorder线框UnderlineInputBorder底边线,默认的
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(30), // 圆角
),
borderSide: BorderSide(
color: Colors.amber, //线框颜色为黄色
width: 2, //线框宽度为2
),
),
labelText: "labelText",
helperText: "helperText",
hintText: "hintText",
prefixIcon: Icon(Icons.perm_identity),
),
);
小技巧
当输入框的默认线框无法满足时,可以使用Container容器自定义边框。这时候可以将装饰器设置为InputDecoration.collapsed(hintText: 'hint')表示禁用装饰线
输入校验 TextInputFormatter
inputFormatters属性需要我们提供一个TextInputFormatter 类型的列表,该类有三个子类提供我们使用
WhitelistingTextInputFormatter白名单校验,只允许输入符合规则的字符BlacklistingTextInputFormatter黑名单校验,除了限定的字符其他的都可以输入LengthLimitingTextInputFormatter长度限制,与maxLength作用类似
前两个在实际使用时,其实是使用的Dart中正则表达式
/// 黑名单校验 + 长度限制
inputFormatters: [
BlacklistingTextInputFormatter(RegExp("[a-z]")),
LengthLimitingTextInputFormatter(11)
],
/// 白名单校验
inputFormatters: [BlacklistingTextInputFormatter(RegExp("[0-9]"))],
Button
Button的通用属性
| 属性名 | 类型 | 简述 |
|---|---|---|
| onPressed | VoidCallback | 点击事件监听 |
| onLongPress | VoidCallback | 长按事件监听 |
| onHighlightChanged | ValueChanged<bool> | 水波纹高亮变化回调,按下返回true,抬起返回false |
| textTheme | ButtonTextTheme | 定义按钮主题 |
| textColor | Color | 按钮文字颜色 |
| disabledTextColor | Color | 禁用按钮时文字颜色 |
| color | Color | 按钮颜色 |
| disabledColor | Color | 禁用按钮时颜色 |
| focusColor | Color | 获取焦点时按钮颜色 |
| splashColor | Color | 水波纹效果的初始化颜色 |
| hoverColor | Color | 当指针悬停在按钮上时的填充颜色 |
| highlightColor | Color | 水波纹的高亮颜色 |
| elevation | double | 阴影高度 |
| hoverElevation | double | 指针悬停在按钮上时的阴影 |
| focusElevation | double | 获取焦点时的阴影 |
| highlightElevation | double | 高亮时的阴影 |
| disabledElevation | double | 禁用时的阴影 |
| colorBrightness | Brightness | 用于此按钮的主题亮度 |
| child | Widget | 子控件 |
| enabled | bool | 是否禁用按钮 |
| padding | EdgeInsetsGeometry | 内边距 |
| shape | ShapeBorder | 设置形状 |
| clipBehavior | Clip | 剪裁 |
| focusNode | FocusNode | 用于焦点管理和监听 |
| autofocus | bool | 是否自动获取焦点 |
| animationDuration | Duration | 设置按钮形状和阴影变化的持续时间 |
| materialTapTargetSize | MaterialTapTargetSize | 配置点击目标的最小大小 |
| minWidth | double | 按钮最小宽度 |
| height | double | 按钮高度 |
| enableFeedback | bool | 是否开启按钮触觉反馈 |
RaisedButton(
child: Text('凸起按钮'),
onPressed: (){},
color: Colors.blue[200],
splashColor:Colors.yellow[100],
),
FlatButton(
child: Text('扁平按钮'),
onPressed: (){},
color: Colors.blue[200],
),
OutlineButton(
child: Text('线框按钮'),
onPressed: (){},
textColor: Colors.red,
borderSide: BorderSide(color: Colors.red,),
),
Radio 与 Checkbox
Radio
| 属性名 | 类型 | 简述 |
|---|---|---|
| value | 动态类型 | 此单选按钮表示的值 |
| groupValue | 动态类型 | 该组单选按钮当前选定的值 |
| onChanged | ValueChanged<T> | 状态变化回调 |
| activeColor | Color | 选中时的颜色 |
| materialTapTargetSize | MaterialTapTargetSize | 配置点击目标的最小大小。 |
| focusNode | FocusNode | 用于焦点管理和监听 |
| autofocus | bool | 是否自动获得焦点 |
Row(
children: <Widget>[
Radio(
value: 1,
activeColor: Colors.pink,
groupValue: this._sex,
onChanged: (value) {
setState(() {
this._sex = value;
});
},
),
Text('男'),
Radio(
value: 2,
activeColor: Colors.pink,
groupValue: this._sex,
onChanged: (value) {
setState(() {
this._sex = value;
});
},
),
Text('女'),
],
),
Checkbox
| 属性名 | 类型 | 简述 |
|---|---|---|
| value | bool | 是否选中此复选框 |
| onChanged | ValueChanged<bool> | 该组单选按钮当前选定的值 |
| tristate | bool | 默认false,如果为true,复选框的值可以为true、false或null |
| activeColor | Color | 选中时的颜色 |
| checkColor | Color | 选中时复选框图标的颜色 |
| materialTapTargetSize | MaterialTapTargetSize | 配置点击目标的最小大小 |
| focusNode | FocusNode | 用于焦点管理和监听 |
| autofocus | bool | 是否自动获得焦点 |
Row(
children: <Widget>[
Checkbox(
activeColor: Colors.pink,
checkColor: Colors.blue,
value: this._flag1,
onChanged: (value) {
setState(() {
this._flag1 = value;
});
},
),
Checkbox(
activeColor: Colors.pink,
checkColor: Colors.blue,
value: this._flag2,
onChanged: (value) {
setState(() {
this._flag2 = value;
});
},
),
],
)
Chip
| 属性名 | 类型 | 简述 |
|---|---|---|
| avatar | Widget | 在碎片标签之前显示的小控件 |
| label | Widget | 碎片的标签 |
| labelStyle | TextStyle | 标签文字样式 |
| labelPadding | EdgeInsetsGeometry | 标签文字内间距 |
| shape | ShapeBorder | 形状 |
| clipBehavior | Clip | 裁剪。 默认Clip.none(不裁剪) |
| backgroundColor | Color | 背景颜色 |
| padding | EdgeInsetsGeometry | 内间距 |
| deleteIcon | Widget | 添加图标按钮, 必须与onDeleted 配合使用 |
| onDeleted | VoidCallback | 图标按钮监听 |
| deleteIconColor | Color | deleteIcon的颜色 |
| deleteButtonTooltipMessage | String | deleteIcon长按文字提示 |
| materialTapTargetSize | MaterialTapTargetSize | 配置点击目标的最小大小 |
| elevation | double | 阴影高度 |
| shadowColor | Color | 阴影颜色 |
Chip(
avatar: Icon(Icons.add_alert),
label: Text('我是一个标签'),
deleteIcon: Icon(Icons.close),
deleteIconColor:Colors.red,
deleteButtonTooltipMessage:'点你妹',
labelStyle: TextStyle(color: Colors.white),
backgroundColor: Colors.blue,
elevation:20,
shadowColor:Colors.grey,
onDeleted: (){
print('onTap');
},
),
Slider 与 CupertinoSlider
Slider
| 属性名 | 类型 | 简述 |
|---|---|---|
| value | double | 当前值 默认 0 -- 1 之间 |
| onChanged | ValueChanged<double> | 滑动过程中调用 |
| onChangeStart | ValueChanged<double> | 开始滑动时调用 |
| onChangeEnd | ValueChanged<double> | 滑动完成时调用 |
| min | double | 最小值 默认 0 |
| max | double | 最大值 默认 1 |
| divisions | int | 分段个数 |
| label | String | 滑动时 显示的文字 (必须与divisions配合使用) |
| activeColor | Color | 用于滑块轨道的活动部分的颜色 |
| inactiveColor | Color | 滑块轨道的非活动部分的颜色 |
CupertinoSlider 控件属性与Slider 基本相同。
Slider(
label:'current ${valuec.round()}',
max: 100,
min: 0,
divisions: 5,
activeColor:Colors.blue,
inactiveColor: Colors.yellow,
value:this.valuec,
onChanged: (double v) {
setState(() {
this.valuec = v;
});
},
onChangeStart: (startValue) {
print('Started at $startValue');
},
onChangeEnd: (newValue) {
print('Ended on $newValue');
},
),
Switch 和 CupertinoSwitch
Switch
| 属性名 | 类型 | 简述 |
|---|---|---|
| value | bool | 当前开关状态 |
| onChanged | ValueChanged<bool> | 开关状态变化回调 |
| activeColor | Color | 打开状态的颜色 |
| activeTrackColor | Color | 打开状态时轨道上的颜色。 |
| inactiveThumbColor | Color | 关闭状态按钮的颜色 |
| inactiveTrackColor | Color | 关闭状态轨道颜色 |
| activeThumbImage | ImageProvider | 打开状态下按钮图片 |
| inactiveThumbImage | ImageProvider | 关闭状态下按钮图片 |
| materialTapTargetSize | MaterialTapTargetSize | 配置点击目标的最小大小 |
| dragStartBehavior | DragStartBehavior | 确定处理拖动启动行为的方式 |
| focusNode | FocusNode | 用于焦点管理和监听 |
| autofocus | bool | 是否自动获得焦点 |
CupertinoSwitch 的属性较少
| 属性名 | 类型 | 简述 |
|---|---|---|
| value | bool | 当前开关状态 |
| onChanged | ValueChanged<bool> | 开关状态变化回调 |
| activeColor | Color | 打开状态的颜色 |
Switch(
activeColor:Colors.red,
activeTrackColor:Colors.yellow,
inactiveThumbColor:Colors.pink[200],
inactiveTrackColor:Colors.black,
value: this.valuea,
onChanged: (v) {
setState(() {
this.valuea = v;
});
},
),
容器与布局
通常的,把只包含单个子Widget的Widget称为容器,把可以包含多个子Widget的Widget称为布局,实际上容器本身也是一种布局,因为布局本质上是指的对控件的位置的管理或控制。
万能容器
Container
它可以为 Widget 添加大小、背景等各种参数,其 child 属性用于设置它的子控件。由于它较为复杂,这里简述一下它的绘制顺序
- 首先会绘制
transform效果 - 接着绘制
decoration - 然后绘制
child - 最后绘制
foregroundDecoration
| 属性 | 类型 | 简述 |
|---|---|---|
| alignment | AlignmentGeometry | 容器内 child 的对齐方式 |
| padding | EdgeInsetsGeometry | 容器内边距 |
| color | Color | 容器的背景色 |
| decoration | Decoration | 容器的背景装饰 |
| foregroundDecoration | Decoration | 容器的前景装饰 |
| width | double | 容器的宽度 |
| height | double | 容器的高度 |
| constraints | BoxConstraints | 容器的大小限制 |
| margin | EdgeInsetsGeometry | 容器外边距 |
| transform | Matrix4 | 容器的变化 |
| child | Widget | 容器里显示的 Widget |
Container 本身是一个盒子模型
Container(
width: 300.0,
height: 500.0,
margin: EdgeInsets.all(16.0),
padding: EdgeInsets.all(16.0),
alignment: Alignment.center,
decoration: BoxDecoration(
//背景填充颜色
color: Colors.amberAccent,
//背景边框
border: Border.all(
//边框颜色
color: Colors.black12,
//边框宽度
width: 5),
//边框圆角
borderRadius: BorderRadius.only(
topLeft: Radius.circular(5.0),
topRight: Radius.circular(10.0),
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(20.0)),
//渐变效果,会覆盖 color
gradient: LinearGradient(
colors: [Colors.redAccent, Colors.greenAccent, Colors.blueAccent],
),
//阴影效果
boxShadow: [BoxShadow(color: Colors.grey, blurRadius: 5.0)],
),
//前景装饰,绘制在 child 之上
foregroundDecoration: BoxDecoration(
image: DecorationImage(image: NetworkImage('https://gitee.com/arcticfox1919/ImageHosting/raw/master/img/emm.jpg'))
))
Container 简单总结
- 未设置约束和固定宽高时,若存在子控件,则原则上
Container和子控件一样大;不存在子控件,则原则上占满全屏。 - 若未设置约束和宽高,但设置了
alignment属性,且存在子控件,那么Container 原则上也会占满全屏 - 设置了固定宽高,则无论是否存在子控件,宽高都是固定的。
关于几个属性的类型:
alignment可使用三种类型的值
- Alignment
- FractionalOffset
- AlignmentDirectional
其中Alignment和FractionalOffset比较类似,而AlignmentDirectional主要用于国际化中左右顺序的支持。
margin与padding都使用相同类型的值,即EdgeInsets,该类型共用四中构造方法
- EdgeInsets.all()
- EdgeInsets.symmetric()
- EdgeInsets.fromLTRB()
- EdgeInsets.only()
decoration有四种类型,都是Decoration的子类,其中三种在开发中会用到
- BoxDecoration
- ShapeDecoration
- UnderlineTabIndicator
ShapeDecoration 通常是用于单独为四条边框绘制不同的效果,它的属性与BoxDecoration大致相同,其中shape属性是和BoxDecoration中不同的,类型为ShapeBorder,它的取值如下
- Border 绘制四边
- UnderlineInputBorder 绘制底边线
- RoundedRectangleBorder 绘制矩形边框
- CircleBorder 绘制圆形边框
- StadiumBorder 绘制竖向椭圆边框
- BeveledRectangleBorder 绘制八角边框
边距容器
Padding
主要用于设置控件之间的边距,用法参见Container
| 属性 | 类型 | 简述 |
|---|---|---|
| padding | EdgeInsetsGeometry | 容器内边距 |
| child | Widget | 容器里显示的 Widget |
基础布局
弹性布局
Flex
弹性布局,类似于 CSS 的 Flexbox。
| 属性 | 类型 | 简述 |
|---|---|---|
| direction | Axis | 主轴的方向 |
| mainAxisAlignment | MainAxisAlignment | 子Widget 在主轴的对齐方式 |
| mainAxisSize | MainAxisSize | 主轴应该占用多大的空间 |
| crossAxisAlignment | CrossAxisAlignment | 子Widget 在交叉轴的对齐方式 |
| textDirection | TextDirection | 子Widget 在主轴方向上的布局顺序 |
| verticalDirection | VerticalDirection | 子Widget 在交叉轴方向上的布局顺序 |
| textBaseline | TextBaseline | 子Widget 时使用哪个基线 |
| children | List< Widget> | Flex布局里排列的子控件 |
direction 的取值:主轴的方向
| Axis 值 | 简述 |
|---|---|
| Axis.horizontal | 主轴为水平方向,子Widget 就会沿水平方向排列,则交叉轴为垂直方向。 |
| Axis.vertical | 主轴为垂直方向,子Widget 就会沿垂直方向排列,则交叉轴为水平方向。 |
mainAxisAlignment 的取值:子控件在主轴的对齐方式
| MainAxisAlignment 值 | 简述 |
|---|---|
| MainAxisAlignment.start | 沿着主轴的起点对齐 textDirection 必须有值,以确定是从左边开始的还是从右边开始的 |
| MainAxisAlignment.end | 沿着主轴的终点对齐 textDirection 必须有值,以确定是在左边结束的还是在右边结束的 |
| MainAxisAlignment.center | 在主轴上居中对齐 |
| MainAxisAlignment.spaceBetween | 在主轴上,两端对齐,子控件之间的间隔都相等 |
| MainAxisAlignment.spaceAround | 在主轴上,将多余的控件均匀分布给 子控件之间,而且第一个 子Widget 和 最后一个子Widget 距边框的距离是 两个 子Widget 距离的一半 |
| MainAxisAlignment.spaceEvenly | 在主轴上,将多余的控件均匀分布给子控件之间,而且第一个 子Widget 和 最后一个子Widget 距边框的距离和 子控件之间的距离一样 |
其中最后三个属性不太好理解,这里给出图示:
MainAxisAlignment.spaceBetween
两端顶格,中间均分
MainAxisAlignment.spaceAround
该效果我称为拉手布局,相当于小朋友伸开手,且相互间手拉手。
MainAxisAlignment.spaceEvenly
均分间距
mainAxisSize 的取值 : 表示主轴应该占用多大的空间
| MainAxisSize 值 | 简述 |
|---|---|
| MainAxisSize.min | 主轴的大小是能显示完 子Widget 的最小大小,主轴的大小就是 子Widget 的大小 |
| MainAxisSize.max | 主轴能显示的最大的大小,根据约束来判断 |
crossAxisAlignment 的取值:子控件在交叉轴的对齐方式
| CrossAxisAlignment 值 | 简述 |
|---|---|
| CrossAxisAlignment.start | 沿着交叉轴的起点对齐 verticalDirection 必须有值,以确定是从左边开始的还是从右边开始的 |
| CrossAxisAlignment.end | 沿着主轴的终点对齐 verticalDirection 必须有值,以确定是在左边结束的还是在右边结束的 |
| CrossAxisAlignment.center | 在交叉轴上居中对齐 |
| CrossAxisAlignment.stretch | 要求 子Widget 在交叉轴上填满 |
| CrossAxisAlignment.baseline | 要求 子Widget 的基线在交叉轴上对齐 |
textDirection 的取值:子控件在主轴方向上的布局顺序
| TextDirection 值 | 简述 |
|---|---|
| TextDirection.rtl | 表示从右到左 |
| TextDirection.ltr | 表示从左到右 |
verticalDirection 的取值 :子控件在交叉轴方向上的布局顺序
| VerticalDirection 值 | 简述 |
|---|---|
| VerticalDirection.up | 表示从下到上 |
| VerticalDirection.down | 表示从上到下 |
线性布局
Row
水平方向的线性布局
| 属性 | 类型 | 简述 |
|---|---|---|
| mainAxisAlignment | MainAxisAlignment | 子Widget 在主轴的对齐方式 |
| mainAxisSize | MainAxisSize | 表示主轴应该占用多大的空间 |
| crossAxisAlignment | CrossAxisAlignment | 子Widget 在交叉轴的对齐方式 |
| textDirection | TextDirection | 子Widget 在主轴方向上的布局顺序 |
| verticalDirection | VerticalDirection | 子Widget 在交叉轴方向上的布局顺序 |
| textBaseline | TextBaseline | 设置 子Widget 基线 |
| children | List< Widget> | 用于排列的子控件列表 |
Column
垂直方向的线性布局,其属性可直接参照Row
流式布局
Wrap
| 属性 | 类型 | 简述 |
|---|---|---|
| direction | Axis | 主轴的方向。默认是 Axis.horizontal |
| alignment | WrapAlignment | 子Widget 在主轴上的对齐方式,默认值为WrapAlignment.start |
| runAlignment | WrapAlignment | 纵轴对齐方式,默认值为WrapAlignment.start |
| runSpacing | double | 纵轴间距,默认是0.0 |
| crossAxisAlignment | WrapCrossAlignment | 交叉轴上的对齐方式 |
| textDirection | TextDirection | 子Widget 在主轴方向上的排列顺序 |
| verticalDirection | VerticalDirection | 子Widget 在交叉轴方向上的排列顺序 |
| children | List< Widget> | 子控件列表 |
层叠布局
Stack
| 属性 | 类型 | 简述 |
|---|---|---|
| alignment | AlignmentDirectional | 决定子Widget如何对齐 ,默认值为 AlignmentDirectional.topStart |
| textDirection | TextDirection | 用于确定 alignment的对齐方向 |
| fit | StackFit | 决定非positioned子Widget 如何去适应Stack的大小 |
| overflow | Overflow | 如何显示超出 Stack空间的 子widget |
| children | List< Widget> | 排列的子控件 |
在Stack布局中,通常会与另外两个控件配合使用,它们是Align和Positioned,前者用于相对定位,后者用于绝对定位。
Align比较简单,这里列一下Positioned的属性
| 属性 | 类型 | 简述 |
|---|---|---|
| left | double | 离 Stack 左边的距离 |
| top | double | 离 Stack 上边的距离 |
| right | double | 离 Stack 右边的距离 |
| bottom | double | 离 Stack 底边的距离 |
| width | double | 设定子控件的宽度 |
| height | double | 设定子控件的高度 |
辅助布局
Center
水平垂直居中布局。类似Container设置alignment
SizedBox
固定宽高布局,类似Container设置了宽高
AspectRatio
宽高比布局。
FractionallySizedBox
百分比布局。
Card
卡片布局。
高级布局
列表 ListView
它是一种可滚动的列表,共四种构造方法。其中最常用的是ListView.builder构造方法,因为它适用于大量的列表项的情形,甚至可以是无限数量的项。
ListView()ListView.builder()ListView.separated()ListView custom()
网格 GridView
用于创建二维网格列表。
GridView()默认构造GridView.count用于快速的创建固定横轴数量的网格GridView.extent用于创建交叉轴子最大可容纳的网格GridView.builder同ListView的builderGridView.custom用于构建自定义子Widget
GridView.count 是在交叉轴上创建固定个数的网格,crossAxisCount为必须的属性,表示交叉轴网格的个数,而GridView.extent是在交叉轴上创建最大可容纳的网格,maxCrossAxisExtent是必须的属性,表示交叉轴上网格的最大的宽度。
表格 Table/TableRow
表格布局和线性布局比较相似,只是使用起来更简洁一些。
| 属性 | 类型 | 简述 |
|---|---|---|
| columnWidths | Map<int, TableColumnWidth> | 设置每一列的宽度 |
| defaultColumnWidth | TableColumnWidth | 默认的每一列宽度值,默认情况下均分 |
| textDirection | TextDirection | 文字方向 |
| border | TableBorder | 表格边框 |
| defaultVerticalAlignment | TableCellVerticalAlignment | 每一个cell的垂直方向的alignment |
| children | List<TableRow> | 子控件列表 |
示例
Container(
child: Table(
columnWidths: const {
//列宽
0: FixedColumnWidth(100.0),
1: FixedColumnWidth(200.0),
2: FixedColumnWidth(50.0),
},
border: TableBorder.all(
color: Colors.green,
width: 2.0,
style: BorderStyle.solid,
),
children: [
TableRow(
decoration: BoxDecoration(
color: Colors.grey,
),
children: [
SizedBox(
height: 30.0,
child: Text('姓名'),
),
Text('性别'),
Text('年龄'),
]
),
TableRow(
children: [
Text('张三'),
Text('男'),
Text('20'),
]
),
TableRow(
children: [
Text('李四'),
Text('女'),
Text('28'),
]
),
],
),
);
栈索引
IndexedStack 继承自Stack,用于显示第index个child,而其他child则是不可见的。所以IndexedStack的尺寸永远是跟最大的子控件尺寸一致。与Stack相比,只是多了index的设置。
class _HomePageState extends State<HomePage> {
int _pageIndex = 0;
@override
Widget build(BuildContext context) {
print("_HomePageState build ...");
const bgColor = const [ Colors.red, Colors.green, Colors.blue, Colors.yellow ];
return Scaffold(
appBar: AppBar(
title: Text("Flutter Widget"),
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: IndexedStack(
index: _pageIndex,
children: <Widget>[
Container(
color: Colors.red,
),
Container(
color: Colors.green,
),
Container(
color: Colors.blue,
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(child: Text("1"),onPressed: ()=>onClick(0),),
FlatButton(child: Text("2"),onPressed: ()=>onClick(1)),
FlatButton(child: Text("3"),onPressed: ()=>onClick(2)),
],
)
],
),
),
);
}
void onClick(int index){
setState(() {
_pageIndex = index;
});
}
}
事件与通知
Flutter中的触摸事件可分为两层来看待。第一层是触摸原事件(指针),有相应的四种事件类型:PointerDownEvent:用户与屏幕接触产生了联系。PointerMoveEvent:手指已从屏幕上的一个位置移动到另一个位置。PointerUpEvent:用户已停止接触屏幕。PointerCancelEvent:此指针的输入不再指向此应用程序。第二层就是检测到的手势,主要分为三大类,包括轻击、拖动和缩放
GestureDetector
GestureDetector 可以进行手势检测,共有7种类型事件。比如点击一次、双击、长按、垂直滑动及水平滑动等
| 属性 | 类型 | 简述 |
|---|---|---|
| onTapDown | GestureTapDownCallback | 手指按下时触发 |
| onTapUp | GestureTapUpCallback | 手指抬起时触发 |
| onTap | GestureTapCallback | 单击屏幕时触发 |
| onTapCancel | GestureTapCancelCallback | 没有完成Tap的动作时触发 |
| onDoubleTap | GestureTapCallback | 快速双击屏幕时触发 |
| onLongPress | GestureLongPressCallback | 长按屏幕时触发 |
| onLongPressStart | GestureLongPressStartCallback | 监听长按事件的开始 |
| onLongPressMoveUpdate | GestureLongPressMoveUpdateCallback | 长按屏幕且移动手指时触发 |
| onLongPressUp | GestureLongPressUpCallback | 手指完全离开屏幕时触发 |
| onLongPressEnd | GestureLongPressEndCallback | 手指开始离开屏幕时触发 |
| onVerticalDragDown | GestureDragDownCallback | 手指按下并在垂直方向上移动时触发 |
| onVerticalDragStart | GestureDragStartCallback | 触摸点开始在垂直方向上移动时触发(在onVerticalDragDown之后) |
| onVerticalDragUpdate | GestureDragUpdateCallback | 触摸点在垂直方向上移动时触发 |
| onVerticalDragEnd | GestureDragEndCallback | 停止移动,且该拖拽操作就被认为是完成了时触发 |
| onVerticalDragCancel | GestureDragCancelCallback | 突然停止拖拽时触发 |
| onHorizontalDragDown | GestureDragDownCallback | 参见onVerticalDragDown,为水平方向 |
| onHorizontalDragStart | GestureDragStartCallback | 参见onVerticalDragDown,为水平方向 |
| onHorizontalDragUpdate | GestureDragUpdateCallback | 参见onVerticalDragDown,为水平方向 |
| onHorizontalDragEnd | GestureDragEndCallback | 参见onVerticalDragDown,为水平方向 |
| onHorizontalDragCancel | GestureDragCancelCallback | 参见onVerticalDragDown,为水平方向 |
| onPanDown | GestureDragDownCallback | 手指接触屏幕,并且可能开始移动时触发 |
| onPanStart | GestureDragStartCallback | 触摸点开始移动时触发 |
| onPanUpdate | GestureDragUpdateCallback | 触摸点不断移动时触发 |
| onPanEnd | GestureDragEndCallback | 操作完成手指离开屏幕时触发 |
| onPanCancel | GestureDragCancelCallback | 先前触发onPanDown的指针未完成时触发 |
| onScaleStart | GestureScaleStartCallback | 触摸屏幕开始缩放操作时触发 |
| onScaleUpdate | GestureScaleUpdateCallback | 缩放过程中的监听 |
| onScaleEnd | GestureScaleEndCallback | 手指离开屏幕,缩放完成时触发 |
| onForcePressStart | GestureForcePressStartCallback | 触摸屏幕且有足够压力时触发(仅在具有压力检测的屏幕设备支持) |
| onForcePressPeak | GestureForcePressPeakCallback | 触摸屏幕压力达到最大时触发(需设备支持) |
| onForcePressUpdate | GestureForcePressUpdateCallback | 有足够的压力并在屏幕上移动时触发(需设备支持) |
| onForcePressEnd | GestureForcePressEndCallback | 离开屏幕时触发(需设备支持) |
| behavior | HitTestBehavior | 在命中测试期间,此手势检测器应如何表现 |
| excludeFromSemantics | bool | 是否从语义树中排除这些手势。 例如,用于显示工具提示的长按手势被排除在外,因为工具提示本身直接包含在语义树中,因此具有显示该工具提示的手势将导致信息重复 |
| dragStartBehavior | DragStartBehavior | 设定处理拖动开始行为的方式 |
需要注意,GestureDetector并不会监听所有的手势,只有传入的回调非空时,才会监听。所以,如果想要禁用某个手势时,可将对应的回调函数设置为null。
另外,GestureDetector的某些事件是互斥的,不能同时存在,例如onVerticalUpdate、onHorizontalUpdate、onPanUpdate这三个事件不能同时存在,onPanUpdate和onScaleUpdate也不能同时存在。
测试手势处理回调
GestureDetector(
child: Container(
width: 300.0,
height: 500.0,
color: Colors.amber,
),
onTapDown: (_) => debugPrint("onTapDown"),
onTapUp: (_) => debugPrint("onTapUp"),
onTap: () => debugPrint("onTap"),
onTapCancel: () => debugPrint("onTapCancel"),
onDoubleTap: () => debugPrint("onDoubleTap"),
onLongPress: () => debugPrint("onLongPress"),
onLongPressUp: () => debugPrint("onLongPressUp"),
onVerticalDragDown: (_) => debugPrint("onVerticalDragDown"),
onVerticalDragStart: (_) => debugPrint("onVerticalDragStart"),
onVerticalDragUpdate: (_) => debugPrint("onVerticalDragUpdate"),
onVerticalDragEnd: (_) => debugPrint("onVerticalDragEnd"),
onVerticalDragCancel: () => debugPrint("onVerticalDragCancel"),
onHorizontalDragDown: (_) => debugPrint("onHorizontalDragDown"),
onHorizontalDragStart: (_) => debugPrint("onHorizontalDragStart"),
onHorizontalDragUpdate: (_) => debugPrint("onHorizontalDragUpdate"),
onHorizontalDragEnd: (_) => debugPrint("onHorizontalDragEnd"),
onHorizontalDragCancel: () => debugPrint("onHorizontalDragCancel"),
onPanDown: (_) => debugPrint("onPanDown"),
onPanStart: (_) => debugPrint("onPanStart"),
onPanUpdate: (_) => debugPrint("onPanUpdate"),
onPanEnd: (_) => debugPrint("onPanEnd"),
onPanCancel: () => debugPrint("onPanCancel"),
onScaleStart: (_) => debugPrint("onScaleStart"),
onScaleUpdate: (_) => debugPrint("onScaleUpdate"),
onScaleEnd: (_) => debugPrint("onScaleEnd"),
),
通过手势处理实现一个拖动示例
class _HomePageState extends State<HomePage> {
double _left = 0.0;
double _top = 0.0;
GlobalKey _gKey = GlobalKey();
double stackWidth = 0.0;
double stackHeight = 0.0;
@override
void initState() {
super.initState();
// 注册一个回调,当屏幕渲染第一帧的时候回调
WidgetsBinding.instance.addPostFrameCallback((_){
stackWidth = _gKey.currentContext.size.width;
stackHeight = _gKey.currentContext.size.height;
});
}
@override
Widget build(BuildContext context) {
print("_HomePageState build ...");
return Scaffold(
appBar: AppBar(
title: Text("Flutter Widget"),
),
body: GestureDetector(
onPanUpdate: (DragUpdateDetails details){
setState(() {
// 边界检查
if(_left + 200 > stackWidth){
_left = stackWidth- 200;
}else if(_left < 0){
_left = 0;
} else{
_left += details.delta.dx;
}
if(_top + 200 > stackHeight){
_top = stackHeight- 200;
}else if(_top < 0){
_top = 0;
}else{
_top += details.delta.dy;
}
});
},
child: Stack(
key: _gKey,
children: <Widget>[
Positioned(
left: _left,
top: _top,
width: 200.0,
height: 200.0,
child: Container(
color: Colors.amber,
),
)
],
),
),
);
}
}
缩放示例
class _ScaleWidgetState extends State<ScaleWidget>{
double _width = 300.0;
double _height = 200.0;
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
child: Container(
width: _width,
height: _height,
color: Colors.pink,
),
onScaleUpdate: (e) {
print(e);
setState(() {
// 限制缩放比例在0.7-1.2之间,超过范围则变为原大小
_width = 300* e.scale.clamp(0.7, 1.2);
_height = 200*e.scale.clamp(0.7, 1.2);
});
},
)
);
}
}
InkWell
具有水波纹效果(或称为溅墨效果)的点击事件控件。能处理的触摸事件很少,如需处理复杂的手势事件,应使用GestureDetector。
需要注意,当InkWell的父控件设置了背景色时,是看不到溅墨效果效果的,此时需要进行特殊处理, 当使用InkWell包裹image时,也无法显示出溅墨效果,这时建议使用Ink.Image控件。
// 需使用Matetial 以及 Ink控件包裹
Material(
child: Ink(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10)
),
child: InkWell(
onTap: (){
print("onTap...");
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16,vertical: 6),
child: Text("这是按钮",style: TextStyle(color: Colors.white),),
),
),
),
)
如需取消溅墨效果,可进行如下设置
// 设置highlightColor和radius取消溅墨效果
InkWell(
highlightColor: Colors.transparent,
radius: 0.0,
child: Text(
"click me",
),
onTap: () {
print("onTap");
},
),
Snackbar
它是Material Design设计中的一个轻量级消息通知控件。
| 属性 | 类型 | 简述 |
|---|---|---|
| content | Widget | 展示的内容 |
| backgroundColor | Color | 背景色 |
| elevation | double | 阴影高度 |
| shape | ShapeBorder | 形状 |
| behavior | SnackBarBehavior | 位置,SnackBarBehavior.fixed 固定在底部;SnackBarBehavior.floating显示在底部导航栏上方 |
| action | SnackBarAction | 执行的动作(相当于按钮) |
| duration | Duration | 停留时间 |
| animation | animation | 显示或隐藏的动画效果 |
| onVisible | VoidCallback | 显示时的回调 |
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('这是一个 SnackBar'),
backgroundColor: Colors.black26,
duration: Duration(seconds: 1),
behavior: SnackBarBehavior.floating,
action: SnackBarAction(
label: '我是按钮',
onPressed: () {
print('点击了按钮');
}),
));
小结:
- 显示
Snackbar:Scaffold.of(context).showSnackBar(snackBar) - 隐藏当前的
SnackBar:Scaffold.of(context).hideCurrentSnackBar()
布局小案例
案例一
Container(
margin: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0),
child: AspectRatio(
aspectRatio: 4 / 3,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.network(
'https://c-ssl.duitang.com/uploads/item/201810/07/20181007131933_qhjkl.thumb.1000_0.jpg',
fit: BoxFit.cover,
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
padding: const EdgeInsets.only(left: 16),
alignment: Alignment.centerLeft,
height: 60,
// width: double.infinity,
color: Color.fromRGBO(0, 0, 0, 0.5),
child: Text("日本万年一遇美女——桥本环奈",style: TextStyle(color: Colors.white,fontSize: 18),),
),
)
],
)),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey, width: 1),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey[400],
offset: Offset(2, 4),
blurRadius: 2)
]),
),
案例二
Container(
color: Colors.grey[200],
child: Column(
children: <Widget>[
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
height: 65,
color: Colors.white,
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(
Icons.wifi,
color: Colors.blueAccent,
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"网络和互联网",
style: TextStyle(fontSize: 18),
),
Text(
"WLAN、移动网络、流量使用",
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.grey[600]),
)
],
),
),
Icon(Icons.arrow_forward_ios,color: Colors.grey,)
],
),
),
Container(
height: 65,
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.symmetric(horizontal: 16),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("设置项1",style: TextStyle(fontSize: 18),),
Checkbox(
value: isCheck,
onChanged: (val){
},
),
// Checkbox(
// value: isCheck,
// onChanged: (val){
//
// },
// )
],
),
),
Container(
height: 65,
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.symmetric(horizontal: 16),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("是否开启蓝牙",style: TextStyle(fontSize: 18),),
Switch(value: isCheck,onChanged: (val){
},),
CupertinoSwitch(value: isCheck,)
],
),
),
Container(
height: 65,
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.symmetric(horizontal: 16),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("音量",style: TextStyle(fontSize: 18),),
Slider(
value: 50,
min: 0,
max: 100,
divisions:10,
onChanged: (val){
},
)
],
),
)
],
),
)
案例三
class _HomePageState extends State<HomePage> {
bool _isShow = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.topLeft,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: NetworkImage(
"http://8.pic.pc6.com/thumb/up/2011-12/20111221111636431530_600_0.png"))),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Color.fromRGBO(0, 0, 0, 0.5)),
margin: EdgeInsets.only(top: 60),
// 线性布局的主轴默认占满屏幕
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"这是标题",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
// 用于控制Widget 是否显示的控件
Visibility(
visible: _isShow,
child: Text("这是一段文本内容这是一段文本内容这这是一段文本内容是一段文本内容这是一段文本内容这是一段文本内容这是一段文本内容",
style: TextStyle(color: Colors.white, fontSize: 18)),
),
// onPressed 按钮点击事件回调
IconButton(icon:Icon(_isShow?Icons.keyboard_arrow_up:Icons.keyboard_arrow_down,size: 40,color: Colors.white),onPressed:(){
print("onPressed !");
// 更新屏幕
setState(() {
_isShow = !_isShow;
});
})
],
),
),
),
);
}
}
布局综合案例练习
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'view.dart';
class LoginPage2 extends StatefulWidget {
@override
_LoginPage2State createState() => _LoginPage2State();
}
class _LoginPage2State extends State<LoginPage2> {
TextEditingController _phoneNum = TextEditingController();
TextEditingController _password = TextEditingController();
TapGestureRecognizer _tapGestureRecognizer;
GlobalKey<ScaffoldState> _gk = GlobalKey<ScaffoldState>();
@override
void initState() {
super.initState();
_tapGestureRecognizer = TapGestureRecognizer();
_tapGestureRecognizer.onTap = myTap;
}
void myTap(){
print("myTap run 。。。。");
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
key: _gk,
body: Stack(
children: <Widget>[
CurveBgWidget(color: const Color(0xfff5f6f7),),
Align(
alignment: Alignment.topCenter,
child: Container(
width: 100,
height: 100,
margin: EdgeInsets.only(top: 150),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle
),
),
),
Container(
padding: EdgeInsets.only(top: 100,left: 16,right: 16),
margin: EdgeInsets.only(top: 180,right: 16,left: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8)
),
child: Column(
children: <Widget>[
TextField(
keyboardType: TextInputType.phone,
controller: _phoneNum,
maxLines: 1,
maxLength: 30,
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone_iphone),
hintText: "请输入手机号",
contentPadding: EdgeInsets.symmetric(
vertical: 10)),
),
Padding(
padding: EdgeInsets.only(
top: 10, bottom: 20),
child: TextField(
controller: _password,
maxLines: 1,
maxLength: 32,
obscureText: true,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock_outline),
hintText: "请输入登录密码",
contentPadding: EdgeInsets.symmetric(
vertical: 10),
suffixIcon: Icon(
Icons.remove_red_eye,
)),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
'忘记密码?',
style: TextStyle(fontSize: 16,color: Color(0xff333333)),
)
]),
InkWell(
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.symmetric(vertical: 12),
padding: EdgeInsets.symmetric(vertical: 6),
child: Text(
'登录',
style: TextStyle(
fontSize: 20, color: Colors.white),
),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
const Color(0xFFEA3D87),
const Color(0xFFFF7095)
]),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
offset: Offset(1.0, 5.0),
color: Color.fromRGBO(234, 61, 135, 0.4),
blurRadius: 5.0,
)
]),
),
),
SizedBox(height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('验证码登录',style: TextStyle(fontSize: 16,color: Color(0xff333333))),
Container(
height: 12,
padding: EdgeInsets.symmetric(horizontal: 8),
child: VerticalDivider(width: 0.0,color: Color(0xFFDEDEDE),thickness: 1,)),
InkWell(
onTap: (){
},
child: Text('新用户注册',style: TextStyle(
fontSize: 16,
color: Color(0xFF02A9FF)),),
),
],
),
SizedBox(height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: true,
onChanged: (v){},
),
Text.rich(
TextSpan(
text:'我已阅读并同意遵守',
style: TextStyle(
fontSize: 16,
color: const Color(0xff999999)
),
children: [
TextSpan(
text: '《服务许可协议》',
style: TextStyle(
fontSize: 16,
decoration: TextDecoration.underline,
color: const Color(0xff333333)
),
recognizer: _tapGestureRecognizer
)
]
)),
],
)
],
),
),
Container(
alignment: Alignment.topCenter,
margin: EdgeInsets.only(top: 160),
child: Column(
children: <Widget>[
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.pinkAccent
),
),
SizedBox(height: 16,),
Text("登录")
],
),
)
],
),
);
}
}
\