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 {
@override
_FavoriteWidgetState createState() => _FavoriteWidgetState();
}
class _FavoriteWidgetState extends State<FavoriteWidget> {
//状态对象存储的信息在 _isFavorited 和 _favoriteCount 变量中。
bool _isFavorited = true;//是否收藏
int _favoriteCount = 41;//收藏数量
@override
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, @required 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 {
@override
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, @required this.title}) : super(key: key);
final String title;
...
}
路由注册方式:
class MyApp extends StatelessWidget {
@override
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';
@override
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';
@override
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',
),
);