Flutter初识

这个春节都关在家里无事可做,于是想到要去了解下Flutter这个移动SDK。经过几天了解,对Flutter有个大概的认知。
下面我将从以下几个方面跟大家分享下:
以下这些问题都能在Flutter中文网中获取,我只是单独拎了些个人觉得比较能全局性地看Flutter存在的意义的几个问题。
大家可以自行去看:
传送门:https://flutterchina.club/faq/

一、什么是Flutter?
Flutter是一款移动应用程序SDK,一份代码可以同时生成iOS和Android两个高性能、高保真的应用程序。

1通过在不同平台实现一个【统一接口】的【渲染引擎】来绘制UI,而【不依赖系统原生控件】,所以可以做到不同平台UI的一致性。(自绘UI+原生) 2 3

注意:自绘引擎解决的是UI的跨平台问题,如果涉及其它系统能力调用,依然要涉及原生开发。

简而言之就是:保性能、保证两个操作系统上 App 界面、功能,甚至代码,都完全统一。

二、为什么用Flutter?能解决行业上的哪些痛点?
Flutter有什么优势?
它可以帮助你:
1.提高开发效率

  • a.同一份代码开发iOS和Android
  • b.用更少的代码做更多的事情
  • c.轻松迭代

在应用程序运行时更改代码并重新加载(通过热重载)
修复崩溃并继续从应用程序停止的地方进行调试

2.创建美观,高度定制的用户体验
受益于使用Flutter框架提供的丰富的Material Design和 Cupertino(iOS风格)的widget
实现定制、美观、品牌驱动的设计,而不受原生控件的限制

核心原则
Flutter包括一个现代的响应式框架、一个2D渲染引擎、现成的widget和开发工具。这些组件可以帮助您快速地设计、构建、测试和调试应用程序。

一切皆为widget
Widget是Flutter应用程序用户界面的基本构建块。每个Widget都是用户界面一部分的不可变声明。 与其他将视图、控制器、布局和其他属性分离的框架不同,Flutter具有一致的统一对象模型:widget。

Widget可以被定义为: 一个结构元素(如按钮或菜单) 一个文本样式元素(如字体或颜色方案) 布局的一个方面(如填充) 等等…

Widget根据布局形成一个层次结构。每个widget嵌入其中,并继承其父项的属性。没有单独的“应用程序”对象,相反,根widget扮演着这个角色。

您可以通过告诉框架使用另一个widget替换层次结构中的widget来响应事件,例如用户交互,替换后框架会比较新的和旧的widget,并高效地更新用户界面。
Flutter Widget组成
组合 > 集成
Widget本身通常由许多更小的、单一用途widget组成,这些widget结合起来产生强大的效果。例如,Container是一个常用的widget, 由多个widget组成,这些widget负责布局、绘制、定位和调整大小。具体来说,Container由 LimitedBox、 ConstrainedBox、 Align、 Padding、 DecoratedBox、 和Transform组成。 您可以用各种方式组合这些以及其他简单的widget,而不是继承容器。
类层次结构很浅且很宽,可以最大限度地增加可能的组合数量。

您还可以通过与其他widget组合来控制widget的布局。例如,要将widget居中,可以将其封装在Center widget中。有填充、对齐、行、列和网格的widget。 这些布局widget没有自己的可视化表示。相反,他们唯一的目的是控制另一个widget布局的某些方面。要理解widget以某种方式呈现的原因,检查相邻widget通常很有帮助。

二、Flutter使用什么语言?为什么?
采用语言
Flutter采用了Dart作为开发框架和widget的语言。底层图形框架和Dart虚拟机在C /C++中实现。

考量因素
Dart运行时和编译器支持Flutter的两个关键特性的组合:
基于JIT的快速开发周期:允许使用类型的语言进行形状更改和有状态的热重载;
AOT编译器:可生成高效的ARM代码,可以快速启动并拥有可预测的生产部署性能。

Dart在以下主要标准上得到高分:

  • 开发人员的效率。Flutter的主要价值主张之一是通过让开发人员使用相同的代码库为iOS和Android创建应用程序,从而节省了工程资源。使用高效的语言可以进一步加速开发周期,并使Flutter更具吸引力。
  • 面向对象。绝大多数开发人员都具有面向对象开发的经验,因此更容易学习如何使用Flutter进行开发。
  • 可预测,高性能。借助Flutter,开发人员能够快速创建流畅的用户体验。为了实现这一点,需要能够在每个动画帧中运行大量的代码。这意味着我们需要一种既能提供高性能又能提供可预测性能的语言,而不会出现会导致丢帧的周期性暂停。
  • 快速内存分配。Flutter框架使用函数式流,它很大程度上依赖于底层的内存分配器,从而有效地处理小的、短期的内存分配会非常重要,所以在缺乏此功能的语言中Flutter无法有效地工作。

