1. BoxFit 各个值得含义
在使用 Image Widget 展示一张图片时,我们通过 fit 参数给它设置图片的拉伸规则,例如:
Image.asset('assets/top.jpeg',fit: BoxFit.cover),
BixFit 本身是一个枚举:
它是表示将一个 box 内置到另一个 box 中时,如何利用外层 box 的剩余空间。
enum BoxFit {
fill,
contain,
cover,
fitWidth,
fitHeight,
none,
scaleDown,
}
1.1 BoxFit.fill
充满父容器。为了适应父容器,宽和高有可能被拉伸或者压缩而导致变形。
1.2 BoxFit.contain
尽可能大,但同时保证不超过父容器的边界。如果子元素的宽和高不能与父容器的宽高匹配,那么子元素的左右两侧或者上下有可能留有空白区域。
1.3 BoxFit.cover
充满容器,可能会被截断。
1.4 BoxFit.fitWidth
图片填满宽度,高度可能会被截断。
1.5 BoxFit.fitHeight
图片填满高度,宽度可能会被截断
1.6 BoxFit.none
默认居中展示在容器的中间,如果超出父容器的宽高,那么超出的部分会被截断;如果没有超出父容器的宽高,直接居中展示。
1.7 BoxFit.scaleDown
默认居中展示在容器的中间,如果超出父容器的宽高,将其进行缩小,以保证全部展示,这点和 BoxFit.contain 一样;如果没有超出父容器的宽高,直接居中展示,这点和 BoxFit.none 一样。
2. Stateful and stateless widgets
有状态的 Widget 和无状态的 Widget 唯一的区别就是看它与用户交互以后,Widget 是否会发生变化。
比如用户可以进行勾选的 Checkbox
。
一个 widget 的状态保存在一个 State
对象中,它和 widget 的显示分离。 Widget 的状态是一些可以更改的值,如一个复选框是否被选中。当 widget 状态改变时,State 对象调用 setState()
,告诉框架去重绘 widget。
如何创建一个 StatefulWidget
需要创建两个类:一个 StatefulWidget 的子类和一个 State 的子类。
例子:点击收藏按钮,更新收藏数量
///创建 StatefulWidget 的子类
///
///FavoriteWidget 类管理自己的状态,因此它通过重写 createState() 来创建状态对象。框架会在构建 widget 时调用 createState()。
///在这个例子中,createState() 创建 _FavoriteWidgetState 的实例,将在下一步中实现该实例。
class FavoriteWidget extends StatefulWidget {
_FavoriteWidgetState createState() => _FavoriteWidgetState();
}
class _FavoriteWidgetState extends State<FavoriteWidget> {
//状态对象存储的信息在 _isFavorited 和 _favoriteCount 变量中。
bool _isFavorited = true;//是否收藏
int _favoriteCount = 41;//收藏数量
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.all(0),
child: IconButton(
padding: EdgeInsets.all(0),
alignment: Alignment.centerRight,
icon: (_isFavorited ? Icon(Icons.star) : Icon(Icons.star_border)),
color: Colors.red[500],
onPressed: _toggleFavorite,
),
),
SizedBox(
width: 18,
child: Container(
child: Text('$_favoriteCount'),
),
),
],
);
}
void _toggleFavorite() {
setState(() {
if (_isFavorited) {
_favoriteCount -= 1;
_isFavorited = false;
} else {
_favoriteCount += 1;
_isFavorited = true;
}
});
}
}
运行效果:
3. Flutter 中的页面指的是什么?
在 Flutter 中我们使用 screen
表示一个页面,它的本质就是一个 Widget
。
那什么是路由呢?
在 Android 开发中,Activity 相当于“路由”,在 iOS 开发中,ViewController 相当于“路由”。在 Flutter 中,“路由”也是一个 widget。
4. Flutter 页面之间如何传值?
在 Flutter 中从一个页面导航到另一个页面主要有两种方式:
- Navigator.push()
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
- 命名路由 Navigator.pushNamed()
onPressed: () {
Navigator.pushNamed(context, '/second');
}
使用不同的导航方式,传值的方式也不同,下面分别来说一下:
4.1 使用 Navigator.push() 传值的方式有两种
第一种:通过构造函数传值
先在目标页面定义好构造函数
class SecondScreen extends StatelessWidget {
//SecondScreen 页面需要一个 title 参数
SecondScreen({
Key key, this.title}) : super(key: key);
...
}
传值:
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(title: '参数',)
)
)
}
第二种:使用 RouteSettings 传递参数
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteManager(),
settings: RouteSettings(arguments: '参数')
)
)
接收参数:ModalRoute.of(context).settings.arguments
class SecondScreen extends StatelessWidget {
Widget build(BuildContext context) {
//接收参数
final String title = ModalRoute.of(context).settings.arguments;
// Use the title to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(title),
),
);
}
}
4.2 使用 Navigator.pushNamed() 时的传值方式
Navigator.pushNamed(
context,
'路由名称',
arguments:'参数'
)
接收参数同样可以用:ModalRoute.of(context).settings.arguments
4.3 适配
那么这里有一个问题,假如一个页面,它的构造函数必须接收一个参数,同时呢,我也想用命令路由的方式跳转到该页面,应该怎么做呢?
假设这个页面是:
class SecondScreen extends StatelessWidget {
//SecondScreen 页面需要一个 title 参数
SecondScreen({
Key key, this.title}) : super(key: key);
final String title;
...
}
路由注册方式:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
//省略无语代码
...
//注册路由表
routes: {
'second': (context) => SecondScreen(
title: ModalRoute.of(context).settings.arguments,
),
},
);
}
}
4.4 从一个页面回传数据
上面介绍了跳转一个新路由时如何传值,那么如果我返回上一个页面的话,如果回传数据呢?
Navigator.pop(context, '回传的值');
接收上个页面返回的值,并通过一个 SnackBar 来显示:
void _navigateAndDisplay(BuildContext context) async {
//导航并接收结果
final result =
await Navigator.pushNamed(context, 'second', arguments: '参数');
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
5. 跨页面切换的动效 Widget (Hero animations)
如何在页面切换时为某个组件加上转场动画,从而在两个页面间建立视觉上的锚定关联。如下如所示:
Flutter 为我们提供了 Hero
animations 来实现:
代码示例:
///第一个页面
class HeroDemoScreen extends StatelessWidget {
static const routeName = '/HeroDemoScreen';
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Hero animations'),
),
body: GestureDetector(
child: Hero(
tag: 'imageHero',
child: Image.network('https://picsum.photos/250?image=9')),
onTap: () => {
Navigator.pushNamed(context, ImageScanScreen.routeName)},
),
);
}
}
///第二个页面
class ImageScanScreen extends StatelessWidget {
static const routeName = '/ImageScanScreen';
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('查看图片'),
),
body: GestureDetector(
child: Center(
child: Hero(
tag: 'imageHero',
child: Image.network('https://picsum.photos/250?image=9')),
),
onTap: ()=>{
Navigator.pop(context)
},
),
);
}
}
为了通过动画在两个页面间建立联系,需要把每个页面的 Image 组件都包裹进 Hero 组件里面。 Hero 组件有两个参数:
tag
作为Hero
组件的标识,在这两个页面中必须相同。child
在两个屏幕直接跨越的那个 widget,在本例中就是 Image。
Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
);