Flutter001-Why Flutter Why Dart

Flutter是开源并且免费的,拥有现代的响应式框架特性,高速的2D渲染引擎,方便快捷的开发工具以及各种开箱即用的UI组件库。
Flutter使用Dart作为开发语言,这是一门简洁、强类型的面向对象的编程语言。为什么Dart是一门适合移动端开发的语言呢?首先是因为它的性能,包括开发和生产环境下的性能,它包含了两种编译模式Just-In-Time(即时编译)和Ahead-Of-Time(运行前编译)。
JIT即时编译可以使得Flutter在应用运行时同时执行代码编译,这就让Flutter具备了极速的开发体验。具备亚秒级的热重载(Hot Reloading)特性。
AOT运行前编译意思就是将代码库直接编译成原生的ARM指令集。这样的方式对于应用来说意味着快速的启动速度和可预见的卓越性能。
Dart同时具有强类型(static types)和类型推导(sound type)的特性。这样可以让Bug暴露于编译期间,更放心的应付代码量的增长。
当然,使用Flutter也有别的原因。

设置环境

我们可以通过https://flutter.io/setup](https://flutter.io/setup来设置自己的开发环境,有一点要注意的是如果大家用的是android studio 那么版本不能低于3.1.X。
如果大家使用的是模拟器进行开发调试的话,理论上可以使用任何一种设备集成任何一种SDK。如果不确定自己该使用哪个系列或版本的话,建议使用Pixel 2 集成最常用的SDK就好了(设置项选择默认的就好了)。

Flutter

先通过些列子来过一下Flutter的框架(reactor framework)、渲染引擎(rendering engine)和开发工具(development tools)。让我们对这个事情有一个更为宏观的认知。
项目创建成功之后你会发现存在一个iOS和Android平台的文件夹。这是因为Flutter的代码会编译成不同平台的代码。在新建的项目中,我们只需要把目光专注到lib下的main.dart文件就好了。这个文件就是Flutter启动的入口文件。之后我们再介绍其他文件的作用。。。

Flutter的框架(reactor framework)

前面已经说了,Flutter包含了一个先进的响应式框架和2D的渲染引擎,这里说的响应式框架到底指的是什么呢?一个View被构建为一个由许多个widget组成的不可变(immutable)的树状结构。widget是Flutter的基础单元,widget是用dart编写的来描述用户界面的代码块。在Flutter的世界里几乎所有的都是用Dart写好的widget小部件。
没有独立的布局文件来自定义排列和文本对齐等,所有的代码都是在一个Flutter widget中定义。当一个widget的state发生改变时,比如发生了用户输入或者触发了动画,widget会根据它当前的最新的state来进行重新绘制。这种方式节省了开发者的时间,因为这种UI可以直接按照某一种state来进行描述。这样我们就不需要再为widget状态的改变而去写多余的代码了。
Flutter提供了框架来区别出widget所发生的改变,并将这个改变更新到渲染树上。渲染引擎是我们应用中的一部分,所以我们不需要将UI渲染的代码和原生平台做桥接。因为移动应用开发中,布局和渲染的触发要比调用平台的摄像头这种操作更为频繁。通过在应用层把所有渲染工作完成,Flutter就可以快速渲染并对widget树重新渲染,让应用具备更丰富和流畅的动画效果。这种渲染引擎建立于Skia之上,那么什么又是Skia呢?skia是使用Dart编写的2D图形渲染框架,它提供了在iOS和android平台绘制widget的能力。所以平台上的系统仅需要提供一个画布(canvas)就好了,widget的渲染就在Flutter内部的引擎中执行了。AOT模式会编译成机器指令进而执行。如果任何人希望编写扩展到其他平台的应用(非iOS和Android),比如相机和Wi-Fi插件,只要数据可以从操作系统传给画布(canvas),那么Flutter应用就有可能编译到你的目标平台运行,理论上(我猜测)。

开发工具

这里有几个名词,热重载(Hot reloading)、widget检查工具(widget inspector)和代码格式化工具(code auto-formatter)。这几个工具在开发中真的提高了我们的效率。
Flutter的热重载工具,能够让UI发生变化后就即刻进行重新渲染,这个功能基于即时编译JIT。而且热重载工具可以在模拟器和真机上运行。如果我们在开发过程中想让APP重置,那么我们可以通过点击绿色的按钮让应用直接重启。那么这个重启的操作会将应用的state重置。
widget检查器,有点像Chrome浏览器的web检察器。检查器可以让我们在设备上的各个像素之间来回切换,也可以在widget tree中上下切换还可以一行一行的调试在应用里创建的widget。
打开widget检查器,可以很方便的了解内置widgets的结构。
X的圆形按钮,这个图标是inspect的图标;
有两个方形的图标(toggle platform mode),用来在不同设备平台之间进行切换;
在我们的widget tree中,加粗的widget表示是我们自己创建的,而非内置的widget。如果此时你双击选择某个加粗的widget时,对应的widget代码就会高亮显示。在应用的某一处双击也可以达到同样的效果。