三、Flutter框架使用什么编程范式?**
Flutter是一个多范式编程环境。在Flutter中使用了过去几十年中开发的许多编程技术。我们使用的每一个范式都是我们相信该它的优势特别适合Flutter:

  • 组合:Flutter使用的主要范例是使用小对象,然后将它们组合在一起以获得更复杂的对象。Flutter widget库中的大多数widget都是以这种方式构建的。例如,Material FlatButton 类是使用MaterialButton 类构建, 该类本身使用IconTheme、InkWell、Padding、Center、Material、AnimatedDefaultTextStyle和ConstrainedBox组合 构建。该InkWell 使用内置GestureDetector。Material 是使用内置AnimatedDefaultTextStyle、NotificationListener和AnimatedPhysicalModel。等等,它们都是widget。
  • 函数式编程:整个应用程序可以仅使用StatelessWidget来构建 ,这些函数本质上是描述参数如何映射到其他函数的函数。在计算布局或绘制图形的底层(这些应用程序不拥有状态,因此通常是非交互式的)例如,Icon widget本质上是一个将其参数(颜色、 icon、size)映射到布局基本单元的函数。此外,大量使用的是不可变数据结构,包括整个Widget类层次结构以及许多支持类,如 Rect和 TextStyle也都是。Dart中的Iterable API经常用来处理框架中的值列表,它大量使用了函数式(map,reduce,where等)方法。
  • 事件驱动:用户交互由事件对象表示,这些事件对象被分派给注册了事件处理程序的回调。屏幕刷新也由类似的回调机制触发。监听类是动画系统的基础,它确立了与多个监听事件订阅模式。
  • 基于类的面向对象编程:框架的大部分API都是使用继承类来构建的。我们使用一种方法来在基类中定义非常抽象的API,然后在子类中迭代地对它们进行定制化。例如,我们的渲染对象有一个与坐标系无关的基类(RenderObject),然后我们有一个子类(RenderBox),它引入了基于笛卡尔坐标系(x / width)和Y /高度)。
  • 基于原型的面向对象编程: ScrollPhysics 类将实例链接起来组成适用于在运行时动态滚动的physics。这使得系统可以编写包含特定平台physics的分页physics,而无需在编译时选择平台。
  • 命令式编程:直接命令式编程通常与对象内部封装的状态配对,用于提供最直观的解决方案。例如,测试是以一种强制性风格编写的,首先描述测试中的情况,然后列出测试必须匹配的不变量,然后根据测试需要推进时钟或插入事件。
  • 响应式编程:widget和元素树有时被描述为响应式的,因为在widget的构造函数中提供的新输入会立即作为widget的构建方法对较低级别widget的更改传播,并在较低widget中进行更改(例如,作为响应到用户输入)通过事件处理程序传播回widget树。根据widget的需求,功能向应和命令响应两方面都存在于框架中。具有构建方法的widget仅由一个表达式组成,该表达式描述了widget如何对其配置中的变化做出反应的功能响应widget(例如,Divider类)。 构建方法通过几个语句构建子项列表的widget,描述了widget如何对其配置中的更改作出反应,这些都是命令性响应widget(例如 Chip类)。
  • 声明式编程:widget的构建方法通常是具有多层嵌套构造函数的单一表达式,使用Dart的严格声明子集编写。这样的嵌套表达式可以机械地转换成任何适合表达的标记语言或从任何适合表达的标记语言转换。例如, UserAccountsDrawerHeader widget具有很长的构建方法(20行以上),由单个嵌套表达式组成。这也可以与命令式风格相结合来构建用纯粹声明式方法难以描述的UI。
  • 泛型:类型可用于帮助开发人员及早发现编程错误。Flutter框架使用泛型编程来处理这个问题。例如, State类根据其关联widget的类型进行参数化,以便Dart分析器可以捕获状态和widget的不匹配。类似地, GlobalKey类需要的类型参数,以便它可以访问远程widget的状态下在一个类型安全的方式(使用运行时检查), 路由接口是参数化时,它是预期使用类型 pop和集合,例如List、Map和 Set都是参数化的,这样可以在分析过程中或在调试期间的运行时提前捕获不匹配的元素。
  • 并发:Flutter大量使用 Future和其他异步API。例如,动画系统通过Future来完成动画完成时的通知。图像加载系统同样使用Future在加载完成时进行报告。
  • 约束:Flutter中的布局系统使用弱形式的约束编程来确定场景的几何形状。约束(例如,对于笛卡尔盒子,最小和最大宽度以及最小和最大高度)从父母传递给孩子,并且孩子选择生成的几何结构(例如,对于笛卡尔盒子,大小,特别是宽度和高度)满足这些限制。通过使用这种技术,Flutter通常可以通过一次遍布整个场景。

