学习Flutter过程中,整理了一下Flutter官方教程 Building Beautiful UIs with Flutter
原文地址 https://codelabs.flutter-io.cn/codelabs/flutter/index.html#0
1.综述
Flutter 是一个开源 SDK用于创建高性能原生iOS与Android应用
Flutter框架使你很方便地创建快速响应的UI界面,减少同步更新APP界面的工作量
Flutter通过使用Material Design和 Cupertino (iOS) 组件使得创建一个漂亮的APP变得更容易
用户会倾心于你的APP的观感和体验,因为Flutter使用了针对Android和iOS平台的滚动条,导航栏,字体等.
通过使用设备和模拟器的热部署,开发人员会直观地感受到Flutter框架的强大和对开发效率的显著提升
Flutter应用使用了Dart语言
如果你已经学习过Java, JavaScript, C#或Swift, 那么Dart的语法会让你倍感亲切
Dart在编译时使用了标准的Android和iOS工具,针对不同的移动平台进行处理
Dart语言提供了丰富的功能包括你可能已经非常熟悉的简洁的语法,first-class functions (将函数作为类似int float的基础类型,可以赋值和作为参数传递),async/await,以及丰富的标准库可供调用
通过本篇教程可以学习到
如何使用Flutter编写一个原生的iOS和Android应用
如何调试Flutter应用
如何在模拟器和真机上运行Flutter应用
2.搭建你的Flutter环境
Flutter环境涉及2个部分,Flutter SDK和一个编辑器(IDE),教程中会使用Android Studio作为例子,但你也可以使用其他你习惯使用的编辑器
实际代码运行可以选择真机(Android或iOS)连接到电脑,并开启开发者模式
使用iOS模拟器,需要安装XCode工具
使用Android模拟器,需要安装Android Studio
3.创建一个新的Flutter项目
首先根据模板创建一个简单的Flutter应用,具体可以参考
原文地址 Getting Started with your first Flutter app.
中文地址 编写您的第一个Flutter应用
将项目命名为 friendlychat, 你将会使用这个应用起步来创建一个完整的app
提示: 如果你在你的Android Studio(或其他IDE)中,没有看到“新建Flutter项目”的选项,请确保您已正确安装了Flutter和Dart插件 (具体可以参考plugins installed for Flutter and Dart.)
本篇教程中,主要涉及修改 lib/main.dart 这个Dart源文件
4.创建主用户界面
通过本教程你将开始实践把一个默认的样例app修改为一个聊天应用
目标是使用Flutter创建一个应用Friendlychat,一个简单的可扩展的聊天app,包含以下功能:
实时展示聊天信息
用户可以通过软键盘或者发送图标来发送一条信息
UI界面同时适用于iOS与Android设备
iOS 与 Android 图示如下
image image着手搭建应用的主要框架
第一个需要添加的元素是一个展示静态标题的简单标题栏
随着教程不断深入,你会逐步添加更多具有状态的和响应的UI组件
main.dart文件位于 Flutter 项目lib目录下, 包含main()方法作为程序的启动入口
main()和runApp()方法定义直接参考默认的项目代码. runApp()方法中的输入参数Widget组件
是Flutter框架用于展示APP运行时的界面. 代码中使用了Material Design元素作为UI界面, 创建一个新的MaterialApp对象,并将它传入runApp()方法,这个组件也就成为了UI界面的根节点
// Replace the code in main.dart with the following.
import 'package:flutter/material.dart';
void main() {
runApp(
new MaterialApp(
title: "Friendlychat",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Friendlychat"),
),
),
),
);
}
为了确认用户打开应用时的默认界面, 在MaterialApp定义中设置home参数.
home参数引用了一个组件,该组件定义了应用的主界面 . 这里新建了一个Scaffold组件, 其中包含了一个简单的AppBar作为子控件
如果你启动这个app,可以看到如下界面
iOS 与 Android 图示如下
image image创建聊天界面
开始着手编写一个交互式的组件, 你需要把这个简单的应用分成两个不同的组件子类:
一个不会变化的根节点FriendlychatApp组件
一个子节点ChatScreen组件,用于处理消息发送和内部状态的改变
目前两个组件都可继承 StatelessWidget (无状态组件)
后续我们会修改ChatScreen,继承StatefulWidget(有状态组件) 并管理状态.
// Replace the code in main.dart with the following.
import 'package:flutter/material.dart';
void main() {
runApp(new FriendlychatApp());
}
class FriendlychatApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Friendlychat",
home: new ChatScreen(),
);
}
}
class ChatScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Friendlychat")),
);
}
}
这一步引入了Flutter框架的几个重要的概念
你通过组件中的build()方法来描述用户界面
Flutter框架调用FriendlychatApp或ChatScreen中的build()方法来添加组件,并更新组件的显示层级
@override是Dart的一个标注,用来标识一个方法重写了父类的方法
部分组件,例如Scaffold和AppBar是Material Design应用所特有的组件
其他组件如Text是适用于所有app的通用组件.
Flutter框架中的不同库的组件可在同一个app中互相兼容并使用
点击 hot reload 热部署按钮可以迅速看到刚刚做的修改已经生效
把UI组件分类2个类并修改根节点组件,你应该看到界面没有发生变化
iOS 与 Android 图示如下
image image5.创建一个UI界面来发送消息
接下来将会学习如何创建一个UI界面让用户输入并发送消息
image在手机上点击文本框会弹出一个软键盘,用户可以输入非空的文字并按软件盘上的Return来发送消息
或者,用户可以通过点击输入框胖的发送按钮来发送消息
目前输入文字的界面在聊天界面的顶部,后续我们会把它移动到底部
添加一个交互式文本输入框
Flutter 框架提供了一个 Material Design 文本框组件TextField. 它是一个有状态的组件,拥有可以定制化的输入框属性
状态指的是指在组件从创建到生命周期结束时,可能发生变化的信息
为Friendlychat创建第一个有状态的组件需要对其做一些修改
你需要将数据封装到一个State对象,然后将它和一个StatefulWidget类关联.
如下main.dart代码中展示了如何添加一个交互式输入框.
首先修改ChatScreen继承StatefulWidget,而非StatelessWidget.
TextField用于处理可变的文本内容, ChatScreen则作为文本框控制器, 你需要定义一个新的ChatScreenState类来继承State类.
重写createState()方法来添加ChatScreenState类. 你需要使用这个新的类来创建带有状态的 TextField组件
在build()方法上方添加一行来定义 ChatScreenState类
// Modify the ChatScreen class definition to extend StatefulWidget.
class ChatScreen extends StatefulWidget { //modified
@override //new
State createState() => new ChatScreenState(); //new
}
// Add the ChatScreenState class definition in main.dart.
class ChatScreenState extends State<ChatScreen> { //new
@override //new
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Friendlychat")),
);
}
}
现在ChatScreenState的build()方法包含了之前ChatScreen的创建的所有组件
当Flutter框架调用build()方法刷新 UI时,将会重建ChatScreenState和它的所有子控件
提示:经常查看Flutter框架的API和源码对于了解背后的实现机制很有帮助
可以使用IntelliJ编辑器, 选择一个类或者方法, 然后右键选择查找定义来查看Flutter源码
TextEditingController对象可以用来读取输入框中的内容并在信息发送后将其清空,接下来我们来创建一下这个对象
// Add the following code in the ChatScreenState class definition.**](https://github.com/flutter/friendlychat-steps/blob/master/offline_steps/step_2_composing_messages/lib/main.dart)
class ChatScreenState extends State<ChatScreen> {
final TextEditingController _textController = new TextEditingController(); //new
接下来我们定义一个私有方法_buildTextComposer()
返回Container组件, 其中包含了TextField组件
// Add the following code in the ChatScreenState class definition.
Widget _buildTextComposer() {
return new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
),
);
}
Container组件首先在屏幕和输入框的四周添加了一个水平边距,这里使用的单位是逻辑像素,实际会根据不同设备转换为特定数量的物理像素
这和iOS中的points点的概念和Android中的 dip (density-independent pixels) 设备无关像素是类似的
设置TextField组件来管理用户交互行为:
TextField构造器包含一个TextEditingController控制器,该控制器用来读取和清空输入框中的内容
为了在用户提交时获取通知, 使用onSubmitted参数来提供一个私有的callback回调方法 _handleSubmitted() 目前我们先实现清空输入框的功能,后续会添加实际的发送功能,代码如下
//Add the following code in the ChatScreenState class definition.
void _handleSubmitted(String text) {
_textController.clear();
}
提示: 在变量前面的下划线 _ 表明该变量是在类的内部私有的. Dart编译器会强制校验变量的私有性. 具体可参考Dart官方信息 libraries and visibility
放置文本输入框组件
接下来需要告诉app如何展示这个文本输入框组件. ChatScreenState类的build()方法中,在body属性上添加一个私有方法 _buildTextComposer
_buildTextComposerm返回一个组件,组件中包含了文本输入框
// Modify the code in the ChatScreenState class definition as follows.
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Friendlychat")),
body: _buildTextComposer(), //new
);
}
将app热部署后,可以看到如下界面
iOS 与 Android
image image添加带有响应的Send发送按钮
接下来我们在文本框的右侧添加 Send发送按钮.我们使用 Row行组件作为父节点,使得文本框和发送按钮处于同一行.
然后将TextField组件包裹在Flexible组件中,使得Row行组件中的文本框能够宽度自适应,自动填满Send按钮之外的剩余空间
//Modify the _buildTextComposer method with the code below to arrange the
// text input field and send button.
Widget _buildTextComposer() {
return new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row( //new
children: <Widget>[ //new
new Flexible( //new
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
),
), //new
], //new
), //new
);
}
现在可以着手创建IconButton带Send图标的按钮组件
在icon属性中,使用常量Icons.send来创建一个新的Icon图标实例. 该常量表示使用了Material Design图标素材库中的send发送图标
提示:标准Material Design图标素材库列表可以参考Material Icons官网和Icons类中的常量
把IconButton组件嵌入Container父节点组件中,使得你可以自定义按钮的边距, 使之与旁边的输入框视觉上更为匹配. 对于onPressed点击属性, 使用一个匿名方法来调用_handleSubmitted()方法并使用 _textController控制器来传入消息内容
// Modify the _buildTextComposer method with the code below to define the
// send button.
Widget _buildTextComposer() {
return new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
),
),
new Container( //new
margin: new EdgeInsets.symmetric(horizontal: 4.0), //new
child: new IconButton( //new
icon: new Icon(Icons.send), //new
onPressed: () => _handleSubmitted(_textController.text)), //new
), //new
],
),
);
}
提示:Dart语法中, 箭头=> 表示的含义为 { return expression; }.
Dart 函数相关信息, 包括匿名和嵌套函数, 可以参考Dart Language Tour.
Material Design主题中按钮默认为黑色
可以通过传入颜色参数在IconButton带有图标的按钮中设置颜色,或者你也可以直接选择不同的主题
图标从IconTheme主题组件中继承获得颜色,透明度和尺寸,使用IconThemeData对象来定义这些特征. 把所有组件包裹在IconTheme组件的_buildTextComposer()方法中, 使用data属性来确定ThemeData当前主题所使用的具体值. 详见以下代码
// Modify the _buildTextComposer method with the code below to give the
// send button the current theme's accent color.
Widget _buildTextComposer() {
return new IconTheme( //new
data: new IconThemeData(color: Theme.of(context).accentColor), //new
child: new Container( //modified
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onSubmitted: _handleSubmitted,
decoration: new InputDecoration.collapsed(
hintText: "Send a message"),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: new IconButton(
icon: new Icon(Icons.send),
onPressed: () => _handleSubmitted(_textController.text)),
),
],
),
), //new
);
}
BuildContext上下文对象是一个当前应用组件树的句柄. 每个组件都有其自己的BuildContext, 它是StatelessWidget.build或State.build方法返回的组件的父节点. 这意味着_buildTextComposer()方法可以访问State 对象内部的BuildContext对象,而不需要在方法中显示的传入上下文对象
热部署应用,可以看到如下界面
iOS****与Android
image image使用IntelliJ来调试你的程序
IntelliJ 集成开发环境IDE可以让你很方便地调试 Flutter应用,无论是运行在真机还是模拟器上. 你可以:
选择一个设备或模拟器来调试应用
查看终端的输出信息
在代码中设置断点
在程序运行时查看变量的值
提示:除了 IntelliJ以外, 还有许多其他工具,命令和技巧可以用来调试Flutter应用 ,更多信息可以参考 Debugging Flutter Applications.
IntelliJ 编辑器在程序运行时可以展示运行日志,并提供调试界面来设置断点和控制执行流程
image使用断点
打开你想要设置断点的源文件
点击你所想设置断点的行,然后在菜单中选择Run运行 > Toggle Line Breakpoint设置行断点. 或者你也可以点击行号右侧来设置断点
如果你还没有启动调试模式,先将程序终止,然后重新用调试模式启动
然后IntelliJ 编辑器会启动调试界面,并在到达断点时暂停程序.然后你就可以查看你所需要排查的问题了
你可以试着使用调试模式,在Friendlychat应用中的build()方法中设置断点,来调试程序
你可以通过查看调用栈来观察你的app所调用过的方法
6.创建一个UI界面来展示信息
有了基础的app框架后,你就可以着手开发展示信息的界面了
image实现消息列表
接下来,你将创建一个组件来展示用户的聊天信息
你将使用多个小组件来组装一个更为复杂的组件
首先创建一个组件来展示单条消息, 然后将其嵌入一个滚动列表, 接着把滚动列表加入基础的应用框架
先来定义展示单条消息的组件
定义一个无状态组件StatelessWidget名为ChatMessage, 它的build()方法返回一个Row行组件,它展示了一个简单的发送信息的用户头像, 一个Column列组件包含了发送消息的人的名字以及消息内容
// Add the following class definition to main.dart.
class ChatMessage extends StatelessWidget {
ChatMessage({this.text});
final String text;
@override
Widget build(BuildContext context) {
return new Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 16.0),
child: new CircleAvatar(child: new Text(_name[0])),
),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(_name, style: Theme.of(context).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 5.0),
child: new Text(text),
),
],
),
],
),
);
}
}
参考如上代码来定义_name变量, 我们用这个变量来表示发送者的名字. 为了简化代码,这里可以先给这个变量赋一个固定值. 更通用的做法是通过用户登录验证来获取用户名字,具体可参考Firebase for Flutter
//Add the following code to main.dart.
const String _name = "Your Name";
为了定制CircleAvatar组件, 可以把用户名称_name的首字母赋给Text组件. 我们使用CrossAxisAlignment.start对齐方式传入Row行组件中来定位头像和消息相对于父节点组件的位置
头像的父节点是一个Row行组件,主坐标为水平方向.CrossAxisAlignment.start坐标对齐方式表示从头开始(从最上端开始). 消息的父节点是一个Column列组件,主坐标为垂直方向,CrossAxisAlignment.start坐标对齐方式表示从头开始(从最左侧开始)
头像的旁边是2个纵向排列的Text文本组件,上面展示发送者的名字,下面展示消息
为了给发送者名字加上样式,使名字显示的比消息文本大一些,你需要使用Theme.of(context)来获取一个ThemeData主题数据对象. textTheme文本主题属性代表了 Material Design的一个文本样式比如subhead副标题, 使你可以避免在代码中写死字体字号等属性
目前我们还没有为app指定一个主题,Theme.of(context)会得到一个默认的Flutter主题
后续你可以重写这个默认主题,使得你的app在android和iOS中展示不同的样式
实现消息列表
接下来进一步细化UI,获取聊天消息并在界面中展示
这个列表应是可滚动的,使得用户可以查看聊天历史记录
排序应采用时间顺序,优先展示最新的消息
在ChatScreenState组件中, 添加一个List列表成员变量_messages来表示消息列表. 列表中每一项都是一个ChatMessage聊天消息实例. 你需要把这个列表初始化为一个空列表
// Add the following code to the ChatScreenState class definition.
class ChatScreenState extends State<ChatScreen> {
final List<ChatMessage> _messages = <ChatMessage>[]; // new
final TextEditingController _textController = new TextEditingController();
当用户发送消息时,需要把新消息加入到消息列表中
修改_handleSubmitted()处理提交方法, 如下
// Modify the code in the _handleSubmitted method definition.
void _handleSubmitted(String text) {
_textController.clear();
ChatMessage message = new ChatMessage( //new
text: text, //new
); //new
setState(() { //new
_messages.insert(0, message); //new
}); //new
}
调用setState()来修改_messages列表,并且通知Flutter框架,组件树中的部分内容已经发生了修改,需要重建UI. 在setState()设置状态方法中只应该有同步操作,否则组件可能在操作完成前就已被重建.
网友评论