在常量前加一个下划线来表示局部常量(仅在这个文件中生效)
const _padding = EdgeInsets.all(20.0);

最后,说一说代码格式化,在每一个属性的后面加上一个逗号,可以被代码格式化工具(code auto-formatter)检测到并自动换行。

小部件 Widget

Widgets are the foundation of Flutter apps. A widget is a description of part of a user interface.

嗯嗯,Flutter的世界里一切皆组件,所见之处都是widget。那么它们又被分为Stateless无状态的和stateful有状态的两种。stateless组件是不可变的,这意味着该组件一旦创建那么它的所有属性都是最终状态。在widget创建的时候,我们可以传递参数,比如背景色。但一旦创建好,这些属性就是不可改变的了。同样这类组件也是不可交互的。
另一类呢,stateful widgets 就是可以创建start对象。
那么无状态的组件是怎么布局的呢?
我们在实例化widget的时候,会传递给它一些参数,有些是必须的,有些是可选的。我们可以通过输入一些属性值来定制化一个组件的对象。大多数的组件都会有 height、width和child属性,用来嵌套其他的子组件。child属性只允许嵌套一个子widget,而children属性则可以嵌套多个。当然child是可选参数,Flutter允许我们创建不包含子组件的组件。

runApp函数可以接受任意的组件作为参数

MateralApp组件中 debugShowCheckedModeBanner: false -- 快速移除角标

为了让我们的函数看起来简洁,我们可以把某些组件在函数外部创建。我们不应该在工程和应用里硬编码所有的widget,这样它们就完全不能重新被创建。更建议是把创建在函数外部的抽离成可以传参的组件。

center组件只对它第一层child有效

自定义Widget

自定义widget可以为应用添加个性化风格,可以设计符合品牌或主题的内容。widget完全可以自定义,我们也可以扩展和自定义现有widget,或将它们作为整体框架来使用。

route 是页面的别名

Clarification - a route takes you to a page , or screen.

our code and animation mentions 'categoryRoute' and 'converterRoute' , but these are more aptly named 'categoryScreen' and 'converterScreen'. These widgets are responsible for the UI at the route's destination.

有状态的组件 stateful widget

之前我们说widget是不可修改的,也就是说它没有状态可言,也不能在创建之后再修改。我们在应用中加入一些状态,这样widget就可能因为用户操作而发生一些变化。
当用户进行操作widget发生变化时,我们希望保持状态并响应用户交互和事件,这时候就需要用到stateful widget。

stateful widget本身也是不可改变的

因为它本身的不可改变,所以它用createState方法创建一个state对象。state对象存储了widgetState信息并且可以在对象的生命周期中发生改变。实例化过程中传入的文本是不可改变的,但是存在于状态对象中的颜色却是可以改变的。仔细观察我们的stateful组件的方法,下划线开头的类说明它是一个私有类,因而唯一能够访问其资源的就是类本身了。在Flutter中为了保持这种一致性,所以我们会看到状态对象也命名为对应的statefulWidget。
下面来聊一聊状态对象本身。它提供了一套可调用的方法用于改变各种状态,比如说,如果我们要在每次点击矩形的时候改变其颜色,为了方便我们选取FlatButton组件作为容器,这样就可以使用onPressed事件属性了。这个widget在状态的数据发生改变时不会自动触发重新渲染的过程。改变颜色这个事情就交给了widget的实现者来调用setState方法,在下一帧中触发重建。

仅仅只有发生改变的widget会被重建

要注意的是文本是不会被修改的,因为它没有存储在状态对象中。当Stateful组件被初始化的时候,我们可以通过重新initState方法来加入一些自定义的代码。
Stateful 组件也可以用于动画,这一过程发生在UI从一个状态变为另一个状态的过程中。滑出的菜单就是一个简单的例子,它内部存储了关闭、隐藏和开启三个状态属性,

颜色

颜色的透明,范围从00开始,即完全透明,可增加到FF,即完全不透明。

Text Input

能够吸引用户的应用有什么秘诀?当然是人机交互了。人机交互包括文本输入,下拉列表选项,手势和数据API交互等等。Flutter的Widget通常以内置方式捕捉用户的操作,我们先从文本的输入开始聊。
在Flutter中通过TextField组件来捕获文本输入,如果要创建表格的话就需要用TextFormField小部件。
通过keyboardType 来设置虚拟键盘类型

字符

Flutter支持Unicode和emojis。
那么如何获取用户输入的字符串呢?类似前面所说,在Flutter中都是通过用户的操作来进行相应的反馈进而修改Widget小部件的内置属性。检索用户输入的文本有三种方法,onChanged方法、onSubmitted方法和controller控制器。

  1. onChanged属性接受一个函数,每次文本更改时都会调用此函数。在函数内部可以设置状态,检查文本的输入是否存在验证错误。
  2. 当用户点击return键时,将调用onSubmitted属性,同样也传递一个函数来执行此操作。只有用户完成输入后才做响应。这种对于需要验证数据有效性的场景十分有用。
  3. 控制器属性允许对用户输入进行更多自定义和控制。我们可以创建并传入文本编辑控制器,添加监听此控制器的listener,这不仅允许我们在用户输入每个字符时进行响应,还允许我们修改和覆盖文本字段的内容。比如自动补全功能。
    在用户操作文本输入这一块,我们可以定义键盘为数字键盘、邮件键盘,但是我们没有办法规避用户输入的空格,所以在每一个input框中都最好加入验证。

手势

Flutter的手势系统中有两个独立的层,第一层时原始指针事件,它描述了指针在屏幕上的位置和移动,这些包括指针向下、指针移动、指针向上和指针取消事件。第二层是手势,手势表示从多个单独指针事件中识别的语义动作,比如点击、拖动和缩放。
手势在生命周期中传递多重事件,如果我们想要将自己的交互性添加任意一个小部件上,我们可以将这个widget小部件包裹在GestureDetector小部件中。

响应式

响应式,意味着我们需要一种基于屏幕方向或其高度和宽度重新调整子widget的方法。在Flutter中,可以使用媒体查询(media query)、方向构建器(orientation builder)和布局构建器(layout builder)。
MediaQuery.of 可以提供有关当前设备上运行的应用程序的大小,方向和其他数据。
orientation builder widget方向构建器的builder属性,是一个将父类widget方向作为参数的函数,我们可以使用此方向来布局子widget。
布局构建器的builder属性是一个带有盒子约束参数的函数,盒子约束可以提供有关当前尺寸约束的信息,比如父类widget的高度或纵横比。
这两个构建器和媒体查询之间的区别在于它们可以特定于一个小部件而不是这个应用程序。同样如果我们使用的是构建器,那么在用户更改APP的尺寸大小时,构建函数会自动重新运行。
还有一些其他的方法,比如在widget周围包含宽高比(aspect ratio 小部件)来强制使用某个宽高比。比如使用Fitted box 小部件来根据子widget来进行缩放。

package plugin和pubspec.yaml

就像widget小部件一样,我们在开发新的应用或功能时都不希望重复造轮子。
package可以创建易于共享的模块化代码,我们可以使用Flutter和Dart包,有些与设备API集成比如Battery包。有些被称为插件,因为它们与iOS或Android平台交互,比如Firebase包。
在pubspec.yaml中包含的信息有名称、版本、描述、作者和依赖项。Flutter应用程序会依赖Flutter SDK。我们还可以在Pubspec中指定资源和字体。当我们对Pubspec进行更改时,如果编辑器没有自动执行此操作,我们还可以通过命令 flutter packages get来执行。这个命令会获取或更新应用包所依赖的必须软件包。Pub创建一个a.packages文件,该文件从包名称映射到那些实际的URI中。即便是导入了大型的软件包,也只有所调用的函数最终会在发布模式下编译为代码,这是因为Flutter使用了tree shaking来删除生产环境中的二进制文件在编译过程中产生的冗余和未使用的代码。

tree shaking:process where redundant and unused code is removed during code compilation.

通过这样,我们无需指定要导入的特定类,而只需要导入整个包且不用担心有未使用的依赖项。

File assets

所谓 assets,指的是和APP捆绑和部署的文件。在运行时可以访问并且是只读的。我们可以通过AssetBundle来访问这个文件夹。Flutter中还附带了rootBundle来访问主资源包。这里我建议使用DefaultAssetBundle来获得当前的资源。在定位和运行测试的时候,可以使父类widget在运行时替换资源。
使用DefaultAssetBundle.of 来间接加载资源,然后导入dart convert 包来将每一个单元映射到对应的类里。

异步

API的调用可能需要一些时间,在flutter中我们将它包裹在异步操作中。这么操作可以让应用程序继续运行而不会被阻止。
当调用返回future的函数时,会发生两件事。首先,该功能提示要完成的工作并返回一个不完整的future对象;然后,当值可用时,future对象将使用该值或返回一个错误。
当值可用时,将future返回的值保存到变量中,并在函数上调用await,我们需要使用async关键字包装调用的函数。
关于错误,我们可以使用条件检查和try-catch语句来捕捉错误,并且根据所捕捉的错误在页面中显示与错误消息关联的UI小部件。

推荐阅读更多精彩内容