widgets简介
Flutter widget创建于源自React的灵感的现代框架,其核心思想便是使用widgets来创建你的用户界面。widget描述了赋予配置和状态的它们应当展现的样子。当一个widget的状态发生改变,widget将根据其描述进行重构,框架将会与之前的描述进行对比,以便确认widget从一种状态转向另一种状态从底层渲染所需的最小更改。
注意: 如果你想要通过源代码来更深入地了解Flutter,可通过 基础布局指引,布局创建,以及 为你的Flutter App添加交互 进行。
Hello world
一个widget仅调用了runApp()函数的最小的Flutter App
import 'package:flutter/material.dart';
void main() {
runApp(
Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
runApp函数接收给定的widget(在这里是Center)并作为wiget的根节点。在上面的示例中,widget树包含有两个widget,Center以及它的子级widget:Text。框架强制根级widget全屏,这意味着文本“Hello,world!”将位于屏幕的正中央。文本显示方向在这个实例中需要被指定;倘若使用的是MaterialAPP widget,那么文字方向将会默认为从左至右,稍后会再做演示。
当你需要编写一个app时,通常需要编写父类为StatelessWidget或StatefulWidget的widget,这取决与你的widget是否需要管理状态。一个widget的主要任务是要实现build()函数,使用基础widgets以描述自身的各个方面。框架将按次序构建widgets,直到渲染到基于RenderObject的最底部widget,它会计算并描述出widget的几何构成。
基础widgets(部件)
Flutter提供了如下一套通用的、强大的部件(widget):
Text部件允许你在应用中创建带有样式的文字内容。
这两个widget使你可以创建水平以及垂直方向的弹性布局。这种设计是基于web技术下的flexbox布局模式。
用以替代线性方向(水平或垂直),Stack widget允许子级部件(widget)进行堆叠。你可以使用Positioned
来描述子级widget相对于Stack widget上下左右位置。Stack布局设计基于web技术的绝对定位布局模式。
Container 部件使你可以创建一个矩形的可视化元素。一个Container能够使用BoxDecoration进行修饰,例如北京、边框、或阴影。Container同时还具有边距(margins)、填充(padding)以及大小约束。另外,Container还能够使用矩阵在三维空间进行转换。
以下是一些使用这些widgets和其他widgets的综合示例
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Fields in a Widget subclass are always marked "final".
final Widget title;
@override
Widget build(BuildContext context) {
return Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(color: Colors.blue[500]),
// Row is a horizontal, linear layout.
child: Row(
// <Widget> is the type of items in the list.
children: <Widget>[
IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child to fill the available space.
Expanded(
child: title,
),
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Material is a conceptual piece of paper on which the UI appears.
return Material(
// Column is a vertical, linear layout.
child: Column(
children: <Widget>[
MyAppBar(
title: Text(
'Example title',
style: Theme.of(context).primaryTextTheme.headline6,
),
),
Expanded(
child: Center(
child: Text('Hello, world!'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(
title: 'My app', // used by the OS task switcher
home: MyScaffold(),
));
}
确保在你的pubspec.yaml文件的flutter节中具有 uses-material-design: true
键值对,这将允许你使用 Material icons(一套谷歌的图标集合) 的预定义设置
name: my_app
flutter:
uses-material-design: true
许多 Material Design widgets 需要位于MaterialApp
来体现其特性,以内置其theme data。因此需要以 MaterialApp 来运行此应用(参照上面的综合示例中的 runApp(MaterialApp....)
MyAppBar widget 创建了一个内部左右padding为8像素,高56逻辑像素(独立于设备的)Container.在Container内部,MyAppBar使用了Row 布局来组织其子项。位于其中间的子项,以Expanded作为title widget的标记,意味着它将随着自身的内容填充满未被其他子项填充的空间。。你可以有多个Expanded的子项,并使用flex参数来限定每个子项展开大小的比重。
MyScaffold widget使用纵向column组织其子项。在column的顶部放置了一个MyAppBar的实例,传给程序栏一个Text widget作为其标题。给其他widget传递widget作为参数是一项强大的技术,它使你可以创建能够广泛服用的通用部件(widgets)。最终,MyScaffold使用了一个Expanded在它的body中来填充多余的空间。它由一个居中的消息内容构成。
想要学习更多的内容,请查看 Layouts 布局介绍。
使用 Material 组件
Flutter提供了一套遵循Material Design风格的为您创建app提供巨大帮助的widget。一个Material app起始于MaterialApp
部件,一个位于你的app根部节点的由诸多有用部件组成的widget,包括管理由字符串标识的widget堆栈 Navigator
,或者我们称之为“routes”(路由)。Navigator使我们能够实现在app中平滑得进行页面跳转。是否使用MaterialApp
是完全可选的,但它也是能够提供非常好的体验。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Flutter Tutorial',
home: TutorialHome(),
));
}
class TutorialHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null,
),
title: Text('Example title'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
// body is the majority of the screen.
body: Center(
child: Text('Hello, world!'),
),
floatingActionButton: FloatingActionButton(
tooltip: 'Add', // used by assistive technologies
child: Icon(Icons.add),
onPressed: null,
),
);
}
}
现在,这些源码从MyAppBar和MyScaffold切换到了AppBar和Scaffold widgets,其引用了 material.dart
,应用看起来更加扁平化。例如,应用栏具有了阴影而且标题文本也自动且正确地位于其内部。并且一个浮动操作按钮也被添加了上去。
请注意,这些widgets被作为参数传递给了其他idgets,Scaffold
widget使用了很多不同的widgets作为其命名的参数,每个widget都位于Scaffold布局中恰当的位置。同样的,AppBar
widget是你传入了一些widget作为头部widget和标题的操作设置。这种方式在框架中十分常见,在设计属于自己的widgets时,希望你能够多考虑这一点。
更多信息,请查阅Material Components widgets。
注意:Material是flutter包含的两种设计方式的其中之一,如若是要创建ios为主的app设计,请查阅 Cupertino components 包,它具有单独的CupertinoApp
, 和CupertinoNavigationBar
版本。
手势处理
大多数应用,都具有不同形式的系统交互方式。创建一个具有交互性的应用的第一步,就是检测用户的手势输入。这里我们来看一下如何通过创建一个简单的按钮来实现它。
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Container(
height: 36.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: Center(
child: Text('Engage'),
),
),
);
}
}
GestureDetector
widget不具有外观,它只是用来监视用户的手势输入。当用户点击Container
,GestureDetector就会调用它的onTap()
回调函数,在这个示例会在控制台中打印出一条消息。你能使用GestureDetector来检测许多输入手势,包括点击、拖拽以及缩放。
大多数widget使用GestureDetector
来为之提供可选的回调功能。例如 IconButton
, RaisedButton
, 以及FloatingActionButton
具有onPressed()
回调则由用户点击widget来触发。
更多信息,请查阅Gestures in Flutter(Flutter中的手势).
widget响应输入
到目前为止,本章节还只有使用过无状态的widget。无状态widget从它的父级widget接收存储于常量中的参数。当一个widget被请求创建:build()
,它将使用这些参数为其新创建的widget派生新的参数。
为了创建更加复杂的场景,例如,为响应更有趣的用户录入的方式,应用程序通常带有一些状态。Flutter使用StatefulWidgets来抓住这种想法。StatefulWidgets是一组知道如何生成状态对象的特殊的widgets,它们被用来保持状态。仔细查看以下例子,使用前面提到过的RaisedButton
class Counter extends StatefulWidget {
// This class is the configuration for the state. It holds the
// values (in this case nothing) provided by the parent and used
// by the build method of the State. Fields in a Widget
// subclass are always marked "final".
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
// This call to setState tells the Flutter framework that
// something has changed in this State, which causes it to rerun
// the build method below so that the display can reflect the
// updated values. If you change _counter without calling
// setState(), then the build method won't be called again,
// and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called,
// for instance, as done by the _increment method above.
// The Flutter framework has been optimized to make rerunning
// build methods fast, so that you can just rebuild anything that
// needs updating rather than having to individually change
// instances of widgets.
return Row(
children: <Widget>[
RaisedButton(
onPressed: _increment,
child: Text('Increment'),
),
Text('Count: $_counter'),
],
);
}
}
你可能会好奇为什么StatefulWidget和State是两个分离的对象,在Flutter中,这两个类型的对象具有不同的生命周期。Widgets是临时对象,用于构造应用程序当前状态的表示形式。State对象在另一方面,在build()调用之间它是持久的,允许它们记住信息。
上面的示例允许用户输入并直接将其结果应用于build()方法。在更多复杂的应用当中,不同层次结构的widget的不同部分,有可能需要负责不同的关注点(切面?)。例如,一个widget也许具有复杂的用户界面来收集信息,例如日期或地点,而其他的widget有可能使用这些信息来修改总体介绍。
在Flutter中,通过回调将通知流“上移”到widget的层次,而当前状态流则被下移到无状态widget来做演示。重定向数据流便是state。下面的稍复杂一些的示例将展示它的运作方式。
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
CounterIncrementor({this.onPressed});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: onPressed,
child: Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return Row(children: <Widget>[
CounterIncrementor(onPressed: _increment),
CounterDisplay(count: _counter),
]);
}
}
注意新创建的两个无状态wigets,清晰地分离了计数器的关注点:显示计数 CounterDisplay
和修改计数 CounterIncrementor
,尽管其最终结果于之前的示例相同。职责分离允许将更复杂的特性封装于单个widget中,同时又保持了其在父级widget的简单性。
更多信息,请查阅:
将这些内容集合在一起
- [ ] 未完待续
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。