**四、Flutter框架组成?
分层的框架
Flutter框架是一个分层的结构,每个层都建立在前一层之上。
分层框架
这个设计的目标是帮助你用更少的代码做更多的事情。
例如,Material层是通过组合来自Widget层的基本Widget来构建的, 并且Widgets层本身是通过较低级对象渲染层构建的。

层为构建应用程序提供了许多选项。选择一种自定义的方法来释放框架的全部表现力,或者使用构件层中的构建块,或混合搭配。 您可以实现Flutter提供的所有现成的widget,或者使用Flutter团队用于构建框架的相同工具和技术创建您自己的定制widget。

没有什么是隐藏的。您可以从高层次,统一的widget概念中获得开发效率优势,而不会牺牲您希望深入到下层的能力。

构建widget
您可以通过实现widget的build返回widget树(或层次结构)来定义widget的独特特征 。 这棵树更具体地表示了用户界面的widget层次。例如,工具栏widget的build函数可能返回一个包含一些文本和各种按钮的水平布局。 然后,框架递归地构建widget,直到该所有widget构建完成,然后framework将他们一起添加到树中。

widget的构建函数一般没有副作用。每当它被要求构建时,widget应该返回一个新的widget树,无论widget以前返回的是什么。 Framework会将之前的构建与当前构建进行比较并确定需要对用户界面进行哪些修改。

这种自动比较非常有效,可以实现高性能的交互式应用程序。而构建函数的设计则着重于声明widget是由什么构成的,而不是将用户界面从一个状态更新到另一个状态的(这很复杂性),从而简化了代码。

处理用户交互
如果widget需要根据用户交互或其他因素进行更改,则该widget是有状态的。例如,如果一个widget的计数器在用户点击一个按钮时递增,那么该计数器的值就是该widget的状态。 当该值发生变化时,需要重新构建widget以更新UI。

这些widget将继承StatefulWidget(而不是State)并将它们的可变状态存储在State的子类中。在这里插入图片描述

每当你改变一个State对象时(例如增加计数器),你必须调用setState()来通知框架,框架会再次调用State的构建方法来更新用户界面。 (思想同其他响应式框架,如ReactNative)

有了独立的状态和widget对象,其他widget可以以同样的方式处理无状态和有状态的widget,而不必担心丢失状态。 父widget可以自由地创造子widget的新实例且不会失去子widget的状态,而不是通过持有子widget来维持其状态。 框架在适当的时候完成查找和重用现有状态对象的所有工作。

五、Flutter App项目结构?
我用的是IntellijIdea,要使用Flutter,先安装把Flutter插件,并下载FlutterSDK,插件中已经集成了DART。
在这里插入图片描述
新建一个App,有两种方式:
1、在命令行中使用命令创建,再导入到IDEA在这里插入图片描述

1Downloads Transfar$ flutter create flutter_demoex 2Creating project flutter_demoex... 3 4 5

2、直接在IDEA中创建,效果是一样的
在这里插入图片描述
项目基本结构如图:
在这里插入图片描述
结构很清晰,提供android和IOS两种系统应用结构,一个测试目录
重点关注:
lib目录- dart代码存放路径
pubspec.yaml-包管理文件,想引用包直接在这里引用。

1dependencies: 2 flutter: 3 sdk: flutter 4 5 # The following adds the Cupertino Icons font to your application. 6 # Use with the CupertinoIcons class for iOS style icons. 7 cupertino_icons: ^0.1.2 8 english_words: ^3.1.0 9 css_colors: ^1.0.0 10 url_launcher: ^3.0.1 11 path_provider: ^0.5.0 12 13

六、FlutterSDK的源码组成?
打开External Libraries
1、Dart管理的包
在这里插入图片描述
2.DART SDK在这里插入图片描述
3.
在这里插入图片描述
七、实践:按Flutter中文网教程新建一个带State的App.**
编写您的第一个 Flutter App
https://flutterchina.club/get-started/codelab/
需求:
建两个页面,一个单词列表页,一个收藏列表页。单词列表页每一条记录都有一个收藏按钮,通过该按钮(state)改变widget状态。单词列表页AppBar右边有个页面跳转按钮,跳转到收藏列表页。
这也是我们平时开发时最普通的功能。

7.1、创建APP的过程省略,看 五、Flutter App项目结构?
此处我们来看下android下的settings.gradle是怎么找Flutter插件的

1include ':app' 2def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 3def plugins = new Properties() 4def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 5if (pluginsFile.exists()) { 6 pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 7} 8plugins.each { name, path -> 9 def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 10 include ":$name" 11 project(":$name").projectDir = pluginDirectory 12} 13 14

local.properties里添加FlutterSDK

1flutter.sdk=/Users/Transfar/Documents/FlutterSDK/flutter 2 3

build.gradle里设置包管理仓库,我们用阿里云的

1buildscript { 2 repositories { 3 //google() 4 //jcenter() 5 maven{ url 'https://maven.aliyun.com/repository/google' } 6 maven{ url 'https://maven.aliyun.com/repository/jcenter' } 7 maven{ url 'https://maven.aliyun.com/nexus/content/groups/public' } 8 } 9 10 dependencies { 11 classpath 'com.android.tools.build:gradle:3.2.1' 12 } 13} 14 15allprojects { 16 repositories { 17 //google() 18 //jcenter() 19 maven{ url 'https://maven.aliyun.com/repository/google' } 20 maven{ url 'https://maven.aliyun.com/repository/jcenter' } 21 maven{ url 'https://maven.aliyun.com/nexus/content/groups/public' } 22 } 23} 24 25

在app的build.gradle看下是怎么获取到flutter的版本号和版本名的

1def flutterRoot = localProperties.getProperty('flutter.sdk') 2if (flutterRoot == null) { 3 throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 4} 5 6def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 7if (flutterVersionCode == null) { 8 flutterVersionCode = '1' 9} 10 11def flutterVersionName = localProperties.getProperty('flutter.versionName') 12if (flutterVersionName == null) { 13 flutterVersionName = '1.0' 14} 15 16apply plugin: 'com.android.application' 17apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 18 19

7.2、使用外部包(package)
找到pubspec.yaml文件,在dependencies中添加

1 english_words: ^3.1.0 2 3

在这里插入图片描述
工具栏中执行 flutter packages get在这里插入图片描述
或者直接点 packages get导入包在这里插入图片描述
7.3、添加一个 有状态的部件(Stateful widget)
Stateless widgets 是不可变的, 这意味着它们的属性不能改变 - 所有的值都是最终的.
Stateful widgets 持有的状态可能在widget生命周期中发生变化. 实现一个 stateful widget 至少需要两个类:

一个 StatefulWidget类。
一个 State类。 StatefulWidget类本身是不变的,但是

1State类在widget生命周期中始终存在. 2 3

7.3.1 添加有状态的 RandomWords widget类,复写方法,实例化RandomWordsState

1class RandomWords extends StatefulWidget { 2 @override 3 createState() => new RandomWordsState(); 4} 5 6

7.3.2 添加RandomWordsState类,管理RandomWords的状态

1class RandomWordsState extends State<RandomWords> { 2 final _suggestions = <WordPair>[];//记录单词对 3 4 final _biggerFont = const TextStyle(fontSize: 18.0); //文本样式 5 6 final _saved = new Set<WordPair>(); //记录被收藏的单词,用Set是为了确保内容唯一 7 8

7.3.3 复写build方法,将生成单词对的代码从MyApp移动到RandomWordsState来生成。

1@override 2 Widget build(BuildContext context) { 3 // TODO: implement build 4 return new Scaffold( 5 appBar: new AppBar( 6 title: new Text('Startup Name Generator'), 7 actions: <Widget>[ 8 new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), 9 ], 10 ), 11 body: _buildSuggestions(), 12 ); 13 } 14 15

7.3.4 body:_buildSuggestions就是创建单词列表。actions: [ ]添加跳转按钮,并添加交互方法_pushSaved,来分别看下这两个方法。
a. 用ListView.builder建list,包括添加分隔线以及追加无限的记录;

1Widget _buildSuggestions() { 2 return new ListView.builder( 3 padding: const EdgeInsets.all(16.0), 4 // 对于每个建议的单词对都会调用一次itemBuilder,然后将单词对添加到ListTile行中 5 // 在偶数行,该函数会为单词对添加一个ListTile row. 6 // 在奇数行,该函数会添加一个分割线widget,来分隔相邻的词对。 7 // 注意,在小屏幕上,分割线看起来可能比较吃力。 8 itemBuilder: (context, i) { 9 // 在每一列之前,添加一个1像素高的分隔线widget 10 if (i.isOdd) return new Divider(); 11 12 // 语法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i为:1, 2, 3, 4, 5 13 // 时,结果为0, 1, 1, 2, 2, 这可以计算出ListView中减去分隔线后的实际单词对数量 14 final index = i ~/ 2; 15 // 如果是建议列表中最后一个单词对 16 if (index >= _suggestions.length) { 17 // ...接着再生成10个单词对,然后添加到建议列表 18 _suggestions.addAll(generateWordPairs().take(10)); 19 } 20 return _buildRow(_suggestions[index]); 21 }); 22 } 23 24

b.再来看下_buildRow方法,onTap管理收藏列表状态;

1Widget _buildRow(WordPair suggestion) { 2 final alreadySaved = _saved.contains(suggestion); 3 return new ListTile( 4 title: new Text( 5 suggestion.asPascalCase, 6 style: _biggerFont, 7 ), 8 trailing: new Icon( 9 alreadySaved ? Icons.favorite : Icons.favorite_border, 10 color: alreadySaved ? Colors.red : null, 11 ), 12 onTap: () { 13 setState(() { 14 if (alreadySaved) { 15 _saved.remove(suggestion); 16 } else { 17 _saved.add(suggestion); 18 } 19 }); 20 }, 21 ); 22 } 23 24

c.跳转交互方法_pushSaved,直接跳转到收藏页面

(在Flutter中称为路由(route),在主路由和新路由之间导航(切换页面))

1void _pushSaved() { 2 Navigator.of(context).push(//添加Navigator.push调用,这会使路由入栈(以后路由入栈均指推入到导航管理器的栈) 3 new MaterialPageRoute(//添加MaterialPageRoute及其builder 4 builder: (context) { 5 final tiles = _saved.map( 6 (pair) { 7 return new ListTile( 8 title: new Text( 9 pair.asPascalCase, 10 style: _biggerFont, 11 ), 12 ); 13 }, 14 ); 15 final divided = ListTile.divideTiles(//ListTile的divideTiles()方法在每个ListTile之间添加1像素的分割线。 该 divided 变量持有最终的列表项。 16 context: context, 17 tiles: tiles, 18 ).toList(); 19 return new Scaffold( 20 appBar: new AppBar( 21 title: new Text('Saved Suggestions'), 22 ), 23 body: new ListView(children: divided), 24 ); 25 }, 26 ), 27 ); 28 } 29 30

7.4、最后返回main.dart,替换MyApp build为下面的代码

1void main() => runApp(MyApp());//入口函数 2 3
1class MyApp extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 // TODO: implement build 5 return new MaterialApp( 6 title: 'Startup Name Generator', 7 theme: new ThemeData(//自定义一个主题 8 primaryColor: Colors.redAccent, 9 ), 10 home: new RandomWords(), //实例化RandomWords,画UI 11 ); 12 } 13 14

7.5、效果
在这里插入图片描述
在这里插入图片描述
不用自己去实现返回的交互

在Flutter中,导航器管理应用程序的路由栈。将路由推入(push)到导航器的栈中,将会显示更新为该路由页面。
从导航器的栈中弹出(pop)路由,将显示返回到前一个路由。

通过这个例子,我们学到了以下内容:

  • 从头开始创建一个Flutter应用程序.
  • 编写 Dart 代码.
  • 利用外部的第三方库.
  • 使用热重载加快开发周期.
  • 实现一个有状态的widget,为你的应用增加交互.
  • 用ListView和ListTiles创建一个延迟加载的无限滚动列表.
  • 创建了一个路由并添加了在主路由和新路由之间跳转逻辑
  • 了解如何使用主题更改应用UI的外观.

八、总结
我们初始了Flutter,大概了解了Flutter架构和SDK,以及怎么去实现一个Flutter应用。要深入学习的内容很多,比如构建、打包、发布,与原生组件通信等等,路很长,大家一起学。

代码交流 2021