Dart4Flutter - 01 – 变量、类型和函数git
Dart4Flutter – 02 –控制流 和异常github
Dart4Flutter - 拾遗01 - flutter-dart环境搭建windows
Flutter 入门-本地访问-MethodChannelless
Flutter 实例 - 从本地到Flutter通讯 - Event Channels
自从10年前Android和iOS出现,在移动开发界,跨平台开发一直是一个目标。一个跨平台的应用能够帮助公司和团队节省大量的时间和精力。
过去这些年,已经发布了不少跨平台的开发工具,包括来自adobe基于web的PhoneGap,来自Microsoft的Xamarin,来自Facebook的React Native。
最近进入跨平台界的工具是来自google的flutter,如今已经发布了第一个预览版。flutter拥有开发效率高、UI渲染快,更容易的自定义组件、和在两个平台上和本地应用同样的性能。
flutter应用是用dart语言开发的,Dart和其余语言,例如Kotlin Swift同样,拥有不少共同的语言特性,并且能够转化为JavaScript代码。
做为一个跨平台框架,flutter很是像React Native,例如flutter支持响应式和声明是编程。可是和React Native不同的是,flutter不须要JavaScript桥接,这样可提高整个应用的性能和应用启动的时间。Dart是经过AOT技术取得了上述的表现。
另外一方面,拥有JIT的Flutter,支持在开发期间,不须要从头构建整个应用,就能够刷新UI,提供开发效率。
正如本教程将要展现的,flutter框架是创建在组件思想之上的。在flutter中,组件不仅仅指应用的视图,而是整个界面甚至是真个应用。
除了开发跨Android和iOS应用,学习flutter将给你一个在Fuchsia平台开发的超前开始。Fuchsia是google开发中的实验性的操做系统。
本教程,咱们构建一个app,首先从github api中获取一个组织的成员信息,而后将成员信息展现在一个滚动列表中。
当开发app时,但是使用Android模拟器、iOS模拟器或者同时使用。
在构建app时,咱们将学习flutter一下内容:
Flutter开发能够在macOS,Linux或Windows上完成。 虽然您能够在Flutter工具链中使用任何编辑器,但IntelliJ IDEA,Android Studio和Visual Studio Code的IDE插件能够简化开发工做。 咱们将在本教程中使用Android Studio。
您能够在这找到配置flutter开发环境的说明,具体步骤因平台而异,但主要步骤是:
flutter doctor
命令,他会安装flutter框架,包括dart,并且提示你任何其余须要安装的依赖。flutter doctor
发现的任何问题。File->new->new Flutter Project
Flutter Application
,点击next
.
在Android Studio中,您会在左侧看到一个显示项目结构的面板。 有iOS和Android的文件夹,以及包含main.dart的lib文件夹。 您将本教程中的只在lib文件夹中工做。
import 'package:flutter/material.dart';
void main() => runApp(new GHFlutterApp());
class GHFlutterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'GHFlutter',
home: new Scaffold(
appBar: new AppBar(
title: new Text('GHFlutter'),
),
body: new Center(
child: new Text('GHFlutter'),
),
),
);
}
}
复制代码
顶部附近的main()函数使用=>运算符,为了单行函数运行应用程序。 您有一个名为GHFlutterApp的应用程序。 你在这里看到你的应用程序自己是一个StatelessWidget(无状态组件)。Flutter应用程序中的大多数实体都是组件,不管是无状态的仍是有状态的。 您重写组件的build()方法来建立您的应用的组件。 您正在使用MaterialApp 组件,该组件提供了大量遵循Material Design规范应用组件。
对于本入门教程,经过选择它并敲击Delete键,从项目中删除test
文件夹中的测试文件widget_test.dart
。
你能够在macOS上,启动iOS模拟器。 您也能够在macOS,Linux或Windows上使用Android模拟器。
您看到的慢速模式banner表示应用程序正在以调试模式运行。
您能够经过单击Android Studio窗口顶部工具栏右侧的中止按钮来中止正在运行的应用程序:
flutter支持Hot Reload 功能。flutter中的Hot reload和Android Studio的Instant Run相似。 构建并运行应用程序,使其在模拟器或模拟器上运行:
appBar: new AppBar(
title: new Text('GHFlutter App'),
),
复制代码
如今单击工具栏上的热从新加载按钮:
因为Flutter处于开发阶段,热从新加载功能可能没法正常工做,但整体而言,在构建UI时节省时间。
您不须要将全部的Dart代码保存在一个main.dart文件中,而是但愿可以从您建立的其余类中导入。 如今您将看到一个用于导入字符串的示例,这将有助于您应用的本地化。 在lib文件夹中右键单击并选择New File建立一个名为strings.dart的文件: 在新建的文件中添加以下类:
class Strings {
static String appTitle = "GHFlutter";
}
复制代码
在main.dart的顶部添加以下引入:
import 'strings.dart';
复制代码
以下更新应用的title:
return new MaterialApp(
title: Strings.appTitle,
复制代码
更新其余的字符串,使用新的字符串文件,因此最终GHFlutterApp类以下:
class GHFlutterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: Strings.appTitle,
home: new Scaffold(
appBar: new AppBar(
title: new Text(Strings.appTitle),
),
body: new Center(
child: new Text(Strings.appTitle),
),
),
);
}
}
复制代码
构建并运行应用程序,您应该看不到任何变化,但如今您正在使用字符串文件中的字符串。
几乎Flutter应用程序的每一个元素都是一个组件。组件被设计成不可变的,由于使用不可变组件有助于保持应用程序UI的轻量级。
您将使用两种基本类型的组件:
Stateless: 仅依赖于本身的配置信息的组件,例如图像视图中的静态图像 Stateful: 须要维护动态信息的组件,并经过与状态对象交互来实现 无状态和有状态的组件在每一个帧的Flutter应用程序中重绘,不一样之处在于有状态组件将其配置委托给状态对象。
要开始制做本身的组件,请在main.dart的底部建立一个新类:
class GHFlutter extends StatefulWidget {
@override
createState() => new GHFlutterState();
}
复制代码
你已经建立了一个StatefulWidget子类,而且你重写了createState()方法来建立它的状态对象。 如今在GHFlutter上添加一个GHFlutterState类:
class GHFlutterState extends State<GHFlutter> {
}
复制代码
GHFlutterState使用GHFlutter的参数扩展状态。
制做新组件时的主要任务是重写在组件呈如今屏幕上时调用的build()方法。
重写GHFlutterState 的build()方法:
@override
Widget build(BuildContext context) {
}
复制代码
最终以下:
@override
Widget build(BuildContext context) {
return new Scaffold (
appBar: new AppBar(
title: new Text(Strings.appTitle),
),
body: new Text(Strings.appTitle),
);
}
复制代码
Scaffold 是material design组件的容器。 它充当组件层次结构的根。 您已将AppBar和一个body添加到scaffold中,而且每一个都包含一个Text组件
更新GHFlutterApp,以便它使用新的GHFlutter组件做为其home属性,而不是构建默认的scaffold:
class GHFlutterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: Strings.appTitle,
home: new GHFlutter(),
);
}
}
复制代码
构建并运行应用程序,您将看到新的组件正在运行:
以前,您将本身的strings.dart文件导入到项目中。 您能够一样导入Flutter框架的其余库或者Dart的库。
例如,您如今将使用框架中的库来进行HTTP网络调用,并将生成的JSON响应解析为Dart对象。 在main.dart的顶部添加两个新的引入:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'strings.dart';
复制代码
您会看到当前未使用的指标在引入上。
Dart应用程序是单线程的,但Dart支持在其余线程上运行代码以及运行异步代码,经过使用async/await
模式,从而不会阻止UI线程。
您将进行异步网络调用以检索GitHub组织成员列表。 在GHFlutterState顶部添加一个空List做为属性,并添加一个属性来保存文本样式(_biggerFont
):
var _members = [];
final _biggerFont = const TextStyle(fontSize: 18.0);
复制代码
名字如下划线开头的变量表示类的私有成员变量。 要进行异步HTTP调用,请在GHFlutterState中添加一个_loadData()
方法:
_loadData() async {
String dataURL = "https://api.github.com/orgs/raywenderlich/members";
http.Response response = await http.get(dataURL);
setState(() {
_members = JSON.decode(response.body);
});
}
复制代码
您已将async关键字添加到_loadData()
中,以告诉Dart它是异步的,可是http.get()的await关键字表示阻塞执行。 经过dataUrl
设置API的请求地址。
当HTTP调用完成时,您将回调传递给在UI线程上同步运行的setState()。 这时,您解码JSON响应并将其赋值给_members变量。 在GHFlutterState中重写initState方法,在initState中调用_loadData() 方法。当组件状态初始化时,_loadData()会被调用,加载数据。
@override
void initState() {
super.initState();
_loadData();
}
复制代码
如今您已经有了成员列表,您须要一种方法在列表UI中显示它们。 Dart提供了一个ListView组件,可以让您在列表中显示数据。 ListView的做用相似于Android上的RecyclerView和iOS上的UITableView,经过重复回收利用之后的view,使列表滚动更加顺滑。 在GHFlutterState中添加_buildRow()
方法:
Widget _buildRow(int i) {
return new ListTile(
title: new Text("${_members[i]["login"]}", style: _biggerFont)
);
}
复制代码
您将返回一个ListTile组件,该组件显示第iJSON成员的"login"值,还使用您以前建立的文本样式(_biggerFont
)。
更新build方法,使body属性值为ListView.builder:
body: new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: _members.length,
itemBuilder: (BuildContext context, int position) {
return _buildRow(position);
}),
复制代码
您已添加padding,将itemCount设置为成员数量,设置itemBuilder调用_buildRow(),传递postion变量。
您能够尝试热从新加载,但可能会收到"Full restart may be required"的消息。 若是是这样,重启:
发起网络请求,解析数据,并在列表中显示结果是很容易的!
为了在列表中增长分割线,须要翻倍list的itemCount属性,而后list中的position为偶数时返回Divider组件。
body: new ListView.builder(
itemCount: _members.length * 2,
itemBuilder: (BuildContext context, int position) {
if (position.isOdd) return new Divider();
final index = position ~/ 2;
return _buildRow(index);
}),
复制代码
必定要itemCount翻倍,由于增长了分割线,因此删除padding。在itemBuilder中,返回Divider组件,或者经过整数除法计算一个新的index,利用_buildRow(index)返回一个组件。
尝试从新加载,你应该在列表中看到分割线:
Widget _buildRow(int i) {
return new Padding(
padding: const EdgeInsets.all(16.0),
child: new ListTile(
title: new Text("${_members[i]["login"]}", style: _biggerFont)
)
);
}
复制代码
ListTile如今是Padding的子部件。 热加载以查看行上的padding而不是分隔符上的Padding。
在上一节中,JSON解析器将JSON响应中的每一个成员都做为Dart Map类型实例添加到_members列表中,这至关于Kotlin中的Map或Swift中的Dictionary。 可是,若是你想使用自定义类。 在main.dart文件中添加Member类:
class Member {
final String login;
Member(this.login) {
if (login == null) {
throw new ArgumentError("login of Member cannot be null. "
"Received: '$login'");
}
}
}
复制代码
member有一个login属性,和一个构造函数,在构造函数中当login值为null时,会抛出一个error。 更新GHFlutterState类中的_members变量声明,因此如今他就是一个Member对象的list
var _members = <Member>[];
复制代码
更新_buildRow()方法使用Member对象的login属性而不是map的"login"key的值。
title: new Text("${_members[i].login}", style: _biggerFont)
复制代码
如今更新在_loadData()中的传递给setState()的回调,将map解析为Member对象,添加到member对象list中。
setState(() {
final membersJSON = JSON.decode(response.body);
for (var memberJSON in membersJSON) {
final member = new Member(memberJSON["login"]);
_members.add(member);
}
});
复制代码
若是你热加载,有可能看到一个错误,这时你从新启动。
每一个member都有一个头像url.你将添加头像到Member类中,而后在app中显示头像。
在Member类中添加avatarUrl属性,这个属性不能为null。
class Member {
final String login;
final String avatarUrl;
Member(this.login, this.avatarUrl) {
if (login == null) {
throw new ArgumentError("login of Member cannot be null. "
"Received: '$login'");
}
if (avatarUrl == null) {
throw new ArgumentError("avatarUrl of Member cannot be null. "
"Received: '$avatarUrl'");
}
}
}
复制代码
更新_buildRow()方法,经过NetworkImage 和 the CircleAvatar 组件显示头像。
Widget _buildRow(int i) {
return new Padding(
padding: const EdgeInsets.all(16.0),
child: new ListTile(
title: new Text("${_members[i].login}", style: _biggerFont),
leading: new CircleAvatar(
backgroundColor: Colors.green,
backgroundImage: new NetworkImage(_members[i].avatarUrl)
),
)
);
}
复制代码
设置头像为ListTile的leading属性,头像将显示在title的前面。 更新_loadData()方法,使在建立一个新的Member时使用map的"avatar_url"值。
final member = new Member(memberJSON["login"], memberJSON["avatar_url"]);
复制代码
构建运行,你将能看到每个行的头像。
大部分代码如今位于main.dart文件中。 为了使代码更清晰一点,你能够重构组件和你已经添加到他们本身的文件中的类。 在lib文件夹中建立名为member.dart和ghflutter.dart的文件。 将Member类移入member.dart,并将GHFlutterState和GHFlutter类转移到ghflutter.dart。 您不须要member.dart中的任何导入语句,但flutter.dart中的导入应以下所示:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'member.dart';
import 'strings.dart';
复制代码
您还须要更新main.dart中的导入,因此整个文件包含如下内容:
import 'package:flutter/material.dart';
import 'ghflutter.dart';
import 'strings.dart';
void main() => runApp(new GHFlutterApp());
class GHFlutterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: Strings.appTitle,
home: new GHFlutter(),
);
}
}
复制代码
经过构建并运行应用程序,您应该看不到任何更改,但代码如今更清晰一些
您能够经过将主题属性添加到您在main.dart中建立的MaterialApp来轻松地将主题添加到应用程序:
lass GHFlutterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: Strings.appTitle,
theme: new ThemeData(primaryColor: Colors.green.shade800),
home: new GHFlutter(),
);
}
}
复制代码
您正在使用绿色阴影做为Material Design主题颜色值。
构建并运行应用程序,以查看新的主题:
大部分应用截图都来自Android模拟器。 您也能够在iOS模拟器中运行最终的主题应用程序: