万事皆 Widget
Widget 是每个 Flutter 应用的基础。每个 Widget 是一部分用户界面上不可变的定义。和其他框架把 View、controller、 Layout 和其他资源分开定义不一样,Flutter 具有一致的、唯一的对象模型: Widget。
一个 Widget 可以定义:
- 一个结构性的元素(比如 按钮或者菜单)
- 一个元素的风格(比如 字体或者颜色)
- 指定布局属性(比如 padding)
- 也可以包含一些业务逻辑
- 以及其他…
组合大于继承(Composition > inheritance)
Widget 通常通过组合的方式来构建复杂的 UI。例如,常用的 Container Widget 就是由几个分别负责 布局、绘制、布局和计算大小的 Widget 组成。具体来说,Container 由
- LimitedBox,
- ConstrainedBox,
- Align,
- Padding,
- DecoratedBox,
- Transform
等widget 组成。如果要自定义 Container 来实现自定义效果,相比使用继承而言,可以使用组合一些简单的 Widget 实现自定义效果。
[图片上传失败...(image-84ef2a-1543882561066)]
基础 Widget
主要文章: widget概述-布局模型
Flutter有一套丰富、强大的基础widget,其中以下是很常用的:
Text:该 widget 可让创建一个带格式的文本。
Row、 Column: 这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于web开发中的Flexbox布局模型。
Stack: 取代线性布局 (译者语:和Android中的LinearLayout相似),Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝度定位(absolute positioning )布局模型设计的。
Container: Container 可让您创建矩形视觉元素。container 可以装饰为一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。
flutter提供了友好的Widget 的查找地址,可以方便的找到自己组要的组件:
Stateful(有状态) 和 Stateless(无状态) widgets
什么是重点?
- 有些widgets是有状态的, 有些是无状态的
- 如果用户与widget交互,widget会发生变化,那么它就是有状态的.
- widget的状态(state)是一些可以更改的值, 如一个slider滑动条的当前值或checkbox是否被选中.
- widget的状态保存在一个State对象中, 它和widget的布局显示分离。
- 当widget状态改变时, State 对象调用setState(), 告诉框架去重绘widget.
stateless widget 没有内部状态. Icon、 IconButton, 和Text 都是无状态widget, 他们都是StatelessWidget的子类。
stateful widget 是动态的. 用户可以和其交互 (例如输入一个表单、 或者移动一个slider滑块),或者可以随时间改变 (也许是数据改变导致的UI更新). Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 stateful widgets, 他们都是 StatefulWidget的子类。
创建一个有状态的widget
- 要创建一个自定义有状态widget,需创建两个类:StatefulWidget和State
- 状态对象包含widget的状态和build() 方法。
- 当widget的状态改变时,状态对象调用setState(),告诉框架重绘widget
State的状态管理
- 有多种方法可以管理状态.
- 选择使用何种管理方法
- 如果不是很清楚时, 那就在父widget中管理状态吧.
一下是常见状态管理的几种方法
如何决定使用哪种管理方法?以下原则可以帮助您决定:
- 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父widget管理
- 如果所讨论的状态是有关界面外观效果的,例如动画,那么状态最好由widget本身来管理.
- 如果有疑问,首选是在父widget中管理状态
Flutter通信机制 使用平台通道编写平台特定的代码
平台方法调用
平台方法调用主要分四步:
- 定义好交互的协议名称以及参数名称(channelName 、methodName、parameter)
- 对应平台工程添加对应的平台调用代码
- 实现MethodChannel.MethodCallHandler接口
- onMethodCall() 中根据参数做对应处理
- 对应平台界面对应位置注册
- flutter中调用
- 创建MethodChannel(channelName)
- MethodChannel.invoke(methodName)
平台事件调用
平台事件调用和方法调用类似,步骤如下:
- 定义好交互的协议名称以及参数名称(eventlName)
- 对应平台工程添加对应的平台调用代码
- 实现EventChannel.StreamHandler接口
- 在onListen中的EventChannel.EventSink eventSink可以用来发送事件的结果
- 对应平台界面对应位置注册
- flutter注册事件
- 创建EventChannel(eventlName)
- 开始监听eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
除了上面提到的MethodChannel,你还可以使用BasicMessageChannel,它支持使用自定义消息编解码器进行基本的异步消息传递。 此外,您可以使用专门的BinaryCodec,StringCodec和 JSONMessageCodec类,或创建自己的编解码器。
Flutter 图片
在Flutter中系统为我们提供了可以加载图片的控件Image,Image 控件提供了如下几种用于加载不同方式的图片。
- new Image, 用于从ImageProvider获取图像。
- new Image.asset, 用于从AssetBundle获取图像。
- new Image.network, 用于从URL获取图像。
- new Image.file, 用于从文件中获取图像。
- new Image.memory, 用于从内存中获取图像
在flutter中Image支持JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, 和 WBMP这几种图片格式。
今天我们主要介绍下两种常用的方式,从asset目录和从network获取图片
从asset目录加载图片
1. 新建存放图片的目录
想要使Image加载asset加载apk内部的图片,首先需要在项目中建立放置图片的目录(名称随意,不要中文就好)
我们在lib目录的同级目录建立images文件夹,并在里面放置了一个cover.jpg的图片
2. 更细配置文件pubspec.yaml
在flutter节点下新增 文件声明 ,如下所示
flutter:
assets:
- images/cover.jpg
3. 加载目录中的图片文件
new Image.asset("images/helloflutter.png")
从network获取图片
基本上和上面asset的参数相同,只不过从网络获取的方式可以传入请求头。
但是,从我网络获取图片展示的调用方式就比从asset读取显示方便的多,只需要一行代码就可以搞定
把Scaffold body的参数写为:
new Image.network("http://pic1.win4000.com/wallpaper/2017-10-25/59f083092ed4f.jpg")
Flutter 网络
注:本篇文档官方使用的是用dart io中的HttpClient发起的请求,但HttpClient本身功能较弱,很多常用功能都不支持。我们建议您使用dio 来发起网络请求,它是一个强大易用的dart http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载……详情请查看github dio
处理异步
注意,HTTP API 在返回值中使用了Dart Futures。 我们建议使用async/await语法来调用API。
网络调用通常遵循如下步骤:
- 创建 client.
- 构造 Uri.
- 发起请求, 等待请求,同时您也可以配置请求headers、 body。
- 关闭请求, 等待响应.
- 解码响应的内容.
get() async {
var httpClient = new HttpClient();
var uri = new Uri.http(
'example.com', '/path1/path2', {'param1': '42', 'param2': 'foo'});
var request = await httpClient.getUrl(uri);
var response = await request.close();
var responseBody = await response.transform(UTF8.decoder).join();
}
Flutter给我们提供了第三发库的支持,同样的下面三个操作
- 打开项目的pubspec.yaml配置我文件在dependencies:节点下新增如下配置
http: ^0.11.3+16 - 点击开发工具提示的packages get按钮或者在命令行输入flutter packages get来同步第三方插件
- 在自己的Dart文件中引入插件即可正常使用了
- import ‘package:http/http.dart’ as http
var url = "http://example.com/whatsit/create";
http.post(url, body: {"name": "doodle", "color": "blue"})
.then((response) {
print("Response status: ${response.statusCode}");
print("Response body: ${response.body}");
});
官网还退出了一个网络第三方库 github dio
Dio dio = new Dio();
_loadDataByDio() async {
try {
Response response = await dio.get("https://news-at.zhihu.com/api/4/news/latest");
if (response.statusCode == HttpStatus.ok) {
_result = response.data.toString();
} else {
_result = 'error code : ${response.statusCode}';
}
} catch (exception) {
print('exc:$exception');
_result = '网络异常';
}
setState(() {});
}
Flutter路由
静态路由
在Flutter中有着两种路由跳转的方式,一种是静态路由,在创建时就已经明确知道了要跳转的页面和值。另一种是动态路由,跳转传入的目标地址和要传入的值都可以是动态的。
OK,还是先来介绍下静态路由
从我们开始学习Flutter到现在,相信大家看到最多的肯定是下面的代码
void main() {
runApp(new MaterialApp(
home: new MyApp(),
routes: <String, WidgetBuilder>{
'/page2': (BuildContext context) => new Page2("requestString"),
},
));
}
routes: const {}
routes需要传入类型的Map,第一个参数是目标路由的名称,第二个参数就是你要跳转的页面。
这种定义路由并使用的方式非常的简单,但是大家肯定会发现一个问题,就是如果我需要传递给第二个页面的数据不是已知的话我就无法使用这种方式,因为我们无法动态改变上面定义的值。
所以,我们就需要了解下Flutter中的动态路由了。
动态路由
在Navigator中还有一个方法是push()方法,需要传入一个Route对象,在Flutter中我们可以使用PageRouteBuilder来构建这个Route对象。
Navigator.of(context).push(new PageRouteBuilder(
pageBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation) {
return new Page2("some attrs you like ");
}))
这样的话,我们就可以把用户操作与交互的数据传递给下个页面。
页面出栈
在Flutter中我们可以使用Navigator.of(context).pop()进行出栈操作,但是值得注意的时如果页面上有Dialog、BottomSheet、popMenu类似的Widget使用pop()方法会优先进行这些Widget的关闭操作。
处理出栈页面返回值
在前面我们介绍到Navigator.of(context).pop()可以使得页面出栈,当然这个pop方法也是可以传值的,只用Navigator.of(context).pop(attrs)就可以传入自己想要返回的值
Future future = Navigator.of(context).pushNamed("/pageB");
future.then((value) {
showDialog(
context: context,
child: new AlertDialog(
title: new Text(value),
));
}
小结
- 使用Navigator.of(context).pushName(“/“)可以进行静态路由的跳转
- 使用push(Route)可以进行态路由的跳转
- 动态路由可以传入未知数据
- 使用pop()可以进行路由的出栈并且可以传递参数
- 可以使用Future接收上个页面返回的值。
Flutter插件
官网提供了很多好用的插件,插件地址
- flutter_image
- 使用NetworkImageWithRetry 代替Image.network 加载网络图片可获得重试能力。
- barcode_scan
- 一个可以扫描二维码和条形码的flutter插件。
- intl
- 该插件提供国际化和本地化设施,包括消息翻译,复数和性别,日期/数字格式和解析以及双向文本。
- location
- 这个插件 能够处理Android和iOS设备的位置。它还提供位置更改时的回调。
等等。。。其他科自行搜索使用。
如果你想开发一个新的插件可以参考开发Packages和插件