Flutter环境搭建
flutter doctor
创建项目
flutter create myapp
cd myapp
flutter devices // 查看开启的模拟器和连接的真机
flutter run -d xxxxxxxx
// 或
flutter run
或
运行
- 确保在VS Code的右下角选择了目标设备
- 按 F5 键或调用Debug>Start Debugging
- 等待应用程序启动
- 如果一切正常,在应用程序建成功后,您应该在您的设备或模拟器上看到应用程序:
热重载
- 开发工具启动,修改代码后
cmd-s
- 终端启动,输入
r
Hello World
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Welcom to Flutter',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center(
child: new Text('Hello World'),
),
),
);
}
}
MaterialApp
Material应用程序以MaterialApp
widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator
, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator
可以让您的应用程序在页面之间的平滑的过渡。 是否使用MaterialApp
完全是可选的,但是使用它是一个很好的做法。
// 主要属性
home 主页
title 标题
color 颜色
theme 主题
routes 路由
。。。
Scaffold
Scaffold
是一个路由页的骨架,appBar、drawer、bottomNavigationBar、floatingActionButton
Stateful widget & Stateless widget
- Stateful widget可以拥有状态,这些状态在widget生命周期中是可以变的,而Stateless widget是不可变的。
- Stateful widget至少由两个类组成:
- 一个
StatefulWidget
类。 - 一个
State
类;StatefulWidget
类本身是不变的,但是State
类中持有的状态在widget生命周期中可能会发生变化。
- 一个
基础组件
Text
属性名 | 类型 | 说明 |
---|---|---|
textAlign | TextAlign | center right left justify start end |
maxLines | ||
overflow | TextOverflow | clip ellipsis fade |
style | TextStyle() | color fontSize height fontFamily background decoration decorationStyle letterSpacing |
textScaleFactor | 相对于当前字体大小的缩放因子 | 1.5等 |
Text("hello",
textAlign: TextAlign.right,
style: TextStyle(
color: Colors.blue,
fontSize: 18.0,
height: 1.2,
background: new Paint()..color=Colors.yellow,
decoration:TextDecoration.underline,
decorationStyle: TextDecorationStyle.dashed
)
文字还有 TextSpan(Text.rich()) DefaultTextStyle
按钮
RaisedButton 漂浮 FlatButton 扁平 OutlineButton 带边框 IconButton 带Icon
RaisedButton(
child: Text("normal"),
onPressed: () {},
);
IconButton(
icon: Icon(Icons.thumb_up),
onPressed: () {},
)
带图标的按钮
RaisedButton.icon(
icon: Icon(Icons.send),
label: Text("发送"),
onPressed: _onPressed,
),
相关属性
const FlatButton({
...
@required this.onPressed, //按钮点击回调
this.textColor, //按钮文字颜色
this.disabledTextColor, //按钮禁用时的文字颜色
this.color, //按钮背景颜色
this.disabledColor,//按钮禁用时的背景颜色
this.highlightColor, //按钮按下时的背景颜色
this.splashColor, //点击时,水波动画中水波的颜色
this.colorBrightness,//按钮主题,默认是浅色主题
this.padding, //按钮的填充
this.shape, //外形
@required this.child, //按钮的内容
})
图片
本地图片
-
在
pubspec.yaml
中的flutter
部分添加如下内容:assets: - images/avatar.png
-
加载该图片
Image( image: AssetImage("images/avatar.png"), width: 100.0 ); // 或 Image.asset("images/avatar.png", width: 100.0, )
网络图片
Image(
image: NetworkImage(
"https://avatars2.githubusercontent.com/u/20411648?s=460&v=4"),
width: 100.0,
)
// 或
Image.network(
"https://avatars2.githubusercontent.com/u/20411648?s=460&v=4",
width: 100.0,
)
fit(BoxFit.fill)
fill
:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。cover
:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被剪裁。contain
:这是图片的默认适应规则,图片会在保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。fitWidth
:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。fitHeight
:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。none
:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。
单选/多选框
Switch(
value: _switchSelected,//当前状态
onChanged:(value){
//重新构建页面
setState(() {
_switchSelected=value;
});
},
),
Checkbox(
value: _checkboxSelected,
activeColor: Colors.red, //选中时的颜色
onChanged:(value){
setState(() {
_checkboxSelected=value;
});
},
)
输入框/表单
const TextField({
...
TextEditingController controller,
FocusNode focusNode,
InputDecoration decoration = const InputDecoration(),
TextInputType keyboardType,
TextInputAction textInputAction,
TextStyle style,
TextAlign textAlign = TextAlign.start,
bool autofocus = false,
bool obscureText = false,
int maxLines = 1,
int maxLength,
bool maxLengthEnforced = true,
ValueChanged<String> onChanged,
VoidCallback onEditingComplete,
ValueChanged<String> onSubmitted,
List<TextInputFormatter> inputFormatters,
bool enabled,
this.cursorWidth = 2.0,
this.cursorRadius,
this.cursorColor,
...
})
获取输入内容
//定义一个controller
TextEditingController _unameController = TextEditingController();
TextField(
autofocus: true,
controller: _unameController, //设置controller
...
)
print(_unameController.text)
监听值的变化
-
设置
onChange
回调,如:TextField( autofocus: true, onChanged: (v) { print("onChange: $v"); } )
-
通过
controller
监听,如:@override void initState() { //监听输入改变 _unameController.addListener((){ print(_unameController.text); }); }
进度条
LinearProgressIndicator CircularProgressIndicator
容器组件
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,
})
AppBar
AppBar({
Key key,
this.leading, //导航栏最左侧Widget,常见为抽屉菜单按钮或返回按钮。
this.automaticallyImplyLeading = true, //如果leading为null,是否自动实现默认的leading按钮
this.title,// 页面标题
this.actions, // 导航栏右侧菜单
this.bottom, // 导航栏底部菜单,通常为Tab按钮组
this.elevation = 4.0, // 导航栏阴影
this.centerTitle, //标题是否居中
this.backgroundColor,
... //其它属性见源码注释
})
TabBar
class _ScaffoldRouteState extends State<ScaffoldRoute>
with SingleTickerProviderStateMixin {
TabController _tabController; //需要定义一个Controller
List tabs = ["新闻", "历史", "图片"];
@override
void initState() {
super.initState();
// 创建Controller
_tabController = TabController(length: tabs.length, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
... //省略无关代码
bottom: TabBar( //生成Tab菜单
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList()
),
body: TabBarView(
controller: _tabController,
children: tabs.map((e) { //创建3个Tab页
return Container(
alignment: Alignment.center,
child: Text(e, textScaleFactor: 5),
);
}).toList(),
),
... //省略无关代码
}
抽屉菜单Drawer FloatingActionButton bottomNavigationBar略
Padding
Padding({
...
EdgeInsetsGeometry padding,
Widget child,
})
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
。
Padding(
//左边添加8像素补白
padding: const EdgeInsets.all(8.0),
child: Text("Hello world"),
)
const EdgeInsets.fromLTRB(1.0, 2.0, 3.0, 4.0)
const EdgeInsets.only({left: 2.0})
const EdgeInsets.symmetric({1.0, 2.0})
尺寸限制类容器(ConstrainedBox/SizedBox…)
ConstrainedBox
ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity, //宽度尽可能大
minHeight: 50.0 //最小高度为50像素
),
child: Container(
height: 5.0,
child: redBox
),
)
BoxConstraints用于设置限制条件,它的定义如下:
const BoxConstraints({
this.minWidth = 0.0, //最小宽度
this.maxWidth = double.infinity, //最大宽度
this.minHeight = 0.0, //最小高度
this.maxHeight = double.infinity //最大高度
})
SizedBox
SizedBox
用于给子元素指定固定的宽高,如:
SizedBox(
width: 80.0,
height: 80.0,
child: redBox
)
UnconstrainedBox 不会对子组件产生任何限制
AspectRatio 指定子组件的长宽比
LimitedBox 指定最大宽高
FractionallySizedBox 根据父容器宽高的百分比来设置子组件宽高
装饰容器(DecoratedBox)
const DecoratedBox({
Decoration decoration,
DecorationPosition position = DecorationPosition.background,
Widget child
})
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.red,Colors.orange[700]]), //背景渐变
border: Border.all(width: 2.0, color: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("Login", style: TextStyle(color: Colors.white),),
)
)
剪切(Clip)
剪裁Widget | 作用 |
---|---|
ClipOval | 子组件为正方形时剪裁为内贴圆形,为矩形时,剪裁为内贴椭圆 |
ClipRRect | 将子组件剪裁为圆角矩形 |
ClipRect | 剪裁子组件到实际占用的矩形大小(溢出部分剪裁) |
ClipOval(child: avatar), //剪裁为圆形
ClipRRect( //剪裁为圆角矩形
borderRadius: BorderRadius.circular(5.0),
child: avatar,
),
Transform 变换
略
布局组件
线性布局Row/Column
Row({
...
TextDirection textDirection,
MainAxisSize mainAxisSize = MainAxisSize.max,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
VerticalDirection verticalDirection = VerticalDirection.down,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
List<Widget> children = const <Widget>[],
})
textDirection
:表示水平方向子组件的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)。mainAxisSize
:表示Row
在主轴(水平)方向占用的空间,默认是MainAxisSize.max
,表示尽可能多的占用水平方向的空间,此时无论子widgets实际占用多少水平空间,Row
的宽度始终等于水平方向的最大宽度;而MainAxisSize.min
表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则Row
的实际宽度等于所有子组件占用的的水平空间;mainAxisAlignment
:表示子组件在Row
所占用的水平空间内对齐方式,如果mainAxisSize
值为MainAxisSize.min
,则此属性无意义,因为子组件的宽度等于Row
的宽度。只有当mainAxisSize
的值为MainAxisSize.max
时,此属性才有意义,MainAxisAlignment.start
表示沿textDirection
的初始方向对齐,如textDirection
取值为TextDirection.ltr
时,则MainAxisAlignment.start
表示左对齐,textDirection
取值为TextDirection.rtl
时表示从右对齐。而MainAxisAlignment.end
和MainAxisAlignment.start
正好相反;MainAxisAlignment.center
表示居中对齐。读者可以这么理解:textDirection
是mainAxisAlignment
的参考系。verticalDirection
:表示Row
纵轴(垂直)的对齐方向,默认是VerticalDirection.down
,表示从上到下。crossAxisAlignment
:表示子组件在纵轴方向的对齐方式,Row
的高度等于子组件中最高的子元素高度,它的取值和MainAxisAlignment
一样(包含start
、end
、center
三个值),不同的是crossAxisAlignment
的参考系是verticalDirection
,即verticalDirection
值为VerticalDirection.down
时crossAxisAlignment.start
指顶部对齐,verticalDirection
值为VerticalDirection.up
时,crossAxisAlignment.start
指底部对齐;而crossAxisAlignment.end
和crossAxisAlignment.start
正好相反;children
:子组件数组
Column与Row参数相同
⚠️如果Row
里面嵌套Row
,或者Column
里面再嵌套Column
,那么只有最外面的Row
或Column
会占用尽可能大的空间,里面Row
或Column
所占用的空间为实际大小。
如果要让里面的Column
占满外部Column
,可以使用Expanded
组件
Flex
Flex({
...
@required this.direction, //弹性布局的方向, Row默认为水平方向,Column默认为垂直方向
List<Widget> children = const <Widget>[],
})
Expanded可实现比例分配
//Flex的两个子widget按1:2来占据水平空间
Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
),
流式布局
Wrap({
...
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0, // 主轴方向子widget的间距
this.runAlignment = WrapAlignment.start, // 纵轴方向的对齐方式
this.runSpacing = 0.0, // 纵轴方向的间距
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})
Flow组件 略
层叠布局(Stack/Position)
Stack
允许子组件堆叠,而Positioned
用于根据Stack
的四个角来确定子组件的位置
Stack
Stack({
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
positioned
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
})
//通过ConstrainedBox来确保Stack占满屏幕
ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Stack(
alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式
children: <Widget>[
Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
color: Colors.red,
),
Positioned(
left: 18.0,
child: Text("I am Jack"),
),
Positioned(
top: 18.0,
child: Text("Your friend"),
)
],
),
);
对齐与相对定位(Align)
Align({
Key key,
this.alignment = Alignment.center,
this.widthFactor,
this.heightFactor,
Widget child,
})
Center 组件继承Align, alignment值为
Alignment.center
滚动组件
ListView
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.builder
child: ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
_buildListItem(data.[index]);
},
),
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>[],
}
SliverGridDelegateWithFixedCrossAxisCount
SliverGridDelegateWithFixedCrossAxisCount({
@required double crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.0,
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),
],
);
SliverGridDelegateWithMaxCrossAxisExtent
SliverGridDelegateWithMaxCrossAxisExtent({
double maxCrossAxisExtent, // 子元素在横轴上的最大长度
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
GridView.extent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0,
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),
],
);