阅读原文️前端
Flutter 目前仍是 Beta 3 版本,1.0 版本还在路上。不过它在 React Native/weex等跨平台方案以外,又为咱们提供了一种跨平台的方案。并且其自身的许多特性,也为咱们扩展了新的视野。若是
Fuchsia
系统最终能和 iOS、Android 成三足鼎立之式,甚至于取代 Android,那么 Flutter 就能为咱们带来更多的可能。因此如今了解一下仍是有必要的。 本文将经过一个简单的实例(知识小集 Flutter 版本客户端,咱们后期会慢慢优化),同时半翻译半参考Raywenderlich
上的 Getting Started with Flutter 这篇文章,来一步步了解如何使用 Flutter 构建 App。android
在这个 App 的开发过程当中,咱们将学习如下关于 Flutter 的内容:ios
在这个过程当中,咱们将同时学习一些 Dart 相关的知识。项目的完整代码在 Github 上能够找到。git
咱们能够在 macOS
、Linux
或者 Windows
上开发 Flutter 应用。目前 Flutter 团队为一些 IDE 开发了相应的插件,这些 IDE 包括 IntelliJ IDEA
、Android Studio
和 Visual Studio Code
。个人开发环境主要为 macOS + Visual Studio Code,因此本文主要基这二者来进行描述。github
实际的配置过程能够参考官方文档 Get Started: Install on macOS。具体的步骤各个平台稍有不一样,但主要是如下几步:macos
git
库;bin
目录到咱们指定的目录;flutter doctor
命令,这个命令将告诉咱们缺乏哪些依赖;须要注意的是,若是想在 iOS 模拟器或 iOS 设备上构建和测试应用,咱们须要使用 macOS 系统,同时须要安装
Xcode 9.0+
。json
在安装了 Flutter 插件的 VS Code 中,咱们能够经过 View > Command Palette...
或者快捷键 cmd+shift+p
来打开 命令面板(command palette),而后输入 Flutter:New Project
并回车:小程序
为工程取名为 awesome_tips_flutter
,并回车。选择一个目录来存储工程,而后等待 Flutter 配置好工程。配置的过程主要有几个步骤:api
flutter packages get
命令来获取依赖包;flutter doctor
命令来检测依赖包;如图是构建过程的部分信息:xcode
工程建立完成后,IDE 会默认打开 lib
目录下的 main.dart
文件,这也是咱们 App 的入口。
注意:从 Flutter Beta 3 开始,建立 Widget 时,
new
关键字是可选的。目前我这生成的模板代码部分仍是带 new 关键字的。
在左侧的工程目录中,咱们能够看到 ios
、android
、lib
这些目录,lib 目录下的代码将应用于两个平台,目前咱们也主要是在这个目录下工做。
为了构建咱们本身的应用,先删除 main.dart 中现有的代码,并用以下代码替代:
import 'package:flutter/material.dart';
void main() => runApp(new AwesomeTips());
class AwesomeTips extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Awesome Tips',
home: Scaffold(
appBar: AppBar(title: Text('Awesome Tips')),
body: Center(
child: Text('Awesome Tips'),
)
)
);
}
}
复制代码
顶部的 main() 函数使用 =>
操做符来指定单行函数的函数体(相似于 ES6 中的箭头函数),并运行 App。runApp
的参数是咱们的 AwesomeTipsApp
类(根 Widget)。
在这里,咱们的 AwesomeTipsApp 类继承自 StatelessWidget
。Flutter 中大部分实体都是 Widget,或者是无状态的(stateless),或者是有状态的(stateful)。咱们重写 Widget 的 build()
方法来构建自定义的 App Widget。
咱们先来运行一下这个 App。首先启动 iOS 模拟器。选择菜单 Debug -> Start Debugging
构建并运行工程。能够看到 VS Code 打开了 Debug Console
(调试控制台) 面板,同时 xcode-builder
开始构建并启动 App。初始效果以下图:
同时,咱们能够在 VS Code 顶部看到一个调试工具栏,咱们能够经过这个工具栏来中止或者从新加载 App。
Flutter 开发最吸引人的一个方面就是当程序代码更改时,能够自动执行 Hot Reload
操做,来从新加载 App。咱们来试试这个特性,对咱们的程序作个小小的修改:
appBar: AppBar(title: Text('Awesome Tips for Test')),
复制代码
在咱们保存文件时,VS Code 会自动启动 Hot Reload 功能,加载完成后,模拟器会显示新的内容。固然咱们也能够手动点击调试工具栏上的 Hot Reload 按钮来启动热加载。来看看效果。
注:因为 Flutter 仍是 Beta 版,因此 Hot Reload 并不老是能正常工具。我就遇到了相似
Request to Dart VM Service timed out: _flutter.listViews({})
这样的问题,解决方法是重启 Debug。
一般咱们都不但愿在一个文件中放入大量的代码,而是将代码分散在不一样的文件中,并经过必定的方式将这些文件组织起来。而后若是一个文件须要用到其它文件的类或方法,只须要导入相关文件便可。在一个 Dart 文件中,咱们能够经过 import
关键字来实现这一目标。
好比上面代码中,咱们但愿将字符串统一放在一个文件中来管理,那么能够建立一个 strings.dart
文件。在 lib 目录处点击右键,会弹出菜单,选择 New File
,并输入文件名。
在 string.dart
中添加如下代码:
class Strings {
static String appTitle = "Awesome Tips";
}
复制代码
而后在 main.dart
中经过如下方式导入:
import 'strings.dart';
复制代码
如今就能够在 AwesomeTipsApp 中使用 appTitle
了:
class AwesomeTipsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Strings.appTitle,
home: Scaffold(
appBar: AppBar(title: Text(Strings.appTitle)),
body: Center(
child: Text(Strings.appTitle),
)
)
);
}
}
复制代码
在 Flutter App 中,几乎全部的界面元素都是 Widget。Widget 被设计成是不可变的(immutable),由于这样可让 App 的 UI 轻量化。咱们可使用两种类型的 Widget:
State
对象交互。两种类型的 Widget 都会在 Flutter App 的每一帧进行重绘,不一样的是 Stateful Widgets 会将其配置交给 State 对象来管理。关于 Flutter 界面开发,能够参考阿里闲鱼团队 的**《深刻了解Flutter界面开发》**一文。
咱们如今来建立一个 Widget 展现列表。在 lib 目录中新建文件 content_list.dart
,在文件中加入以下代码:
import 'package:flutter/material.dart';
class ContentList extends StatefulWidget {
@override
createState() => _ContentListState();
}
复制代码
这里咱们建立了 StatefulWidget
的一个子类 ContentList
并重写了 createState()
方法,该方法返回 ContentList 对应的 State 对象。而后咱们在同一文件中添加如下代码:
class _ContentListState extends State<ContentList> {
}
复制代码
_ContentListState
继承自泛型参数为 ContentList 的 State 对象。在 _ContentListState 中,咱们的主要工做就是重写 build()
方法,这个方法在 Widget 被渲染到屏幕上时会调用。目前咱们尚未涉及到数据的处理,因此暂时和以前同样,在 ContentList 中显示一个简单的文本。在 build() 方法中添加如下代码:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Strings.appTitle)),
body: Text(Strings.appTitle),
);
}
复制代码
Scaffold
类是Material Design Widgets
的容器。它一般做为 Widget 层级的根。
上面的代码咱们添加了一个 AppBar 和一个 body 到 Scaffold 中。接下来咱们用这个 ContentList Widget 替换 main.dart 中的 home
属性的内容:
import 'content_list.dart';
void main() => runApp(AwesomeTipsApp());
class AwesomeTipsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Strings.appTitle,
home: ContentList(), // 替换此处内容
);
}
}
复制代码
编译运行程序,获得的结果和上面差很少。
咱们最终要展现的是知识小集的内容清单,因此须要从服务器上获取到清单内容,并转换成咱们须要的 Dart 对象。这里咱们须要用到两个库:
package:http/http.dart
:负责网络请求,从服务端获取数据;dart:convert
:将服务端返回的字符串转换成 JSON
对象;咱们在 main.dart 中导入这两个模块:
import 'package:http/http.dart';
import 'dart:convert';
复制代码
须要注意的是:Dart 应用是单线程的,可是 Dart 支持代码运行在其它线程上,同时也支持使用
async/await
模式让代码异步执行,而不会阻塞 UI 线程。
接下来咱们须要经过异步网络调用来获取知识小集的内容列表。首先咱们在 _ContentListState 类的顶部添加一个空列表属性,用于保存内容清单:
var _items = [];
复制代码
Dart 语言中,若是属性/方法名是以_开头,则表示这个属性/方法是类私有的。
而后添加一个 _loadData() 方法,咱们在这作网络请求:
void _loadData() async {
String dataURL =
"https://app.kangzubin.com/iostips/api/feed/list?page=1&from=flutter-app&version=1.0";
http.Response response = await http.get(dataURL);
// ...
}
复制代码
这里咱们在 _loadData() 后面加上 async
关键字,用于告诉 Dart 这是一个异步方法,同时在 http.get
前使用 await
关键字,来阻塞后面的代码执行。当 HTTP 调用完成后,服务端返回的是一个 JSON
字符串,具体结构以下:
{
"code": 0,
"msg": "SUCCESS",
"data": {}
}
复制代码
对于 feed/list
接口,其 data
中的结构以下:
"data": {
"feeds": [{
"fid": "96",
"auther": "halohily",
"title": "如何重写自定义对象的 hash 方法",
"url": "https://weibo.com/3656155132/GfEGebnEN",
"platform": "0",
"postdate": "2018-05-08"
}, {
"fid": "95",
"auther": "南峰子",
"title": "微博一周推送",
"url": "https://weibo.com/3321824014/GfviNzT3z",
"platform": "0",
"postdate": "2018-05-07"
}]
}
复制代码
在获取到 JSON 字符串后,咱们首先须要将其转换成 JSON 对象,而后根据 code 是否为 0 作处理。若是请求成功,则须要从 data
中取出 feeds
的数据。同时,咱们但愿将 feed 数据转换成一个 Dart 对象,因此咱们建立一个 feed.dart
文件,并添加以下代码:
class Feed {
final String author;
final String title;
final String postdate;
Feed(this.author, this.title, this.postdate);
}
复制代码
而后咱们就能够对返回的数据作处理,将每一条 feed 转换成一个 Feed
对象,并存储在 _items 中。完整的 _loadData() 代码以下所示:
void _loadData() async {
String dataURL =
"https://app.kangzubin.com/iostips/api/feed/list?page=1&from=flutter-app&version=1.0";
http.Response response = await http.get(dataURL);
final body = JSON.decode(response.body);
final int code = body["code"];
if (code == 0) {
final feeds = body["data"]["feeds"];
var items = [];
feeds.forEach((item) =>
items.add(Feed(item["author"], item["title"], item["postdate"])));
setState(() {
_items = items;
});
}
}
复制代码
若是咱们但愿在状态改变时,触发界面从新渲染,则须要调用 setState() 方法来设置咱们的属性值。
有了加载数据的方法,咱们就须要在合适的位置来调用。咱们暂且在 _ContentListState 类中重写 State 的 initState()
方法,以下所示:
@override
void initState() {
super.initState();
_loadData();
}
复制代码
Widget 生命周期相关的内容,咱们有机会再讲。
至此,咱们已经有了列表数据,接下来就须要将数据显示在界面上了。Flutter 提供了 ListView
Widget 来显示一个列表,这个 Widget 能很流畅地展现列表内容。
咱们先在 _ContentListState 类中添加一个私有方法 _buildRow()
,以建立显示单元格的 widget:
Widget _buildRow(int i) {
Feed feed = this._items[i];
return ListTile(
title: Text(
feed.title,
overflow: TextOverflow.fade,
),
subtitle: Text(
'${feed.postdate} @${feed.author}',
));
}
复制代码
咱们暂且返回一个 ListTile
来显示内容的标题及发布日期和做者。接下来咱们修改 build()
方法中 Scaffold 的 body:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Strings.appTitle)),
body: new ListView.builder(
padding: const EdgeInsets.all(13.0),
itemCount: _items.length * 2,
itemBuilder: (BuildContext context, int position) {
// 此处为添加分割线
if (position.isOdd) return Divider();
final index = position ~/ 2;
return _buildRow(index);
},
),
);
}
复制代码
在这段代码中,咱们经过 ListView.builder
来建立一个 ListView,并经过参数来配置列表的显示。这里咱们没有处理单元格点击等事件,后续咱们会作改进。
OK,保存代码,Hot Reload 后的效果以下:
很简单吧?这样,咱们的任务基本完成。
这里咱们只是获取了第1页的数据,分页处理后续再完善。
最后咱们来看看如何为 App 添加主题。能够说这很容易,只须要设置 main.dart 中 MaterialApp 的 theme
属性,咱们来试试:
class AwesomeTipsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Strings.appTitle,
theme: ThemeData(primaryColor: Colors.red.shade800),
home: ContentList(),
);
}
}
复制代码
咱们使用了 Material Design
颜色值来设置主题颜色,效果以下:
在本文中,咱们经过一个简单的例子来了解了一下若是使用 Flutter 来构建 App,能够在 awesome-tips-flutter-app 下载完整的示例代码。固然,构建一个完整的 App 还须要作不少事情,还有许多技术学习。后期咱们会逐步来完善这个 App,并让其达到上线的标准,最终发布到应用市场上。
为了更方便你们获取 Flutter 相关的开发资源,咱们在 Github 上开了一个 repo flutter-resources,欢迎你们一块儿来维护这个 repo。
知识小集是一个团队公众号,主要定位在移动开发领域,分享移动开发技术,包括 iOS、Android、小程序、移动前端、React Native、weex 等。每周都会有 原创 文章分享,咱们的文章都会在公众号首发。欢迎关注查看更多内容。