关于dart:实现Dart版本对象存储COS插件

前言在 Flutter Web 在《一起漫部》的性能优化摸索与实际 一文中,在做加载优化时须要实现资源文件cdn化,意味着要将资源文件上传到腾讯的COS或者阿里的OSS这样的第三方对象存储服务器。 目前公司应用的是腾讯的对象存储(COS),本来想用官网提供的SDK去实现资源上传性能,然而官网并没有提供Dart版本的SDK, 去pub.dev搜了下对于cos的相干插件,也没有找到性能绝对欠缺的插件,于是便打算手写一个Dart版本对象存储(COS)插件。 简介在官网提供的API性能过于丰盛和工夫无限的状况下,只实现了局部性能: 反对Bucket接口的基本操作,减少、删除、查问存储桶等反对Bucket接口的访问控制(acl)反对Bucket接口的跨域资源共享(cors)反对Bucket接口的防盗链(referer)反对Object接口的基本操作,上传、删除、查问存储对象等反对Object接口的访问控制(acl)构造工程次要包含示例(example)、外围代码(lib)和单元测试(test)三局部 ├── CHANGELOG.md├── LICENSE├── README.md├── analysis_options.yaml├── example // 示例├── lib // 外围代码│ ├── src│ │ ├── api│ │ ├── client│ │ ├── model│ │ └── src.dart│ └── tencent_cos_plus.dart├── pubspec.lock├── pubspec.yaml├── tencent_cos_plus.iml└── test // 单元测试示例示例目前仅包含tencent_cos_plus_example.dart文件,次要是介绍如何应用插件,包含初始化配置、存储桶Api调用和存储对象 Api调用。 ├── example│ └── tencent_cos_plus_example.dart外围代码外围代码(lib)局部由client、api和model三层形成: client层封装了http申请和接口签名性能api层实现了存储桶(bucket)和存储对象(object)局部api的调用model层负责xml数据和实体对象的解析├── api│ ├── api.dart│ ├── cos_abstract_api.dart│ ├── cos_api_factory.dart│ ├── cos_api_mixin.dart│ ├── cos_bucket_api.dart│ └── cos_object_api.dart├── client│ ├── client.dart│ └── cos_client.dart├── model│ ├── common│ │ ├── cos_access_control_policy.dart│ │ ├── cos_common.dart│ │ ├── cos_config.dart│ │ ├── cos_cors_configuration.dart│ │ ├── cos_exception.dart│ │ └── cos_referer_configuration.dart│ ├── model.dart│ ├── request│ │ ├── cos_bucket_acl_header.dart│ │ ├── cos_create_bucket_configuration.dart│ │ ├── cos_delete.dart│ │ ├── cos_get_object.dart│ │ └── cos_restore_request.dart│ └── response│ ├── cos_copy_object_result.dart│ ├── cos_delete_result.dart│ ├── cos_list_all_my_buckets_result.dart│ ├── cos_list_bucket_result.dart│ └── cos_list_versions_result.dart单元测试单元测试包含cos_bucket_api_test.dart和cos_object_api_test.dart两个文件,次要是笼罩了存储桶(bucket)和存储对象(object)局部api的单元测试 ...

October 21, 2022 · 1 min · jiezi

关于dart:Dart的混入概念和Vue3的混入概念

Dartmixin即mix in,中文翻译过去是“混入”,就是在类中混入其余性能 在Dart官网中的定义是Mixins are a way of reusing code in multiple class hierarchies.翻译过去就是“Mixins是一种在多类层次结构中复用代码的一种形式” Dart的mixins应用首先用mixin关键字定义一个mixin类对于mixin关键字能够这样了解,定义类用class,定义接口用interface,而定义Mixins用的就是mixin A {void a() {print('A');}}而后用with关键字混入一个mixin类对于with关键字也能够这样了解,集成类用extends,实现接口用implements,而混入Minxins用的就是withvoid main() { Mix m = Mix(); m.a();} 多混入Mixins反对多混入,这样就能够应用多个Mixin类的性能如下的Mix类混入了A、B两个类mixin A { void a() { print('A'); }}mixin B { void b() { print('B'); }}class Mix with A, B { //混入多个类}void main() { Mix m = Mix(); m.a(); m.b();} 注意事项Mixins除了继承Object外,不能够继承任何其余类Mixins不能够定义构造方法办法笼罩,后盖前vue3的混入混入 (mixins)定义了一部分可复用的办法或者计算属性。混入对象能够蕴含任意组件选项。当组件应用混入对象时,所有混入对象的选项将被混入该组件自身的选项。 const myMixin = { created() { this.hello() }, methods: { hello() { console.log('wdada!') } }} // 定义一个利用,应用混入const app = Vue.createApp({ mixins: [myMixin]}) app.mount('#app') 混入合并当组件和混入对象含有同名选项时,这些选项将以失当的形式混合。比方,数据对象在外部会进行浅合并 (一层属性深度),在和组件的数据发生冲突时以组件数据优先。以下实例中,Vue 实例与混入对象蕴含了雷同的办法。从输入后果能够看出两个选项合并了。 ...

July 19, 2022 · 1 min · jiezi

关于dart:Dart-学习笔记

语法篇九种内置类型Numbers (int, double)Dart 反对两种 Number 类型:int(整数值) double(双精度浮点数字)整型反对传统的位移操作,移位(<<、>> 和 >>>)、补码 (~)、按位与 (&)、按位或 (|) 以及按位异或 (^) assert((3 << 1) == 6); // 0011 << 1 == 0110assert((3 | 4) == 7); // 0011 | 0100 == 0111assert((3 & 4) == 0); // 0011 & 0100 == 0000Strings (String)应用三个单引号或者三个双引号也能创立多行字符串在字符串中,请以 ${表达式} 的模式应用表达式,如果表达式是一个标识符(var s = 'string'),能够省略掉 {}能够应用 + 运算符或并列搁置多个字符串来连贯字符串应用三个单引号或者三个双引号也能创立多行字符串在字符串前加上 r 作为前缀创立 “raw” 字符串, 则不会被本义 Booleans (bool) Lists (也被称为 arrays) var list = [1, 2, 3];在 List 字面量前增加 const 关键字会创立一个编译时常量 ...

July 11, 2022 · 4 min · jiezi

关于dart:dart系列之和null说再见null使用最佳实践

简介null可能是大家在编写程序中最为头疼的一个货色,稍不注意的状况下就有可能应用到了这个空字符。所以dart在2.12引入了nll safety,默认状况下强制所有的类型都不为null,只有在你认为它能够为null的时候才能够设置为null。 尽管有了null safety,然而这里还有一些咱们须要思考的null的最佳实际。 不须要初始化对象为null在dart2.12之后,所有的对象都强制为非空的,除非你显示指定其为可空的对象。 如果一个对象能够为空,那么咱们能够这样指定: String? name;如果定义一个对象能够为空,那么对dart来说会隐式对其初始化为null。 所以上面的显示初始化为null是齐全没有必要的: String? name=null;同样的,如果参数是一个能够为空的对象,那么dart也会将其初始化为null,咱们也没有必要显示去设置其值: void echoName(String? name){ print(name);}null的三元操作符所谓三元就是有三个变量,咱们常见的三元操作符就是?:,通常来说是这样用的: name==null?true:false;下面的逻辑实际上是把一个null转换成了一个bool类型。 为了实现这个性能,dart提供了一个更加简洁的操作符??, 能够这样应用: name??false;下面的代码示意如果name是空,则返回false。 留神,这里只是返回值扭转了,然而name值自身并没有变动,也不会将name从一个可为空的类型,变成不为空的类型。所以如果咱们在if语句外面对字符进行判断,则还是须要显示进行null的比拟: int measureMessage(String? message) { if (message != null && message.isNotEmpty) { // dart晓得message不为空 return message.length; } return 0;}如果这样编写,则会出现异常: int measureMessage(String? message) { if (message?.isNotEmpty ?? false) { //dart并不知道message不为空 return message!.length; } return 0;}如果在应用中须要判断类型是否为空,则不要应用latelate是做什么用的呢?late示意该类型目前不会初始化,然而会在将来的某个工夫对其进行初始化。 所以,如果你用late示意某个类型,那么在后续应用的时候是不须要进行手动判断该类型是否为空的。 如果你依然要手动判断,那么就没必要设置该类型为late。 本地变量的类型晋升dart有一个十分好的个性,就是当咱们判断一个变量不为空之后,该变量就会被晋升为非空变量。 当晋升为非空变量之后,就能够自在拜访该非空变量外部的属性和办法了。 然而惋惜的是,dart中的类型晋升只是针对与local变量或者参数而言的,对于类变量或者是top level的变量并不实用,所以咱们须要将这些变量拷贝到本地变量,从而应用类型晋升的个性。 咱们看上面的例子: class UploadException { final Response? response; UploadException([this.response]); @override String toString() { var response = this.response; if (response != null) { return 'Could not complete upload to ${response.url} ' '(error code ${response.errorCode}): ${response.reason}.'; } return 'Could not upload (no response).'; }}其中UploadException中的response是一个顶级变量,尽管咱们对其进行测试是否为空,然而在应用的过程中还是不能间接拜访其外部的属性,因为response可能为空。 ...

January 17, 2022 · 1 min · jiezi

关于dart:dart系列之手写LibraryLibrary编写最佳实践

简介Library是dart用来组织代码的一种十分有用的形式,通过定义不同的Library,能够将十分有用的dart代码进行封装,从而提供给其余的我的项目应用。尽管咱们能够自在应用import或者export来对library进行导入和导入。然而什么样的用法才是最合适的用法呢? 一起来看看吧。 应用part和part of尽管很多程序员厌恶应用part,然而dart的确提供了part这种性能用来将一个大的lib拆分成多个小的文件。 没错,和part的中文含意一样,part就是将lib文件进行拆分用的。 part of示意以后文件是另外一个主文件的一部分。part示意主文件是由援用的文件组成的。 咱们举个例子,如果当初有三个文件student_age.dart,student_name.dart和student.dart. 其中后面两个文件是前面一个文件的组成部分。 student_age.dart: part of student;int getAge(){ return 18;}student_name.dart: part of student;String getName(){ return "jack";}student.dart: library student;part 'some/other/student_age.dart';part 'some/other/student_name.dart';下面的代码有什么问题呢? 下面代码的问题在于对于student_age.dart来说,外面的part of只是指定了所属的library,然而咱们读起来会一头雾水,因为不晓得具体的library到底在什么中央。 所以应该这样写: part of '../../student.dart';src中的文件默认状况下lib目录下的src文件只是package外部应用的,不容许被内部的我的项目所调用。 所以咱们肯定不要间接引入lib包中的src文件。 package中的lib文件对于package来说,lib中的文件是能够被导出的文件,然而咱们在引入package的时候最好不要应用绝对路径或者相对路径间接导入lib中的文件。 而是须要应用import 'package:'. 举个例子,如果咱们有上面构造的library文件: my_package└─ lib └─ api.dart test └─ api_test.dartapi.dart就是咱们要导出的文件。如果咱们在api_test.dart中须要援用api.dart,则能够有上面两种形式: import 'package:my_package/api.dart';和: import '../lib/api.dart';其中下面一种形式是官网举荐的形式,为什么不应用上面一种形式呢?这是因为相对路径的形式只能在包外部应用。并且dart官网不倡议将lib放在援用门路中,如果要援用lib外部的文件, 肯定要应用package:。 当然,如果是package外部的援用,则优先应用相对路径,比方: my_package└─ lib ├─ src │ └─ stuff.dart │ └─ utils.dart └─ api.dart test │─ api_test.dart └─ test_utils.dart那么对应lib/api.dart来说,能够这样援用: import 'src/stuff.dart';import 'src/utils.dart';对于utils.dart来说,能够这样援用: ...

January 10, 2022 · 1 min · jiezi

关于dart:dart系列之dart代码最佳实践

简介每种语言都有本人的代码格调,这种代码格调是跟语言个性非亲非故的。如果在编码的过程中遵循这种对立的编码规定,会给咱们的业务带来十分多的便当。 同样的,对应dart而已,也有属于本人的编码格调,一起来看看吧。 命名规定一般来说,这个世界上有三种命名规定,别离是UpperCamelCase,lowerCamelCase和lowercase_with_underscores. UpperCamelCase示意的是驼峰格局,也就是首字母大写,其余的字母小写。 而lowerCamelCase也是驼峰格局,不同的是它的第一个单词的首字母是小写的。 lowercase_with_underscores则是将单词用下划线进行连贯。 对于类,typedef,枚举这些类型,个别都应用的是UpperCamelCase模式: class ClassRoom {}typedef Predicate<T> = bool Function(T value);对于类的实例来说,应用lowerCamelCase: const classRoom = ClassRoom();对于办法名来说,也应用lowerCamelCase: void main() {}之前咱们讲到了dart 2.7引入的extension,extension也须要应用UpperCamelCase: extension StringCompare on String { ... }对于libraries, packages, 目录和源文件来说,应用lowercase_with_underscores,如下所示: library common_convert.string_convert;import 'lib_one.dart';import 'lib_two.dart';如果要将import的lib进行重命名,则须要应用lowercase_with_underscores,如下所示: import 'lib_one.dart' as lib_one;对于某些回调函数中的参数,如果并没有应用到的话,则能够用_来代替: futureOfVoid.then((_) { print('Operation complete.');});如果是private属性,则举荐在名字后面加上_,示意它是一个公有值。 import中的程序在dart中,咱们须要应用到其余的package,一般来说咱们在编码过程中并不会特地留神到import的程序。 不过dart对于import的程序也是有举荐的。 首先 “dart:”,须要放在所有其余的import之前: import 'dart:html';import 'package:bar/bar.dart';而"package:" 须要放在外部我的项目援用之前: import 'package:foo/foo.dart';import 'util.dart';如果须要导出的话,export须要和import辨别开: import 'src/foo_bar.dart';export 'src/error.dart';而后依照下面提到的程序对具体的import按字母表的程序进行排序。 格式化对于dart来说,dart语言自身是不辨认空格的,然而对于人类来说,须要通过空格来格式化代码,从而达到可良好浏览的目标。 为了对立格局,dart提供了dart format命令. 尽管dart format命令为你做了99%的工作,然而还有1%的工作是你须要本人做的 。 比方:一行不超过80个字符,所有的流控制语句都用大括号括起来等等其余一些要留神的工作。 总结以上就是dart中的代码格调总结。 本文已收录于 http://www.flydean.com/27-dart-style/ 最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现! 欢送关注我的公众号:「程序那些事」,懂技术,更懂你! ...

January 4, 2022 · 1 min · jiezi

关于dart:dart系列之你的地盘你做主使用Extension对类进行扩展

简介个别状况要扩大一个类,须要继承这个类,这是在大多数java或者其余面向对象语言中要做的事件。 然而有些时候扩大类并不是特地好用,首先在有些语言中,有些类是禁止被扩大的。即便能够被扩大,然而扩大之后的类是一个新的类,而不是原来的父类,所以在应用的过程中可能会呈现一些类型转换的问题。 那么在dart中是怎么解决这个问题的呢? dart中extension的应用dart在2.7之后,引入了extension,用来对类的办法进行扩大。 到底怎么扩大呢?咱们举个例子. 咱们能够将字符串转换为int,通过调用int的parse办法,如下所示: int.parse('18')然而通过int类来进行转换通常不太直观,咱们心愿可能在String类中提供一个toInt的办法,能够间接调用,将字符串转换成为int。 '18'.toInt()然而很遗憾,String并没有提供toInt的办法,所以咱们能够通过extension来对String进行扩大: extension StringToNumber on String { int toInt() { return int.parse(this); } // ···}如果这个文件的名字叫做string_to_number.dart,那么咱们能够这样应用: import 'string_to_number.dart';// ···print('18'.parseInt()); dart中办法扩大最为不便的是,你只有引入对应的lib,应用的时候甚至都不晓得在应用lib的扩大。 当然,并不是所有的类都能够应用extention进行扩大。比方dynamic类型就不能进行扩大。 然而应用var类型,只有该类型能够被推断进去,那么就能够应用extention扩大。 API抵触既然能够对lib进行扩大,那么就有可能呈现API抵触的状况。那么怎么解决API抵触呢? 比方咱们须要应用两个lib扩大文件,extention1.dart和extention2.dart.然而两个扩大文件中都定义了parseInt办法对String进行扩大。 如果同时援用的话,就会呈现问题。 这时候能够应用show或者hide来限度具体应用哪一个扩大文件的中的办法。 import 'extention1.dart';import 'extention2.dart' hide StringToNumber2;print('18'.parseInt());还有一种状况就是显示调用extension,如下所示: import 'extention1.dart';import 'extention2.dart';print(StringToNumber('18').parseInt());print(StringToNumber2('18').parseInt());通过extention的名字来进行辨别。 如果两个extention的名字也雷同的话,那么能够通过prefix来进行辨别: import 'extention1.dart';import 'extention2.dart' as ext2;print(StringToNumber('18').parseInt());print(ext2.StringToNumber('18').parseInt());extention的实现实现扩大很简略,实现语法如下: extension <extension name> on <type> { (<member definition>)*}上面是一个扩大String的例子: extension NumberParsing on String { int parseInt() { return int.parse(this); } double parseDouble() { return double.parse(this); }}extension还能够扩大泛型参数: ...

December 31, 2021 · 1 min · jiezi

关于dart:dart系列之dart优秀的秘诀隔离机制

简介之前介绍了很多dart中的异步编程技巧,不晓得大家有没有发现一个问题,如果是在java的异步编程中,必定会提到锁和并发机制,然而对于dart来说,如同素来没有听到多线程和并发的问题,这是为什么呢? 明天,给大家解说一下dart中的隔离机制,大家就明确了。 dart中的隔离机制dart是一个单线程的语言,然而作为一个单线程的语言,dart却反对Future,Stream等异步个性。这一切都是隔离机制和事件循环带来的后果。 首先看一下dart中的隔离机制。 所谓隔离指的是dart运行的一个特定的空间,这个空间领有独自的内存和单线程的事件循环。 如下图所示: 在java或者c++等其余语言中,多个线程是共享内存空间的,尽管带来了并发和数据沟通的不便路径,然而同时也造成了并发编程的艰难。 因为咱们须要思考多线程之间数据的同步,于是额定多出了很多锁的机制,具体理解或者用过的人应该都会很懊恼。 多线程最大的缺点就是要求程序员的罗辑思维和编程技巧足够优良,这样才可能设计出完满运行的多线程程序。 然而在dart中,这些都不是什么问题。dart中所有的线程都领有本人的运行空间,这个线程的工作就是运行事件循环。 那么问题来了,主线程在处理事件循环,然而如果遇到了一个十分耗时的操作,该怎么办呢? 如果间接在主线程中运行,则可能会导致主线程的阻塞。 dart也充分考虑到了这个问题,所以dart提供了一个Isolate的类来对隔离进行治理。 因为dart程序自身就在一个Isolate中运行,所以如果在dart中定义一个Isolate,那么这个Isolate通常示意的是另外一个,须要和以后Isolate进行通信的Isolate。 生成一个Isolate那么如何在以后的dart程序中生成一个Isolate呢? Isolate提供了三种生成办法。 一个十分罕用的是Isolate的工厂办法spawn: external static Future<Isolate> spawn<T>( void entryPoint(T message), T message, {bool paused = false, bool errorsAreFatal = true, SendPort? onExit, SendPort? onError, @Since("2.3") String? debugName});spawn会创立一个新的Isolate,调用它须要传入几个参数: entryPoint示意的是生成新Isolate的时候须要调用的函数。entryPoint承受一个message参数。通常来说message是一个SendPort对象,用于两个Isolate之间的沟通。 paused示意新生成的Isolate是否处于暂停状态,他相当于: isolate.pause(isolate.pauseCapability)如果后续须要勾销暂停状态,则能够调用: isolate.resume(isolate.pauseCapability)errorsAreFatal 对应的是setErrorsFatal办法。 onExit对应的是addOnExitListener, onError对应的是addErrorListener。 debugName示意的是Isolate在调试的时候展现的名字。 如果spawn出错,则会抛出IsolateSpawnException异样: class IsolateSpawnException implements Exception { /// Error message reported by the spawn operation. final String message; @pragma("vm:entry-point") IsolateSpawnException(this.message); String toString() => "IsolateSpawnException: $message";}spawn办法生成的是和以后代码一样的Isolate。如果想要应用不同的代码来生成,则能够应用spawnUri,通过传入对应的Uri地址,从而生成不一样的code。 ...

December 27, 2021 · 1 min · jiezi

关于dart:dart系列之安全看我dart中的安全特性null-safety

简介在Dart 2.12中引入了null safety的新个性,也就是说dart程序中默认类型都是非空的,除非你显示通知编译器,这个类型能够为空。 看起来是一个小小的改变,然而这个小小的改变导致了很多Dart包的大版本升级,从而导致应用Dart2.12之前的版本跟应用dart2.12之后的版本齐全就是两个不同的世界。 真的这么微妙吗?一起来看看Dart 2.12 null safety的个性吧。 Non-nullable类型在感触Non-nullable类型之前,咱们先看一段代码: void main(){ String name; print('name is $name.');}代码很简略,咱们定义了一个String类型的name字符串,而后在print语句中打印它。 如果你是在dart 2.12版本之前,那么是没有问题的。 然而到了2.12,则会报错: The non-nullable local variable 'name' must be assigned before it can be used.Try giving it an initializer expression, or ensure that it's assigned on every execution path.意思就是说,name是非空的,你必须要给他赋个值。 通过强制不为空,保障了代码的安全性,十分好用。 那么如果name就能够为空怎么解决呢? 别急,咱们能够给能够为空的类型前面加上?即可: void main(){ String? name; print('name is $name.');}Nullable List Of Strings 和 List Of Nullable Strings如果咱们要创立一个List,list外面蕴含的是String,则能够这样创立: List<String> aListOfStrings = ['one', 'two', 'three'];在dart 2.12中,非空查看也被用到了泛型中,所以,默认状况下List中的String也不能为空,如果非要为空,则须要这样写: ...

December 22, 2021 · 1 min · jiezi

关于dart:dart系列之如丝滑般柔顺操作文件和目录

简介文件操作是IO中十分常见的一种操作,那么对应dart语言来说,操作文件是不是很简略呢?实际上dart提供了两种读取文件的形式,一种是一次性全副读取,一种是将文件读取为流。 一次性读取的毛病是须要将文件内容一次性全副载入到内存中,如果遇到文件比拟大的状况,就会比拟难堪。所以还须要流式读取文件的形式。一起来看看dart中这两种文件的读取形式吧。 File事实上dart中有很多中央都有File这个类,这里咱们要解说的File类是dart:io包中的。 读取整个文件File代表一个整体的文件,他有三个构造函数,别离是: factory File(String path) factory File.fromUri(Uri uri)factory File.fromRawPath(Uint8List rawPath)其中最罕用的就是第一个构造函数。 咱们能够这样来结构一个文件: var file = File('file.txt');有了文件之后,就能够调用File中的各种读取办法。 文件读取自身有两种模式,一种是文本,一种是二进制。 如果是文本文件,File提供了readAsString的办法,将整个文件读取为字符串。 Future<String> readAsString({Encoding encoding: utf8});咱们能够这样应用: var stringContents = await file.readAsString();另外,咱们还能够一行一行的对文件进行读取: Future<List<String>> readAsLines({Encoding encoding: utf8});后果返回的是一个List,list中示意文件每行的内容。 var lines = await file.readAsLines();下面两个办法是异步的办法,File还提供了两个同步的办法: String readAsStringSync({Encoding encoding: utf8});List<String> readAsLinesSync({Encoding encoding: utf8});如果文件是二进制,那么能够应用readAsBytes或者同步的办法readAsBytesSync: Future<Uint8List> readAsBytes();Uint8List readAsBytesSync();dart中示意二进制有一个专门的类型叫做Uint8List,他实际上示意的是一个int的List。 还是刚刚的文件,咱们看下怎么以二进制的模式进行读取: var file = File('file.txt');var contents = await file.readAsBytes();以流的模式读取文件下面咱们讲到的读取形式,都是一次性读取整个文件,毛病就是如果文件太大的话,可能造成内存空间的压力。 所以File为咱们提供了另外一种读取文件的办法,流的模式来读取文件. 相应的定义方法如下: Stream<List<int>> openRead([int? start, int? end]);咱们看一个根本的应用: import 'dart:io';import 'dart:convert';Future<void> main() async { var file = File('file.txt'); Stream<List<int>> inputStream = file.openRead(); var lines = utf8.decoder .bind(inputStream) .transform(const LineSplitter()); try { await for (final line in lines) { print('Got ${line.length} characters from stream'); } print('file is now closed'); } catch (e) { print(e); }}随机拜访个别状况下文件是程序拜访的,然而有时候咱们须要跳过某些后面的数据,间接跳转到指标地址,则须要对文件进行随机拜访。 ...

December 20, 2021 · 1 min · jiezi

关于dart:dart系列之实时通讯在浏览器中使用WebSockets

简介web客户端和服务器端通信有两种形式,一种是应用HTTP申请,从服务器端申请数据。这种申请的毛病就是只能客户端拉取服务器端的数据,只能进行轮询。 另外一种形式是应用WebSocket,在客户端和服务器端之间建设通道,这样服务器就能够间接向客户端推送音讯,防止了客户端频繁的拉取服务器端的数据,造成服务器端的压力。 dart:html包中就蕴含了WebSockets的相干操作,一起来看看吧。 dart:html中的WebSocketsWebSocket应用的是ws和wss作为URI的标记符。其中ws示意的是websocket,而wss示意的是WebSocket Secure。 WebSocket能够分为客户端和服务器端两局部。dart:html中提供的WebSocket对象中蕴含的是客户端的逻辑。 咱们先看下WebSocket类的定义: @SupportedBrowser(SupportedBrowser.CHROME)@SupportedBrowser(SupportedBrowser.FIREFOX)@SupportedBrowser(SupportedBrowser.IE, '10')@SupportedBrowser(SupportedBrowser.SAFARI)@Unstable()@Native("WebSocket")class WebSocket extends EventTarget能够看到它继承自EventTarget,并且反对chrome、firfox、IE10和Safari这几种浏览器。 创立一个WebSocketWebSocket有两种创立形式,第一种是带protocal,一种是不带protocal: factory WebSocket(String url, [Object? protocols]) { if (protocols != null) { return WebSocket._create_1(url, protocols); } return WebSocket._create_2(url); }这里的protocols指的是在webSocket协定框架之下的子协定,它示意的是音讯的格局,比方应用soap或者wamp。 子协定是在WebSocket协定根底上倒退进去的协定,次要用于具体的场景的解决,它是是在WebSocket协定之上,建设的更加严格的标准。 咱们看一个最简略的创立WebSocket的代码: var webSocket = new WebSocket('ws://127.0.0.1:1337/ws');以上如果服务器存在的话,就会胜利建设一个WebSocket的连贯。 WebSocket的状态WebSocket有四个状态,别离是closed, closing, connecting和open,都是以static来定义的,能够间接援用: static const int CLOSED = 3; static const int CLOSING = 2; static const int CONNECTING = 0; static const int OPEN = 1;发送音讯dart中的WebSocket定义了5中发送音讯的办法: void send(data) native; void sendBlob(Blob data) native; void sendByteBuffer(ByteBuffer data) native; void sendString(String data) native; void sendTypedData(TypedData data) native;WebSocket反对发送[Blob], [ByteBuffer], [String] 或者 [TypedData] 这四种数据类型。 ...

December 17, 2021 · 1 min · jiezi

关于dart:dart系列之浏览器中的舞者用dart发送HTTP请求

简介dart:html包为dart提供了构建浏览器客户端的一些必须的组件,之前咱们提到了HTML和DOM的操作,除了这些之外,咱们在浏览器端另一个罕用的操作就是应用XMLHttpRequest去做异步HTTP资源的申请,也就是AJAX申请。 dart同样提供了相似JS中XMLHttpRequest的封装,其对应的类叫做HttpRequest,一起来看看在dart中怎么应用HttpRequest吧。 发送GET申请尽管古代的web APP被各种框架所封装,然而归根结底他还是一个AJAX的富客户端利用。咱们通过各种异步的HTTP申请向服务器端申请数据,而后展现在页面上。一般来说数据的交互格局是JSON,当然也能够有其余的数据交互格局。 AJAX中最罕用的形式就是向服务器端发送get申请,对应的HttpRequest有一个getString办法: static Future<String> getString(String url, {bool? withCredentials, void onProgress(ProgressEvent e)?}) { return request(url, withCredentials: withCredentials, onProgress: onProgress) .then((HttpRequest xhr) => xhr.responseText!); }留神,getString办法是一个类办法,所以间接应用HttpRequest类来调用: var name = Uri.encodeQueryComponent('John'); var id = Uri.encodeQueryComponent('42'); HttpRequest.getString('users.json?name=$name&id=$id') .then((String resp) { // Do something with the response. });因为getString返回的是一个Future,所以能够间接在getString前面接then语句,来获取返回的值。 当然,你也能够在async办法中应用await来获取返回值。 Future<void> main() async { String pageHtml = await HttpRequest.getString(url); // Do something with pageHtml...}或者应用try catch来捕捉异样: try { var data = await HttpRequest.getString(jsonUri); // Process data...} catch (e) { // Handle exception...}发送post申请GET是从服务器拉取数据,相应的POST就是通用的向服务器中提交数据的办法。在HttpRequest中,对应的办法是postFormData: ...

December 15, 2021 · 2 min · jiezi

关于dart:netty系列之性能为王创建多路复用http2服务器

简介在之前的文章中,咱们提到了在netty的客户端通过应用Http2FrameCodec和Http2MultiplexHandler能够反对多路复用,也就是说在一个连贯的channel根底上创立多个子channel,通过子channel来解决不同的stream,从而达到多路复用的目标。 既然客户端能够做到多路复用,同样的服务器端也能够,明天给大家介绍一下如何在netty的服务器端打造一个反对http2协定的多路复用服务器。 多路复用的根底netty中对于http2多路复用的根底类是Http2FrameCodec、Http2MultiplexHandler和Http2MultiplexCodec。 Http2FrameCodec是将底层的HTTP/2 frames音讯映射成为netty中的Http2Frame对象。 有了Http2Frame对象就能够通过Http2MultiplexHandler对新创建的stream开启不同的channel。 Http2MultiplexCodec是Http2FrameCodec和Http2MultiplexHandler的结合体,然而曾经不再被举荐应用了。 因为Http2FrameCodec继承自Http2ConnectionHandler,而Http2MultiplexHandler继承自Http2ChannelDuplexHandler,所以这两个类能够同时在客户端和服务器端应用。 客户端应用Http2FrameCodecBuilder.forClient().build()来取得Http2FrameCodec,而服务器端通过Http2FrameCodecBuilder.forServer().build()来取得Http2FrameCodec。 多路复用在server端的应用配置TLS处理器对于服务器端,同样须要解决TLS和一般clear text两种状况。对于TLS来说,咱们须要自建ProtocolNegotiationHandler继承自ApplicationProtocolNegotiationHandler,而后实现configurePipeline办法,在其中别离解决http2和http1.1的连贯: protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { if (ApplicationProtocolNames.HTTP_2.equals(protocol)) { //增加多路复用反对 ctx.pipeline().addLast(Http2FrameCodecBuilder.forServer().build()); ctx.pipeline().addLast(new Http2MultiplexHandler(new CustMultiplexHttp2Handler())); return; } if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) { ctx.pipeline().addLast(new HttpServerCodec(), new HttpObjectAggregator(MAX_CONTENT_LENGTH), new CustHttp1Handler("ALPN Negotiation")); return; } throw new IllegalStateException("未知协定: " + protocol); }首先增加Http2FrameCodec,而后增加Http2MultiplexHandler。因为Http2MultiplexHandler曾经封装了多路复用的细节,所以自定义的handler只须要实现失常的音讯解决逻辑即可。 因为Http2FrameCodec曾经对音讯进行了转换成为HTTP2Frame对象,所以只须要解决具体的Frame对象: public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof Http2HeadersFrame) { onHeadersRead(ctx, (Http2HeadersFrame) msg); } else if (msg instanceof Http2DataFrame) { onDataRead(ctx, (Http2DataFrame) msg); } else { super.channelRead(ctx, msg); } }配置clear text upgrade对于h2c的降级来说,须要向pipline中传入sourceCodec和upgradeHandler两个处理器。 ...

December 14, 2021 · 1 min · jiezi

关于dart:dart系列之HTML的专属领域除了javascript之外dart也可以

简介尽管dart能够同时用作客户端和服务器端,然而基本上dart还是用做flutter开发的根本语言而应用的。除了andorid和ios之外,web就是最常见和通用的平台了,dart也提供了对HTML的原生反对,这个反对就是dart:html包。 dart:html提供了对DOM对象的各种有用的操作和对HTML5 API的反对。这样咱们能够间接应用dart来操作HTML。 除了DOM之外,dart:html还能够对css进行操作,应用dart:html也非常简单: import 'dart:html';DOM操作对于DOM操作来说,首先是须要找到这个元素。 dart提供了querySelector() 和 querySelectorAll()办法,能够依据ID, class, tag, name或者这些元素的汇合来进行查找。 同样都是query办法,两者的不同在于,querySelector只返回找到的第一个元素,而querySelectorAll返回找到的所有元素。 所以querySelector返回的是一个Element,而querySelectorAll返回的是一个汇合List<Element>。 Element idElement = querySelector('#someId')!;Element classElement = querySelector('.some-class')!;List<Element> divElements = querySelectorAll('div');List<Element> textInputElements = querySelectorAll( 'input[type="text"]',);List<Element> specialElement = querySelectorAll('#someId div.class');下面就是咱们查找DOM中元素的操作。找到之后,就能够对这些元素进行操作了。 dart应用Element来示意DOM中的元素。对于每个Element来说,都领有classes, hidden, id, style, 和 title 这些属性。 如果Element中并没有要设置的属性,则能够应用attributes,如下: elem.attributes['someAttribute'] = 'someValue';当然对应某些非凡的Element,会有Element对应的子类与之绑定。 比方对于一个a标签来说,如下所示: <a id="name" href="/name/detail">详情</a>a标签对应的是dart中的AnchorElement元素。 如果要扭转a标签的href值,能够这样: var anchor = querySelector('#name') as AnchorElement;anchor.href = 'http://www.flydean.com';还能够增加、替换或者删除对应的节点: querySelector('#id')!.nodes.add(elem);querySelector('#id')!.replaceWith(elem);querySelector('#id')?.remove();下面咱们应用了一个非凡的运算符,感叹号,示意的是将一个可为空的类型转换成为不为空的类型。CSS操作CSS实际上就是element中的class,当咱们获取到element之后,就能够调用他的classes字段,而后对CSS进行解决。 elem.classes返回的是一个list,咱们能够向其增加或者删除对应的class。 var name = querySelector('#id')!;name.classes.add('redline');有class当然是最好了,class也是咱们举荐的写法。然而有时候还是须要间接在element中增加style,如下所示: name.style ..fontWeight = 'bold' ..fontSize = '3em';处理事件和DOM的交互就是各种事件,向element中增加event,能够应用element.onEvent.listen(function). 比方咱们能够增加click事件: querySelector('#id')!.onClick.listen((e) { // do something});上面是罕用的一些event: ...

December 7, 2021 · 1 min · jiezi

关于dart:dart系列之数学什么的就是小意思看我dart如何玩转它

简介dart也能够进行数学运算,dart为数学爱好者专门创立了一个dart:math包来解决数学方面的各种操作。dart:math包提供了正弦,余弦,最大值,最小值和随机数等操作。 一起来看看dart:math包都能做什么吧。 dart:math包的形成如果你去查看dart:math的源代码,你会发现,dart:math包其实很简略,它外面只有4个文件。别离是: math.dart,random.dart,point.dart和rectangle.dart。 前面两个文件,次要跟二维坐标无关,这里不具体阐明。 咱们罕用到的就是后面两个文件,math和random。 mathmath中定义了咱们在数学运算中罕用到的一些常量,如: const double e = 2.718281828459045;const double ln10 = 2.302585092994046;const double ln2 = 0.6931471805599453;const double log2e = 1.4426950408889634;const double log10e = 0.4342944819032518;const double pi = 3.1415926535897932;const double sqrt1_2 = 0.7071067811865476;const double sqrt2 = 1.4142135623730951;计算最大值和最小值: assert(max(18, 20) == 20);assert(min(18, 20) == 18);应用三角函数: assert(cos(pi) == -1.0);var degrees = 30;var radians = degrees * (pi / 180);var sinOf30degrees = sin(radians);assert((sinOf30degrees - 0.5).abs() < 0.01);Randomdart中的random包提供了一些比拟有用的生成随机数的办法,先看下Random类的定义: abstract class Random { external factory Random([int? seed]); external factory Random.secure(); int nextInt(int max); double nextDouble(); bool nextBool();}咱们能够应用Random中提供的nextInt,nextDouble和nextBool来生成对应的随机数: ...

December 3, 2021 · 1 min · jiezi

关于dart:dart系列之时间你慢点走我要在dart中抓住你

简介工夫和日期是咱们常常会在程序中应用到的对象。然而对工夫和日期的解决因为有不同时区的起因,所以始终以来都不是很好用。就像在java中,为工夫和日期批改和新增了屡次API,那么作为新生的语言dart而言,会有什么不一样的中央吗? dart中对于日期和工夫的两个十分重要的类是DateTime和Duration. 其中DateTime示意的是工夫,而Duration示意的是时间差。 DateTime先看一下DateTime的应用。 DateTime示意的是一个工夫点。因为世界时钟有UTC和本地工夫两种。所以,在应用DataTime的时候,也能够应用这两种时钟。 最简略的就是获取以后的工夫: var now = DateTime.now();如果要创立指定日期的工夫,则能够将年月日传入DateTime的构造函数: var now = DateTime(2021, 11, 20); 留神,下面创立的日期是本地日期。 如果要创立UTC time,则能够应用DateTime.utc办法: var utc = DateTime.utc(2021, 11, 20);还有一种示意工夫的办法是unix time, Unix time指的是从1970年1月1日开始所通过的秒数. DateTime有两种示意Unix time工夫的办法,别离是: DateTime.fromMicrosecondsSinceEpoch(10000); DateTime.fromMillisecondsSinceEpoch(10000);他们的区别在于,一个示意的是微秒,一个示意的是毫秒。 DateTime还能够将字符串转换成为DateTime对象: var time= DateTime.parse('2002-02-27T14:00:00-0500');事实上,DateTime.parse能够承受多种字符类型,如下所示: `"2012-02-27"``"2012-02-27 13:27:00"` `"2012-02-27 13:27:00.123456789z"``"2012-02-27 13:27:00,123456789z"``"20120227 13:27:00"``"20120227T132700"``"20120227"``"+20120227"` `"2012-02-27T14Z"``"2012-02-27T14+00:00"`DurationDuration示意的是两个工夫之间的差值。 来看下Duration的构造函数: const Duration( {int days = 0, int hours = 0, int minutes = 0, int seconds = 0, int milliseconds = 0, int microseconds = 0}) : this._microseconds(microsecondsPerDay * days + microsecondsPerHour * hours + microsecondsPerMinute * minutes + microsecondsPerSecond * seconds + microsecondsPerMillisecond * milliseconds + microseconds);能够看到Duration能够示意从天到microseconds的距离,曾经足够用了. 应该怎么应用呢? ...

December 2, 2021 · 1 min · jiezi

关于dart:URL-URI傻傻分不清楚dart告诉你该怎么用

简介如果咱们要拜访一个网站,须要晓得这个网站的地址,网站的地址个别被称为URL,他的全称是Uniform Resource Locator。那么什么是URI呢? URI的全程是Uniform Resource Identifier,也叫做对立资源标志符。 URI用来对资源进行标记,而URL是对网络上的资源进行标记,所以URL是URI的子集。 理解了URI和URL之间的关系之后,咱们来看看dart语言对URI的反对。 dart中的URIdart中为URI创立了一个专门的类叫做Uri: abstract class Uri Uri是一个抽象类,他定义了一些对URI的基本操作。它有三个实现类,别离是_Uri,_DataUri和_SimpleUri。 接下来,咱们一起来看看,dart中的Uri都能够做什么吧。 encode和decode为什么要对encode URI? 一般来说URI中能够蕴含一些特殊字符,像是空格或者中文等等。这些字符在传输中可能不被对方所意识。所以咱们须要对Uri进行编码。 然而对于URI中的一些非凡然而有意义的字符,比方: /, :, &, #, 这些是不必被本义的。 所以咱们须要一种可能对立编码和解码的办法。 在dart中,这种办法叫做encodeFull() 和 decodeFull(): var uri = 'http://www.flydean.com/doc?title=dart uri';var encoded = Uri.encodeFull(uri);assert(encoded == 'http://www.flydean.com/doc?title=dart%20uri');var decoded = Uri.decodeFull(encoded);assert(uri == decoded);如果要编码所有的字符,包含那些有意义的字符:/, :, &, #, 那么能够应用encodeComponent() 和 decodeComponent(): var uri = 'http://www.flydean.com/doc?title=dart uri';var encoded = Uri.encodeComponent(uri);assert(encoded == 'http%3A%2F%2www.flydean.com%2Fdoc%3Ftitle%3Ddart%20uri');var decoded = Uri.decodeComponent(encoded);assert(uri == decoded);解析URIURI是由scheme,host,path,fragment这些局部组成的。咱们能够通过Uri中的这些属性来对Uri进行合成: var uri = Uri.parse('http://www.flydean.com/doc#dart');assert(uri.scheme == 'http');assert(uri.host == 'www.flydean.com');assert(uri.path == '/doc');assert(uri.fragment == 'dart');assert(uri.origin == 'http://www.flydean.com');那么怎么结构Uri呢?咱们能够应用Uri的构造函数: ...

December 1, 2021 · 1 min · jiezi

关于dart:没有人比我更会使用集合对-是dart中的集合

简介dart中的汇合有三个,别离是list,set和map。dart在dart:core包中提供了对于这三种汇合十分有用的办法,一起来看看吧。 List的应用首先是list的创立,能够创立空的list或者带值的list: var emptyList =[];var nameList = ['jack','mac'];应用List的构造函数来创立: var nameList = List.filled(2, 'max');向list中增加元素或者list: nameList.add('tony');nameList.addAll(['lili', 'bruce']);删除元素: nameList.removeAt(0);nameList.clear();dart提供了list的排序办法sort(),sort能够接一个比拟的函数,用来示意谁在前谁在后: var names = ['jack', 'tony', 'max'];fruits.sort((a, b) => a.compareTo(b));list中还能够应用泛型,示意list中固定的类型: var names = <String>[];names.add('jack');Set的应用Set示意的是不反复的元素的汇合。然而set和list不同的是set是无序的,所以你不能用index来定位set中的元素。 来看下set的根本用法: //创立一个空的setvar names = <String>{};// 增加新的元素names.addAll(['jack', 'tony', 'max']);//删除元素names.remove('jack');或者应用Set的构造函数来结构Set: var names = Set.from(['jack', 'tony', 'max']);判断汇合中元素是否存在: assert(names.contains('jack'));assert(names.containsAll(['jack', 'tony']));set还有一个intersection的操作,用来求两个set的交加: var name1 = Set<String>();name1.addAll(['jack', 'tony', 'max']);var name2 = Set.from(['tony', 'bily']);var intersection = name1.intersection(name2);Map的应用Map是一种key,value的数据类型,也是一种在程序中十分常见的数据类型。 先看下怎么创立Map: // 创立mapvar studentMap = { 'name': 'jack', 'age': '18', 'class': 'class one'};var teacherMap = Map();var teacherMap2 = Map<String, String>();增加和删除: ...

November 26, 2021 · 1 min · jiezi

关于dart:dart系列之在dart中使用数字和字符串

简介要想相熟一种语言,最简略的做法就是相熟dart提供的各种外围库。dart为咱们提供了包含dart:core,dart:async,dart:math,dart:convert,dart:html和dart:io这几种罕用的库。 明天给大家介绍一下dart:core中的数字和字符串的应用。 # 数字 dart:core中定义了三种类型的数字,别离是num,int和double。 num是所有数字的总称。int和double都是继承自num,是num的子类。 事实上,dart:core中还有以一种数据类型叫做BigInt,BigInt是一种独立的数据类型,并不是num的子类: abstract class BigInt implements Comparable<BigInt>数字中最常见的操作就是将字符串转换为数字,转换能够调用parse办法,先看下num中parse办法的定义: static num parse(String input, [@deprecated num onError(String input)?]) { num? result = tryParse(input); if (result != null) return result; if (onError == null) throw FormatException(input); return onError(input); }传入的input能够是十进制、也能够是十六进制,如下所示: assert(int.parse('18') == 18);assert(int.parse('0x05') == 5);assert(double.parse('0.50') == 0.5);num.parse会将对应的字符转换成为int或者double类型: assert(num.parse('18') is int);assert(num.parse('0.50') is double);parse办法还能够传入字符串对应的基数,比方是十进制还是十六进制: assert(int.parse('11', radix: 16) == 17);下面咱们讲到了如何将字符串转换成为数字,上面是如何将数字转换成为字符串,num提供了toString()办法,能够不便的将int和double转换成为string。 assert(18.toString() == '18');assert(3.1415.toString() == '3.1415');对于小数来说,能够应用toStringAsFixed来指定小数的位数: assert(3.1415.toStringAsFixed(2) == '3.14');如果要应用迷信记数法的话,能够应用toStringAsPrecision: assert(314.15.toStringAsPrecision(2) == '3.1e+2');字符串所有的字符串在dart中都是以UTF-16进行编码的,dart中的string定义了很多罕用的并且十分有用的办法。 ...

November 24, 2021 · 1 min · jiezi

关于dart:dart系列之在dart中使用生成器

简介ES6中在引入异步编程的同时,也引入了Generators,通过yield关键词来生成对应的数据。同样的dart也有yield关键词和生成器的概念。 什么时候生成器呢?所谓生成器就是一个可能继续产生某些数据的安装,也叫做generator。 两种返回类型的generator依据是同步生成还是异步生成,dart返回的后果也是不同的。 如果是同步返回,那么返回的是一个Iterable对象. 如果是异步返回,那么返回的是一个Stream对象。 同步的generator应用sync*关键词如下: Iterable<int> naturalsTo(int n) sync* { int k = 0; while (k < n) yield k++;}异步的generator应用的是async* 关键词如下: Stream<int> asynchronousNaturalsTo(int n) async* { int k = 0; while (k < n) yield k++;}生成关键词应用的是yield。 如果yield前面跟着的自身就是一个generator,那么须要应用yield*。 Iterable<int> naturalsDownFrom(int n) sync* { if (n > 0) { yield n; yield* naturalsDownFrom(n - 1); }}Stream的操作stream示意的是流,失去这个流之后,咱们须要从流中取出对应的数据。 从Stream中取出数据有两种形式,第一种就是应用Stream自身的API来获取Stream中的数据。 最简略的就是调用stream的listen办法: StreamSubscription<T> listen(void onData(T event)?, {Function? onError, void onDone()?, bool? cancelOnError});listen能够接数据的解决办法,具体应用如下: final startingDir = Directory(searchPath); startingDir.list().listen((entity) { if (entity is File) { searchFile(entity, searchTerms); } });默认的办法是onData办法。 ...

November 23, 2021 · 1 min · jiezi

关于dart:dart系列之dart中的异步编程

简介相熟javascript的敌人应该晓得,在ES6中引入了await和async的语法,能够不便的进行异步编程,从而解脱了回调天堂。dart作为一种新生的语言,没有理由不继承这种优良的品质。很天然的,dart中也有await和async语言,一起来看看吧。 为什么要用异步编程那么为什么要用异步编程呢? 只用同步不可能解决吗? 其实大多状况下同步曾经够用了,然而在上面的几种状况下,同步的场景还是有缺点的。 须要花很长时间从网络上下载数据的状况。读取数据库的耗时状况。从文件读取数据的状况。总结而言,如果某些操作须要破费大量的工夫,那么就能够用到异步编程了。 怎么应用async是办法的描述符,如果要应用await,则必须配合async一起应用: Future<void> checkVersion() async { var version = await lookUpVersion(); // Do something with version}留神,await前面个别接着的是Future对象。先看一个谬误应用异步编程的例子: String createOrderMessage() { var order = fetchUserOrder(); return 'Your order is: $order';}Future<String> fetchUserOrder() => Future.delayed( const Duration(seconds: 2), () => 'Order one!', );void main() { print(createOrderMessage());}下面的代码本意是打印出从数据库耗时取出的数据,然而后果并不是设想的那样,其起因就是fetchUserOrder办法是一个异步办法,所以不会立刻返回,从而导致后果打印失败。 将下面的代码用async改写: Future<String> createOrderMessage() async { var order = await fetchUserOrder(); return 'Your order is: $order';}Future<String> fetchUserOrder() => Future.delayed( const Duration(seconds: 2), () => 'Large Latte', );Future<void> main() async { print('Fetching user order...'); print(await createOrderMessage());}Future下面咱们在应用async和await的过程中应用到了Future。在java中Future示意的是线程的执行后果。在dart中Future示意的是一个异步执行的后果。 ...

November 21, 2021 · 1 min · jiezi

关于dart:dart系列之创建Library-package

简介在dart零碎中,有pubspec.yaml文件的利用就能够被成为一个package。而Libray package是一类非凡的package,这种包能够被其余的我的项目所依赖. 也就是通常所说的库。 如果你也想你写的dart程序能够上传到pub.dev上,或者提供给他人应用,则来看看这篇文章吧。 Library package的构造先看下library package的构造: app3├── lib│   └── main.dart└── pubspce.yaml这是一个最简略的Library package的构造,在root目录上面,咱们有一个pubspce.yaml文件。而后还有一个lib目录寄存的是library的代码。 一般来说lib上面的库是能够供内部进行援用的。如果是library外部的文件,则能够放到lib/src目录上面,这外面的文件示意是private的,是不应该被别的程序引入的。 如果想要将src中的包导出供内部应用,则能够在lib上面的dart文件中应用export,将须要用到的lib导出。这样其余用户只须要import这个一个文件即可。 export的例子如下: library animation;export 'src/animation/animation.dart';export 'src/animation/animation_controller.dart';export 'src/animation/animations.dart';export 'src/animation/curves.dart';export 'src/animation/listener_helpers.dart';export 'src/animation/tween.dart';export 'src/animation/tween_sequence.dart';下面的代码是flutter的animation库。 导入library怎么应用呢?咱们能够应用import语句来导入对应的lib: import 'package:flutter/animation.dart';如果是外部文件的导入,则能够应用相对路径。只有在导入内部package的时候才须要加上package:前缀。 条件导入和导出library因为dart是设计在能够在不同的平台上进行工作,所以一个library在不同的平台可能须要导入或者导出不同的library文件, 这就叫做条件导入和导出。 比方能够通过判断dart库是io库还是html库来抉择导出不同的文件: export 'src/hw_none.dart' // Stub implementation if (dart.library.io) 'src/hw_io.dart' // dart:io implementation if (dart.library.html) 'src/hw_html.dart'; // dart:html implementation下面的意思是,如果在app中可能应用dart:io,那么就导出src/hw_io.dart. 如果可能应用dart:html,那么就导出src/hw_html.dart,否则就导出src/hw_none.dart。 如果是条件导入的话,将export改成import即可。 增加其余无效的文件因为不同的library有不同的作用,所以通常须要增加一些额定的文件来保障library的有效性和完整性。 为了保障library的有效性,须要增加测试代码,测试代码通常放在test目录中。 如果是创立命令行工具,则须要将对应的工具放到tools目录中。 另外还有 README.md 和 CHANGELOG.md等文件。 library的文档dart文档能够应用 dartdoc这个工具来生成。dart中的文档格局是以///结尾的,如下: /// The event handler responsible for updating the badge in the UI.void updateBadge() { ...}公布到pub.dev一个最好共享library的形式就是将其发送到pub.dev上。具体的命令是:pub publish。 ...

November 20, 2021 · 1 min · jiezi

关于dart:dart系列之元世界pubspecyaml文件详解

简介pubspec.yaml是所有dart我的项目的灵魂,它蕴含了所有dart我的项目的依赖信息和其余元信息,所以pubspec.yaml就是dart我的项目的meta! pubspec.yaml反对的字段依据dart的定义,pubspec.yaml中能够蕴含上面的字段: 字段名是否必须字段形容name是package的名字version如果公布到pub.dev,则须要package的版本号description如果公布到pub.dev,则须要package的形容信息homepage否package的主页repository否package的源代码地址issue_tracker否package问题跟踪地址documentation否package的文档信息dependencies否package的依赖信息dev_dependencies否pacakge的dev依赖信息dependency_overrides否想要笼罩的packageenvironmentdart2须要 executables否package的可执行文件门路publish_to否package将如何公布留神,以上是dart中pubspec.yaml反对的字段,如果是在flutter环境中,则会有些额定反对的字段。# 一个例子 咱们看一个具体的例子: name: my_appversion: 11.15description: >- this is a new apphomepage: http://www.flydean.comdocumentation: http://www.flydean.comenvironment: sdk: '>=2.10.0 <3.0.0'dependencies: efts: ^2.0.4 transmogrify: ^0.4.0dev_dependencies: test: '>=1.15.0 <2.0.0'字段详情上面来看下各个字段的详情和限度状况: Namename示意的是包的名字,name必须是全小写,如果有多个词的话,能够用下划线来辨别,如:my_app. 并且只能应用小写字母和数字的组合,同时不能以数字结尾,并且不要应用dart中的保留字。 VersionVersion示意的是版本号,版本号是由点宰割的三个数字,如:11.15.0. 前面还能够跟上build版本号:+1, +2, +hotfix.oopsie, 或者预公布版本等:-dev.4, -alpha.12, -beta.7, -rc.5. Descriptionpackage的形容信息最好应用英文来刻画,长度是60 到180个字符,示意这个包的作用。 Dependencies有两种依赖信息,一种是所有应用到这个packages的人都须要用到的依赖,这种依赖放在dependencies中。 还有一种是只用在以后pacakge开发中的包,这种依赖放在dev_dependencies中。 在某些状况下,咱们有可能须要笼罩某些依赖包,则能够放在:dependency_overrides中。 Executables有些pacakges提供的是工具供大家应用,这些工具有可能是命令行工具,所以须要在executables中指定能够执行的命令的门路。 比方上面的配置: executables: slidy: main fvm:那么在执行pub global activate之后,就能够在全局执行slidy来执行bin/main.dart, 和fvm来执行binfvm.dart. environment因为Dart是一门新的语言,所以目前来说其变动还是挺大的。所以有些利用能够依赖于不同的dart版本,这时候就须要用到environment: environment: sdk: '>=2.10.0 <3.0.0'下面的代码中,咱们指定了dart sdk的版本范畴。 从dart1.19之后,environment:中还反对指定flutter的版本: environment: sdk: '>=1.19.0 <3.0.0' flutter: ^0.1.2总结以上就是dart的元世界pubspec.yaml详解。 本文已收录于 http://www.flydean.com/10-dart-pubspec/ 最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现! 欢送关注我的公众号:「程序那些事」,懂技术,更懂你! ...

November 19, 2021 · 1 min · jiezi

关于dart:dart系列之在dart中使用packages

简介java中应用jar包来封装有用的性能,而后将其散发到maven仓库中,供其他人应用。同样的在dart中也有相似的概念叫做packages。packages就是能够用来共享的软件包,能够蕴含libraries和tools。 你能够在pub.dev网站中查到dart中所有的共享packages的信息。 那么怎么在一个dart我的项目中应用这些packages呢? pubspec.yaml简略点讲,一个dart的package就是蕴含pubspec.yaml的目录。pubspec.yaml是一个形容文件,用来表明该package的元信息,包含以后package的名字,版本号和依赖信息等。 要想应用pub.dev上的packages,只须要在pubspec.yaml引入对应的依赖即可。 咱们举个例子: name: app2description: a demo appversion: 1.0.0+1environment: sdk: ">=2.7.0 <3.0.0"dependencies: image_picker: ^0.6.7+22 video_player: ^0.10.12+5这里咱们的引入了两个依赖包,别离是image_picker和video_player。 get packages当咱们批改了pubspec.yaml之后,其实对应的package并没有下载到本地来,还须要通过上面的命令来下载对应的packages: cd <path-to-my_app> dart pub getdart pub get会依据pubspec.yaml中配置的内容下载对应的包,并搁置在零碎缓存中。 在Mac或者Linux零碎中,这个缓存目录的地址是:~/.pub-cache,在windows中这个目录地址是:%LOCALAPPDATA%\Pub\Cache。 当然,你也能够通过设置PUB_CACHE来更换这个地址。如果你依赖的包又依赖其余的包的话,其余依赖包也会被下载下来。 当下载完依赖包之后,dart会在 .dart_tool/目录中创立一个 package_config.json文件,用来示意以后我的项目和零碎缓存包的映射关系。 # 应用packages 万事俱备,只欠东风。当初包也有了,剩下就是应用了。 应用libary能够用关键字import。如果是dart SDK中的包,则以dart:结尾: import 'dart:html';如果是第三方包,则以package: 结尾: import 'package:test/test.dart';引入的libary还能够被重命名: import 'package:lib1/lib1.dart';import 'package:lib2/lib2.dart' as lib2;// Uses Element from lib1.Element element1 = Element();// Uses Element from lib2.lib2.Element element2 = lib2.Element();还能够应用show和hide引入局部library: // Import only foo.import 'package:lib1/lib1.dart' show foo;// Import all names EXCEPT foo.import 'package:lib2/lib2.dart' hide foo;默认状况下,引入的包是初始加载的,如果某些包特地大,或者你想要在应用的时候再进行加载,则能够应用deferred关键字进行延时加载: ...

November 18, 2021 · 1 min · jiezi

关于dart:dart系列之dart类中的泛型

简介相熟JAVA的敌人可能晓得,JAVA在8中引入了泛型的概念。什么是泛型呢?泛型就是一种通用的类型格局,个别用在汇合中,用来指定该汇合中应该存储的对象格局。 有了泛型能够简化咱们的编程,并且能够缩小谬误的产生,十分的不便。 dart语言中也有泛型。一起来看看吧。 为什么要用泛型应用泛型的次要目标是保障类型平安,比方咱们有一个List,而后只心愿List中保留String类型,那么在dart中能够这样指定: var stringList = <String>[];stringList.addAll(['jack ma', 'tony ma']);stringList.add(18); // 报错那么在应用的过程中,只能向stringList中增加字符串,如果向其增加数字,则会报错,从而保障List中类型的一致性。 奇妙的应用泛型还可能缩小咱们的代码量,因为泛型能够代表一类通用的类型。 比方,在学校中,咱们有寝室,寝室是有男女之分的,那么对应男生来说有这样的定义: abstract class BoyRoom { Boy getByName(String name);}对于女生来说有这样的定义: abstract class GirlRoom{ Girl getByname(String name);}事实上,两者实质上没太大区别,只是参数或者返回值的类型产生了变动,那么咱们能够这样写: abstract class Room<T>{ T getByname(String name);}从而简化了代码的应用。 怎么应用泛型泛型个别应用大写的单个字符来示意,通常来说是E, T, S, K 和 V等。 泛型最常见的应用中央是汇合中,比方List, set 和 map中: var listExample = <String>['jack ma', 'tony ma'];var setExamples = <String>{'jack ma', 'tony ma'};var mapExamples = <String, String>{ 'name1': 'jack ma', 'name2': 'tony ma',};泛型还能够用在这些汇合类的构造函数中,如下: var stringMap = Map<String, String>();示意结构进去的汇合中,应该蕴含对应的类型。 ...

November 17, 2021 · 1 min · jiezi

关于dart:dart系列之dart类的扩展

简介尽管dart中的类只能有一个父类,也就是单继承的,然而dart提供了mixin语法来绕过这样限度。 明天,和大家一起来探讨一下dart类中的继承。 应用extends和JAVA一样,dart中能够定义一个父类,而后应用extends来继承他,失去一个子类,如下所示: class Student{}class Boy extends Student{}在子类中,能够应用super关键词来调用父类的办法。 抽象类和接口dart中除了继承一般类之外,还能够继承抽象类和实现接口。 抽象类是以abstract关键词润饰的class,如下所示: abstract class Student{ String getName();}抽象类中通常会有形象办法,形象办法须要在子类中进行实现。 当然抽象类中也能够有具体实现的办法,然而抽象类不能够被实例化,如果你想在抽象类中实例化对象,这能够应用之前咱们提到的工厂构造函数。 和java不同的是,dart中并没有interface,他引入的是一个Implicit interfaces的概念。 对应每个对象来说,都隐式定义了一个蕴含类中所有办法和属性的接口。 一般来说,如果一个对象蕴含另外一个对象的构造和办法,然而他们之间的内容又是不一样的,则能够应用implements来隐式实现接口,如下所示: class Student{ String name; String get _name => name;}class Girl implements Student{ @override String name; @override String get _name => "girls";}在dart中一个类能够implements多个接口。 下面的例子中,咱们用到了@override注解,他示意子类对父类办法或者属性的重写。 在应用@override中,咱们须要留神的是,子类对父类的实现会有上面几个限度: 子类的实现办法的返回值,必须和父类返回值雷同,或者是父类返回值的子类。子类的实现办法的参数,必须和父类办法参数雷同,或者是父类参数的父类。子类办法的参数必须和父类的参数个数雷同。mixins尽管dart不反对多重继承,然而能够应用mixin来实现相似多重继承的性能。 要应用mixins,能够应用关键字with,如下所示: class Boy extends Student with Person { // ··· name='boy'; myName();}在dart中mixin是一个非凡的类,应用关键词mixin来形容,mixin的类中,没有构造函数,如下所示: mixin Person { String name=''; void myName() { print('my name is:'+name); }}在mixin中能够定义有用的办法和属性,继承mixin的类能够重写对应的属性和办法,从而达到自定义的性能。 在mixin中咱们也能够指定特定的类,也就是说只有特定的类才可能应用mixin,则能够应用关键词on,如下所示: mixin Person on Boy{ String name=''; void myName() { print('my name is:'+name); }}总结以上就是dart中继承的应用,dart中还能够继承办法,这是dart的高级利用,咱们会在后续的文章中进行介绍,敬请期待。 ...

November 16, 2021 · 1 min · jiezi

关于dart:dart系列之dart类中的构造函数

简介dart作为一种面向对象的语言,class是必不可少的。dart中所有的class,除了Null都继承自Object class。 要想应用dart中的类就要结构类的实例,在dart中,一个类的构造函数有两种形式,一起来看看吧。 传统的构造函数和JAVA一样,dart中能够应用和class名称雷同的函数作为其构造函数,这也是很多编程语言中首先的构造函数的创立形式,咱们以Student类为例,来看看dart中的构造函数是怎么样的: class Student { int age = 0; int id = 0; Point(int age, int id) { this.age = age; this.id = id; }}下面的this示意的是以后类的实例,对dart来说,this是能够疏忽的,然而在下面的例子中,因为类变量的名字和构造函数传入参数的名字是一样的,所以须要加上this来进行辨别。 下面的代码尽管很简略,然而写起来还是有太多的内容,上面是dart中的一种简写形式: class Student { int age = 0; int id = 0; Student(this.age, this.id);}当然,你也能够不指定构造函数,这样的话dart会为你创立一个默认的无参的构造函数。 命名构造函数dart和其余语言不同的中央是,还能够应用命名构造函数。命名构造函数的格局是ClassName.identifier,如下所示: class Student { int age = 0; int id = 0; Student(this.age, this.id); Student.fromJson(Map data) { print('in Student'); }}下面的Student.fromJson就是一个命名构造函数。能够应用该构造函数从Map中生成一个Student对象,有点像是java中的工厂办法。 构造函数的执行程序咱们晓得,dart中的类是能够继承的,那么对于dart中的子类来说,其构造函数的执行程序是怎么样的呢? 如果不给dart类指定构造函数,那么dart会为类主动生成一个无参的构造函数,如果这个类是子类的话,则会主动调用父类的无参构造函数。 那么对应子类的构造函数来说,初始化的时候有三步: 调用初始化列表调用父类的构造函数调用本人的构造函数在步骤2中,如果父类没有默认的无参构造函数,则须要手动指定具体父类的构造函数。怎么调用呢?能够间接在子类的构造函数前面应用:操作符接父类的构造函数,如下所示: class Student { String? firstName; Student.fromJson(Map data) { print('in Student'); }}class Jone extends Student { Jone.fromJson(Map data) : super.fromJson(data) { print('in Jone'); }}了解了父类的构造函数之后,咱们再看一下什么是初始化列表呢? ...

November 14, 2021 · 1 min · jiezi

关于dart:dart系列之dart语言中的异常

简介Exception是程序中的异常情况,在JAVA中exception有checked Exception和unchecked Exception。那么在dart中的状况是不是一样的呢?一起来看看吧。 Exception和ErrorDart中示意异样的类有两个,别离是Exception和Error。他们两个有什么区别呢? Exception是由VM或者dart code中抛出的。 Exception次要用来示意用户程序编写过程中产生的异样,是能够定位到的能够解决的异样。通常来说Exception中蕴含了足够的信息来不便用户来定位异样点。 所以Exception通常是须要被catch的。然而和java不同的是,dart中所有的异样都是unchecked 异样,也就是说dart中的异样并不强制要求被捕捉,是否捕捉异样是由程序员自行决定的。 结构一个异样很简略,如下所示: Exception("message")然而dart并不举荐这样应用,因为这样结构的异样太过通用了,即便捕捉到这样的异样,能够取得信息也比拟少。所以dart举荐抛出自定义异样,也就是说依据业务须要去创立Exception对应的类,而后依据业务须要进行抛出。 dart中也有很多Exception的子类,比方FormatException来示意各种不同的异样情景。 同样的,在JAVA中也是这样举荐的,不要间接抛出Exception,而是依据业务须要抛出自定义的异样。 和JAVA一样,dart中的Error示意的是一个重大的谬误,Error是应该在程序编写过程中须要防止的。 dart中的Error并不需要被捕捉,因为产生了Error就示意程序呈现了十分重大的谬误,曾经无奈运行上来了。 所以Error是咱们在程序编写过程中须要防止的。 Throw和catch如果程序产生了异样,则能够应用Throw语句将其抛出,而后在适合的中央应用catch进行捕捉。 比方咱们throw一个格局异样: throw FormatException('这是一个格局异样');然而在dart中,不仅仅能够throw Exception或者Error,任何一个Object都能够throw进来,如下所示: throw "这是一个异样!";抛出的异样能够应用catch来捕捉: try{ do something}catch(e){}dart也能够捕捉特定的异样,这种状况用on语句来示意,如下: try { someException();} on OutOfIndexException { // 捕捉特定的异样 doSomething();} on Exception catch (e) { // 捕捉其余的Exception print('其余的异样: $e');} catch (e) { // 解决剩下的异样 print('剩下的异样: $e');}dart中的catch能够指定两个参数,第一个参数就是throw的异样,第二个参数是StackTrace对象: try {} catch (e, s) { print('异样信息: $e'); print('堆栈信息: $s');}在解决完异样之后,如果想要再将其抛出,能够应用rethrow: void doSomething(){ try{ }catch (e) { print('get exception'); rethrow; // rethrow这个异样 }}Finally和JAVA一样,dart中也有Finally,用来进行最终的解决。Finally会在所有的catch语句执行结束之后执行: ...

November 13, 2021 · 1 min · jiezi

关于dart:dart系列之dart语言中的特殊操作符

简介有运算就有操作符,dart中除了一般的算术运算的操作符之外,还有自定义的十分非凡的操作符,明天带大家一起来摸索一下dart中的非凡操作符。 一般操作符一般操作符就很好解释了,就是加减乘除,逻辑运算符,比拟运算符和位运算符等。 这些操作符和其余语言的操作符没什么差异,这里就不具体介绍了。大家看几个一般操作符的例子: a++a + ba = ba == bc ? a : bassert(2 == 2);assert(2 != 3);assert(3 > 2);assert(2 < 3);类型测试操作符dart中的类型测试符相似JAVA中的instance of操作,次要有三个,别离是as,is和is! 其中is是类型判断操作符,而as是类型转换操作符,也就是常说的强制转换。 对上面的语句来说,如果obj是T的子类或者实现了T的接口,那么就会返回true。 obj is T 而上面的语句则会始终返回true: obj is Object?dart中的as操作符示意的是类型转换,转换类型之后就能够应用对应类型中的办法了。如下所示: (student as Student).firstName = 'Bob';那么问题来了,下面的写法和上面的写法有什么区别吗? if (student is Person) { // Type check student.firstName = 'Bob';}第一种写法中,如果student是空,或者不是Student的实例,则会报错,而第二种并不会。 条件运算符dart中也反对条件运算符,最常见的就是三元运算符: condition ? expr1 : expr2示意如果condition是true,则返回expr1, 否则返回expr2。 咱们在日常的工作中,常常会有一些判空操作,dart为咱们提供了十分简便的判空操作符: expr1 ?? expr2上式示意如果expr1为空,则抉择expr2。举个例子: String playerName(String? name) => name ?? 'Guest';级联符号级联符号是 .. 或者?.. , 用来在同一对象上进行序列操作,级联操作能够让咱们少写很多代码,能够在创立一个对象的同时,给对象赋值: ...

November 12, 2021 · 1 min · jiezi

关于dart:dart系列之dart语言中的变量

简介flutter是google在2015年dart开发者峰会上推出的一种开源的挪动UI构建框架,应用flutter能够十分不便的编译成运行在原始android,ios,web等挪动平台上的挪动利用。 flutter是应用dart来编写的,最新的flutter版本是2.5.3,而最新的Dart语言版本是2.14。 本系列将会深刻谈谈dart语言的用法和最佳实际,心愿大家可能喜爱。 dart中的变量Dart语言汲取了java和javascript的精髓,如果你是上述语言的开发者,那么会很容易迁徙到dart语言上。咱们先从一个语言最根本的变量开始,探讨dart语言的神秘。 定义变量Dart中定义变量和java,javascript中定义变量是统一的,如下所示: var name = 'jack';下面咱们应用var示意name的类型是能够通过推断失去。在程序编写过程中,如果咱们遇到某些变量并不知道其类型的时候,能够尝试应用var,让dart自行推断。 当然,咱们也能够指定变量的类型,如上所示,能够指定name为String类型: String name = 'jack';在dart中,所有的变量都是Object,而每个对象都是一个Class的实例。包含数字、函数、null都是Object。所有的对象都继承自Object类。 所以下面的赋值也能够这样写: Object name = 'jack';变量的默认值在dart中,未初始化的变量都有一个nullable类型的值,这个值的初始值是null。 和java中不一样的是,dart中所有的number的初始值也是null。这是因为dart中的number也是一个对象。 如果一个变量能够为null,那么在赋值的时候能够在变量类型前面加上?, 如下所示: int? age;对于类变量来说,只会在应用的时候进行初始化,这种初始化策略叫做延时初始化。 Late变量Late修饰符是在Dart 2.12引入的新个性。他能够示意变量须要被延时加载,或者示意一个不为空的变量会在后续被初始化。 咱们能够这样应用: late int age;void main() { age = 18; print(age);}为什么用late呢?因为有时候Dart无奈查看某些变量在应用之前是否被初始化了,然而如果你十分确定的话,应用late来润饰它即可。 另外,late润饰的变量只有在应用的时候才会被初始化,所以咱们能够应用late来定义一些耗时、耗资源的操作。 常量如果变量是不会变动的,那么这就不是变量了,而是常量。 常量能够用final或者const来润饰,final变量示意变量只会被赋值一次。 而const变量示意变量会在编译的时候被赋值,默认const也是final的。 如下所示: final age = 18; final int age = 18;const age = 20; 如果const变量是class变量,那么将其设置为static。 constant还能够用来赋值,如下所示: var age = const [];final bar = const [];const baz = []; // Equivalent to `const []`下面的代码中,尽管age的值是const的,然而age自身并不是const,所以age是能够从新被赋值的: ...

November 8, 2021 · 1 min · jiezi

关于dart:一个-JSer-的-Dart-学习日志三类

本文是“一个 JSer 的 Dart 学习日志”系列的第三篇,本系列文章次要以开掘 JS 与 Dart 异同点的形式,在温习和坚固 JS 的同时安稳地过渡到 Dart 语言。鉴于作者尚属 Dart 初学者,所以意识可能会比拟浮浅和全面,如您慧眼识虫,心愿不吝指正。如无非凡阐明,本文中 JS 蕴含了自 ES5 至 ES2021 的全副个性, Dart 版本则为 2.0 以上版本。 在 ES6 问世之前,宽泛风行的 JS 面向对象编程是应用原型链而非应用类,开发者须要对相干个性有足够的理解,并遵循一些默认的规定,能力勉强模拟出一个大抵可用的“类”。即使是 ES6 引入了 class 关键字来补救,作为新一代 JS 基础设施的类还是有待欠缺。相比之下,Dart 对类的反对就要欠缺和弱小得多。 一. 类似的整体构造两种语言中,用于定义类的语法结构高度类似,次要包含class关键字、类名、包裹在花括号{}外部的成员。 > /* Both JS and Dart */> class ClassName {> attrA;> attrB = 1;>> methodA(a, b){> // do something> this.attrA = a;> this.attrB = b;> }> }二. 构造函数相同之处构造函数在实例化类的时候调用,用于解决实例化参数、初始化实例属性等;应用 super 拜访超类的构造函数;没有超类,或超类的构造函数没有参数的时候,构造函数能够省略,省略构造函数的子类实例化的时候会隐式地调用超类的构造函数。不同之处1. constructor vs SameNameJS 中的构造函数为 constructor;Dart 中的构造函数为与类名统一的函数。 ...

September 28, 2021 · 4 min · jiezi

关于flutter:Flutter中关于使用异步时获得FutureT中数据的问题

应用异步取得Future<T>在flutter我的项目中应用async await异步获取数据时,会返回一个Future<T>类型的数据,然而将其应用在widget中,会有一个问题:在statefulWidget中应用变量接管值,会呈现无奈赋值的状况 ///index.dart class Index extends StatefulWidget { Index({Key key}) : super(key: key); @override _IndexState createState() => _IndexState();} class _IndexState extends State<Index> { List<Widget> _temps; String _studentId = "abcd"; String _token = "lkjncasfmalknfadwSadwawf1dawd"; @override void initState() { super.initState(); _temps = _getClassItems(getOneDaylessons(_dates, _studentId, _token)); } var _dates = initDate(); void _getDates(value) { _dates = DateFormat('EEE', "en_US").format(value).toString(); setState(() { _temps = _getClassItems(getOneDaylessons(_dates, _studentId, _token)); }); } List<Widget> _getClassItems(List<Widget> classItems) { if (classItems.isEmpty) { return [ Container( height: 60, child: Center( child: Text("今日暂无课程"), ), ) ]; } else { return classItems; } } @override Widget build(BuildContext context) { return Scaffold( body: Container( child: Column( children: _temps,//报错处 ), ) ) } ///getOneDaylessons.dart List<Widget> getOneDaylessons(String date, String studentId, String token) { ClassResponseData _data; getTimeTableFromStudentID(studentId, token) .then((v) => {_data=v}); List<ClassItemViewModel> datas = getADayClasses(data); List temp = datas .map((e) { if (e.date == date) { return ClassItem(data: e); } }) .toList() .where((element) => element != null) .toList(); return temp;}//运行我的项目报错_temps是null解决方案将getOneDaylessons同样应用async包装,返回Future<T>到index.dart.在index.dart中,应用FutureBuilder,取值 ...

April 8, 2021 · 2 min · jiezi

关于dart:Dart-212-现已发布

作者 / Michael Thomsen Dart 2.12 现已公布,其中蕴含 健全的空平安 和 Dart FFI 的稳定版。空平安是咱们最新主打的一项生产力强化性能,意在帮忙您躲避空值谬误,以前这种谬误通常很难被发现,您能够观看上面这支视频理解详情。FFI 则是一种互操作机制,反对调用以 C 语言编写的既有代码,例如调用 Windows Win32 API。欢送大家即刻开始应用 Dart 2.12。 https://www.bilibili.com/vide... Dart 平台的独特性能在具体理解健全空平安和 FFI 之前,咱们先来讨论一下它们在哪些方面符合了咱们对 Dart 平台的冀望。编程语言往往有很多相似的性能,例如,很多语言都反对面向对象的编程或在 web 上运行。真正将各个语言辨别开来的,是其独特的性能组合。 Dart 具备横跨三个维度的独特性能组合: 可移植性: 高效的编译器可针对设施生成 x86 和 ARM 机器代码,并针对 web 生成优化的 JavaScript。同时兼容挪动设施、桌面 PC、利用后端等多种 指标平台。大量的开发库和 package 提供了可在所有平台上应用的统一的 API,进一步升高了开发者创立真正多平台利用的老本。高生产力: Dart 平台反对热重载,因而可在原生设施和 web 上实现疾速迭代开发。此外,Dart 还提供了丰盛的构造,如 isolates 和 async/await 等,用以解决和实现常见的并发和事件驱动的利用模式。持重: Dart 的健全空平安类型零碎能够在开发过程中就捕捉到谬误。整个平台领有极好的可扩展性和可靠性,曾经被大量且多样的利用在累计超过十年的生产环境中实战测验过,其中包含 Google 的一些要害业务利用,如 Google Ads 和 Google Assistant 等。健全空平安加强了类型零碎的稳健性,同时进步了性能。借助 Dart FFI,您能够取得更强的可移植性,同时沿用由 C 语言编写的既有代码,在解决对性能要求极为严苛的工作时,能够纵情应用通过精心优化的 C 语言代码。 ...

March 12, 2021 · 3 min · jiezi

关于dart:Flutter-24FlutterUI布局和WidgetStatelesswidget与Statefulwidget

Statelesswidget如果一个Widget从初始化到应用再到销毁,整个过程中都不须要批改其UI的款式,例如纯展现页面,咱们就用Statelesswidget。常见的Statelesswidget有:Text、Icon、ImageIcon、Dialog等。能够看到这些往往都是一些展现类的,不须要扭转其状态的控件。应用Statelesswidget更轻量,更节俭内存资源。初始化Statelesswidget的时候不会附带一些动静更新UI的办法,这样也会晋升咱们软件的性能。 须要留神的是:在iOS开发中,初始化一个Label并命名为la,扭转它的文字内容,会调用la.text = @"new text",咱们能够了解为Label不是Statelesswidget的,因为它的text属性被扭转了。那Flutter的Text为什么又是Statelesswidget的呢?因为Flutter中所有Widget都是 “配置文件”,当咱们批改文本之后,Flutter会帮忙咱们从新初始化一个Text,而不是批改以后的Text对象,这是与原生开发不一样的中央。StatefulwidgetStatefulwidget是可变的Widget,在咱们的开发中会大量应用Statefulwidget。它实现了一个setState办法,当咱们调用这个办法的时候,该Statefulwidget会被从新渲染,留神是从新被渲染,而不是部分更新。当咱们调用setState时,Flutter在收到该音讯后,会从新调用其build办法从新构建这个widget,从而达到更新UI的目标。 来看如下代码: class StatefulWidgetDemoPage extends StatefulWidget { @override _StatefulWidgetDemoPageState createState() => _StatefulWidgetDemoPageState();}class _StatefulWidgetDemoPageState extends State<StatefulWidgetDemoPage> { @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { setState(() {}); }, child: Icon(Icons.add), ), appBar: AppBar( title: Text("StatefuleWidget Demo"), centerTitle: true, backgroundColor: Colors.blue, ), body: Column( children: [ Container( width: 100, height: 100, margin: EdgeInsets.all(10), /// 色彩一个随机值 color: _randomColor(), ), ], ), ); } /// 获取一个随机的色彩值 _randomColor() { return Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255), Random().nextInt(255)); }}咱们定义了一个生成随机色彩的办法_randomColor(),它会返回一个Color对象,而后咱们又定义了一个Container,Container的初始化参数color的值是_randomColor()的返回值。而后咱们在FloatingActionButton的onPressed的办法中调用一下setState办法,这个时候Flutter会从新绘制StatefulWidgetDemoPage,所以每次点击按钮,咱们能够看到Container的色彩都是不一样的。 ...

January 23, 2021 · 1 min · jiezi

关于dart:Flutter-117Flutter手把手教程UI控件多图预警按钮详解

作者 | 弗拉德起源 | 弗拉德(公众号:fulade_me) Material 格调中罕用的按钮有三种RaiseButton、FlatButton、OutlineButton。这三种按钮都是继承了MaterialButton,而MaterialButton又继承自StatelessWidget。 RaiseButton:带有暗影成果的按钮,默认带有灰色背景,点击上来会有点击成果和暗影。FlatButton: 扁平格调按钮,点击上来会有背景色彩。OutlineButton: 带有边框的按钮,且边框会在点击时扭转色彩。 1. RaisedButton咱们先来看RaisedButton的构造方法 const RaisedButton({ Key key, /// 点击后的回调办法 @required VoidCallback onPressed, /// 长按后的回调办法 VoidCallback onLongPress, /// 高亮时候的回调办法 ValueChanged<bool> onHighlightChanged, /// 鼠标指针的光标进入或悬停在此按钮(这个用于Web端或PC端) MouseCursor mouseCursor, /// 文本的主题,这个跟MaterialApp的属性theme无关 ButtonTextTheme textTheme, /// 文本色彩 Color textColor, /// 不可点击时的文本色彩 Color disabledTextColor, /// 背景色彩 Color color, /// 可点击时的背景色彩 Color disabledColor, /// 获取焦点时的色彩(用于Web端或PC端) Color focusColor, /// 指鼠标悬停时的色彩(用于Web端或PC端) Color hoverColor, /// 高亮时的色彩 Color highlightColor, /// 水波纹色彩,按下松开会有水波纹成果 Color splashColor, /// 按钮主题色彩,默认浅色 Brightness colorBrightness, /// 默认时的 暗影大小 double elevation, /// 选中时的 暗影大小 double focusElevation, /// 指鼠标悬停时的暗影大小 double hoverElevation, /// 高亮时的暗影大小 double highlightElevation, /// 不可选中时的暗影大小 double disabledElevation, /// 内边距 跟布局无关 EdgeInsetsGeometry padding, VisualDensity visualDensity, /// 设置按钮的形态 ShapeBorder shape, /// 切边的款式 Clip clipBehavior = Clip.none, FocusNode focusNode, bool autofocus = false, MaterialTapTargetSize materialTapTargetSize, /// 动画的工夫 Duration animationDuration, /// 子控件 Widget child, }) 1.1 一个最简略的RaisedButton ...

December 20, 2020 · 3 min · jiezi

关于dart:Flutter-115Flutter手把手教程Dart语言包管理工具Pub详解pub-getpub-cache使用

作者 | 弗拉德起源 | 弗拉德(公众号:fulade_me) 什么是Pub工具Dart 生态系统应用包来治理共享软件,比方:库和工具。咱们应用Pub包管理工具 来获取Dart包。在Pub上,能够找到公开可用的包。或者从本地文件系统或其余的地位,比方Git仓库,加载可用的包。无论包是从什么路径加载的, Pub 都会进行版本依赖治理,从而帮忙咱们取得版本兼容的软件包以及SDK。pub工具蕴含治理 Package 、部署 Package 和部署命令行利用的命令。Dart 包目录中至多蕴含一个pubspec文件。 pubspec 文件记录一些对于我的项目的依赖数据。此外还有一些其余数据比方:Dart 库,利用,资源,测试,图片,以及示例。 上面是一个 pubspec 的示例,示例中申明依赖了在 Pub 站点上托管的两个包( js 和 intl ): name: my_appdependencies: js: ^0.6.0 intl: ^0.15.8pub get在我的项目中配置了pubspec文件后,就能够在我的项目根目录中执行pub get命令: cd <path-to-my_app> pub getpub get命令确定以后利用所依赖的包,并将它们保留到地方零碎缓存(central system cache)中。如果以后利用依赖了一个公开包,Pub会从Pub站点 该包。对于一个Git依赖,Pub会Clone该Git仓库。同样包含包的相干依赖也会被下载。例如,如果 js 包依赖 test 包, pub 会同时获取js包和test包。 Pub 会创立一个.packages 文件(位于应用程序的根路目录下),该文件将应用程序所依赖的每个包名相应的映射到零碎缓存中的包。 pub upgrade第一次获取依赖时,Pub 会下载依赖及其兼容的最新版本。而后通过创立lockfile 锁定依赖,以始终应用这个版本。 Pub会在pubspec旁创立并存储一个名为pubspec.lock文件。它列出了应用的每个依赖包的指定版本(以后包或传递包的版本)。在开发我的项目中的每个人都可能应用所有雷同版本的包。同样退出到 lockfile 能够保障部署的利用应用的是同一版本的代码。 如果曾经筹备更新依赖到最新版本,应用命令 pub upgrade : pub upgrade下面的命令用于从新生成 lockfile 文件,并应用最新可用版本的依赖包。如果仅降级某个依赖,能够在命令中指定须要降级的包: ...

December 15, 2020 · 2 min · jiezi

关于dart:Flutter-114Flutter手把手教程Dart语言Dart语言引用importpackage使用

作者 | 弗拉德起源 | 弗拉德(公众号:fulade_me) 库import 关键字能够帮忙你创立一个模块化和可共享的代码库,代码库不仅只是提供 API 而且还起到了封装的作用:以下划线(_)结尾的成员仅在代码库中可见。 应用库应用import来指定命名空间以便其它库能够拜访。比方你能够导入代码库 dart:html来应用Dart Web中相干 API: import 'dart:html';import的惟一参数是用于指定代码库的URI,对于Dart内置的库,应用 dart:xxxxxx的模式。而对于其它的库,你能够应用一个文件系统门路或者以 package:xxxxxx 的模式。package:xxxxxx 指定的库通过包管理器(比方 pub 工具)来提供: import 'package:test/test.dart';指定库前缀如果你导入的两个代码库有抵触的标识符,你能够为其中一个指定前缀。比方如果 library1和library2 都有Element 类,那么能够这么解决: import 'package:lib1/lib1.dart';import 'package:lib2/lib2.dart' as lib2;// 应用 lib1 的 Element 类。Element element1 = Element();// 应用 lib2 的 Element 类。lib2.Element element2 = lib2.Element();导入库的一部分如果你只想应用代码库中的一部分,你能够有选择地导入代码库。例如: // 只导入 lib1 中的 foo。(Import only foo).import 'package:lib1/lib1.dart' show foo;// 导入 lib2 中除了 foo 外的所有。import 'package:lib2/lib2.dart' hide foo;提早加载库提早加载(也常称为懒加载)容许利用在须要时再去加载代码库,上面是可能应用到提早加载的场景: 为了缩小利用的初始化工夫。解决 A/B 测试,比方测试各种算法的不同实现。加载很少会应用到的性能,比方可选的屏幕和对话框。应用deferred as关键字来标识须要延时加载的代码库: ...

December 12, 2020 · 1 min · jiezi

关于dart:Flutter-113Flutter手把手教程Dart语言异步FutureStreamasyncawait详解

作者 | 弗拉德起源 | 弗拉德(公众号:fulade_me) 异步Dart 代码库中有大量返回Future或Stream对象的函数,这些函数都是异步的,它们会在耗时操作执行结束前间接返回而不会期待耗时操作执行结束。async和await关键字用于实现异步编程,并且让你的代码看起来就像是同步的一样。 Future能够通过上面两种形式,取得Future执行实现的后果: 应用async和await;应用Future API;应用async和await的代码是异步的,然而看起来有点像同步代码。例如,上面的代码应用await期待异步函数的执行后果。 await lookUpVersion();必须在带有async关键字的异步函数中应用 await: Future checkVersion() async { var version = await lookUpVersion(); // 应用 version 持续解决逻辑}只管异步函数能够解决耗时操作,然而它并不会期待这些耗时操作实现,异步函数执行时会在其遇到第一个 await表达式的时候返回一个Future对象,而后期待await表达式执行结束后继续执行。 应用try、catch以及finally来解决应用await导致的异样: try { version = await lookUpVersion();} catch (e) { // 无奈找到版本时做出的反馈}你能够在异步函数中屡次应用await关键字。例如,上面代码中期待了三次函数后果: var entrypoint = await findEntrypoint();var exitCode = await runExecutable(entrypoint, args);await flushThenExit(exitCode);await表达式的返回值通常是一个Future对象;如果不是的话也会主动将其包裹在一个Future对象里。Future对象代表一个"承诺",await表达式会阻塞直到须要的对象返回。 如果在应用await时导致编译谬误,请确保await在一个异步函数中应用。例如,如果想在main()函数中应用await,那么main()函数就必须应用async关键字标识。 Future main() async { checkVersion(); print('在 Main 函数中执行:版本是 ${await lookUpVersion()}');}申明异步函数定义异步函数只需在一般办法上加上async关键字即可。将关键字async增加到函数并让其返回一个Future 对象。假如有如下返回String对象的办法: String lookUpVersion() => '1.0.0';将其改为异步函数,返回值是Future: ...

December 12, 2020 · 1 min · jiezi

关于dart:Flutter-112Flutter手把手教程Dart语言什么是泛型和泛型的使用场景

作者 | 弗拉德 起源 | 弗拉德(公众号:fulade_me) 泛型如果你查看数组的API文档,你会发现数组List的理论类型为List<E>。<> 符号示意数组是一个泛型(或参数化类型)通常应用一个字母来代表类型参数,比方E、T、S、K 和 V 等等。 为什么应用泛型?泛型罕用于须要要求类型平安的状况,然而它对代码运行也有益处: 适当地指定泛型能够更好地帮忙代码生成。应用泛型能够缩小代码反复。比方你想申明一个只能蕴含String类型的数组,你能够将该数组申明为List<String>,这示意只能蕴含字符串类型的数组。这样的话就能够很容易防止因为在该数组放入非String类变量而导致的诸多问题,同时编译器以及其余浏览代码的人都能够很容易地发现并定位问题: var names = List<String>();names.addAll(['Seth', 'Kathy', 'Lars']);names.add(42); // 这样写就会报错另一个应用泛型的起因是能够缩小反复代码。泛型能够让你在多个不同类型实现之间共享同一个接口申明,比方上面的例子中申明了一个类用于缓存对象的接口: /// 定义一个 抽象类abstract class ObjectCache { Object getByKey(String key); void setByKey(String key, Object value);}不久后你可能又会想专门为String类对象做一个缓存,于是又有了专门为String做缓存的类: /// 另外一个抽象类abstract class StringCache { String getByKey(String key); void setByKey(String key, String value);}如果过段时间你又想为数字类型也创立一个类,那么就会有很多诸如此类的代码。这时候能够思考应用泛型来申明一个类,让不同类型的缓存实现该类做出不同的具体实现即可: abstract class Cache<T> { T getByKey(String key); void setByKey(String key, T value);}在上述代码中,T是一个代替类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。 应用汇合字面量List、Set以及Map字面量也能够是参数化的。定义参数化的List只需在中括号前增加<type>;定义参数化的Map只须要在大括号前增加 <keyType, valueType>: var names = <String>['小芸', '小芳', '小民'];var uniqueNames = <String>{'小芸', '小芳', '小民'};var pages = <String, String>{ 'index.html': '主页', 'robots.txt': '网页机器人提醒', 'humans.txt': '咱们是人类,不是机器'};应用类型参数化的构造函数在调用构造方法时也能够应用泛型,只需在类名后用尖括号<...>将一个或多个类型包裹即可: ...

December 11, 2020 · 1 min · jiezi

关于dart:flutter异步编程事件循环IsolateStream流

事件循环、Isolate开始前咱们须要明确 Dart 是单线程的并且 Flutter 依赖于 Dart 如果你晓得js 中的event loop 将很好了解dart的整个异步过程 先看一段代码 import 'dart:async';Future eventLoop() async{print('A');Future((){print('F');scheduleMicrotask((){print('H');});Future((){print('M');}).then((_){print('N');});}).then((_){print('G');});Future((){print('I');}).then((_){print('J');});scheduleMicrotask(text1);scheduleMicrotask((){print('D');});print('B');}void text1() {print('C');scheduleMicrotask((){print('E');});Future((){print('K');}).then((_){print('L');});}你只到输入后果吗 正确的输入程序是: A B C D E F G H I J K L M NeventLoop1、MicroTask 队列微工作队列,个别应用scheduleMicroTask办法向队列中增加 这是大多数时候你不用应用的货色。比方,在整个 Flutter 源代码中 scheduleMicroTask() 办法仅被援用了 7 次, 所以最好优先思考应用 Event 队列 2、Event 队列I/O、手势、绘图、计时器、流、futures等等异步操作都将进入event队列 尽量应用事件队列能够使微工作队列更短,升高事件队列卡死的可能性 代码执行程序首先咱们晓得dart是单线程的,所以dart的代码执行程序是: 同步代码顺次执行碰到异步代码先进对应的队列中,而后继续执行上面的代码当同步代码执行结束,先去看MicroTask 队列中的工作,将MicroTask队列中的工作顺次执行结束MicroTask中的工作执行结束后,再去看Event 队列中的工作,event队列出一个工作 而后执行 , 而后回到第三步 循环 直到所有队列都清空IsolateIsolate 是 Dart 中的 线程, Flutter的代码都是默认跑在root isolate上的 「Isolate」在 Flutter 中并不共享内存。不同「Isolate」之间通过「音讯」进行通信。import 'dart:async';import 'dart:io';import 'dart:isolate';import 'package:flutter/foundation.dart';import 'package:flutter/material.dart';//一个普普通通的Flutter利用的入口//main函数这里有async关键字,是因为创立的isolate是异步的void main() async{runApp(MyApp());//asyncFibonacci函数里会创立一个isolate,并返回运行后果print(await asyncFibonacci(20));}//这里以计算斐波那契数列为例,返回的值是Future,因为是异步的Future<dynamic> asyncFibonacci(int n) async{//首先创立一个ReceivePort,为什么要创立这个?//因为创立isolate所需的参数,必须要有SendPort,SendPort须要ReceivePort来创立final response = new ReceivePort();//开始创立isolate,Isolate.spawn函数是isolate.dart里的代码,_isolate是咱们本人实现的函数//_isolate是创立isolate必须要的参数。await Isolate.spawn(_isolate,response.sendPort);//获取sendPort来发送数据final sendPort = await response.first as SendPort;//接管音讯的ReceivePortfinal answer = new ReceivePort();//发送数据sendPort.send([n,answer.sendPort]);//取得数据并返回return answer.first;}//创立isolate必须要的参数void _isolate(SendPort initialReplyTo){final port = new ReceivePort();//绑定initialReplyTo.send(port.sendPort);//监听port.listen((message){//获取数据并解析final data = message[0] as int;final send = message[1] as SendPort;//返回后果send.send(syncFibonacci(data));});}int syncFibonacci(int n){return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1);}因为Root isolate会负责渲染,还有UI交互,如果咱们有一个很耗时的操作呢?后面晓得isolate里是一个event loop(事件循环),如果一个很耗时的task始终在运行,那么前面的UI操作都被阻塞了,所以如果咱们有耗时的操作,就应该放在isolate里! ...

September 18, 2020 · 1 min · jiezi

dart字符串基本属性及方法

代码示例// 单行字符串String str = 'abc';//多行字符串String multiLine = '''这是多行文本 能够回车的''';// 字符串连贯形式'Dart ' 'is ' 'fun!'; // 'Dart is fun!''Dart ' + 'is ' + 'fun!'; // 'Dart is fun!''Dart' * 2; // 'DartDart'String属性属性形容List<int> codeUnits获取字符串utf-16编码值的列表int hashCode获取字符派生的哈希代码bool isEmpty字符串是否为空bool isNotEmpty字符串是否不为空int length获取字符串长度Runes runes获取字符串utf-16编码值的可迭代列表Type runtimeType获取运行时的数据类型String办法属性形容int codeUnitAt(int index)返回给定索引值的对应utf-16编码int compareTo(String other)与传入字符串进行比拟,有雷同返回1,否则返回-1bool contains(Pattern other, [int index])查找返回字符串是否有符号要求的,传入index规定从index位开始查找bool endsWith(String other)字符串的结尾是否为传入的值int indexOf(Pattern other, [int start])从字符串后面开始查找返回合乎规定的第一个的索引地位,传入start规定从哪里开始查找int lastIndexOf(Pattern other, [int start])与indexOf雷同,不同的是这个办法是从前面开始查找String padLeft(int width, [String padding])如果字符串没有width的长度,则在后面加上padding字符串并返回,不会扭转原字符串String padRight(int width, [String padding])同padLeft办法雷同,不同的是从padding加在前面String replaceAll(Pattern from, String to)替换所有匹配的子字符串String replaceAllMapped(Pattern from, String replace(match))将匹配到的字符串用函数解决后返回字符串替换String replaceFirst(Pattern from, String to, [int index])替换第一个匹配的子字符串,能够规定从index出开始匹配String replaceFirstMapped(Pattern from, String replace(match), [int index])同replaceAllMapped,此办法只替换第一个匹配的值,能够规定从index处开始匹配String replaceRange(int start, int end, String to)替换范畴类的字符串List<String> split(Pattern pattern)拆分字符串为数组String splitMapJoin(Pattern pattern, { String onMatch(match), String onNonMatch(match) })拆分替换字符串,匹配和不匹配的执行对应函数替换成返回值bool startsWith(Pattern pattern, [int index])是否是匹配的正则或字符串开始,能够规定从index开始查找String substring(int startIndex, [int endIndex])提取字符串中startIndex(蕴含)到endIndex(不蕴含)两个指定的索引号之间的字符。String toLowerCase()把字符串转换为小写String toUpperCase()把字符串转换为大写String trim()去除字符串两边的空白String trimLeft()去除字符串右边的空白String trimRight()去除字符串左边的空白

July 15, 2020 · 1 min · jiezi

Dart面向对象编程一基础篇

6-2类与对象主文件// 引入库(dart文件)import 'calss_and_obj2.dart';void main() {  // 创建一个对象  var getp = new p();// 赋值 写入类中的属性的值  getp.name = 'tom';  getp.age = 20;// 获取值 读类中的属性  print(getp.name);// 读类中的方法  getp.work();  print(getp.address);}引入文件// 声明一个类class p {  // 加下划线代表私有的 只在当前dart文件中可以调用 外链的dart文件不可见  // 属性和方法也可加下划线表示私有// class _p{  String name;  int age;// final定义的 只能读不能写  final String address = 'aaaa';// 声明方法  void work() {    print('name is $name,age=$age,he is working');  }}6-3计算属性// void main(){//   var rect=new Rctangle();//   rect.height=20;//   rect.width=10;//   print(rect.area());// }// class Rctangle{//   num width,height;//   num area(){//     return width * height;//   }// }void main() {  var rect = new Rctangle();  rect.height = 20;  rect.width = 10;  print(rect.area);  rect.area = 200;  print(rect.width);}class Rctangle {  num width, height;  // 计算属性get获取值 也可以set  num get area {    return width * height;  }  set area(value) {    width = value / 20;  }}6-4构造方法void main() {  // var person=new Person('Tom',30,'male');  var person = new Person('Tom', 30);  var person1 = new Person.getn('Tom1');  person.name = 'topm';  person.age = 20;  print(person.name);  print(person.age);  print(person1.name);  // print(person.gender);}class Person {  String name;  int age;  // 构造方法 如果有自定义的构造方法 那么默认的是不要的  Person(String name, int age) {    this.name = name;    this.age = age;  }// final String gender;// 语法糖 这种方法可以 给final变量赋值// Person(this.name,this.age,this.gender);// 有名字的构造方法  Person.getn(this.name);  void work() {    print('working');  }}6-5常量构造方法// 常量构造方法void main() {  const person = const Person('Tom', 30);  print(person.name);}class Person {  final String name;  final int age;  // 构造方法 如果有自定义的构造方法 那么默认的是不要的  const Person(this.name, this.age);// final String gender;// 语法糖 这种方法可以 给final变量赋值// Person(this.name,this.age,this.gender);// 有名字的构造方法  // Person.getn(this.name);  void work() {    print('working');  }}6-6工厂构造方法class Logger {  final String name;  // Map<String, Logger>泛型 来限制key value的类型  static final Map<String, Logger> _cache = <String, Logger>{};  // 工厂构造方法 可以返回对象  factory Logger(String name) {    if (_cache.containsKey(name)) {      return _cache[name];    } else {      final logger = Logger._internal(name);      _cache[name] = logger;      return logger;    }  }  // 构造方法是不能有返回值的  Logger._internal(this.name);  void log(String msg) {    print(msg);  }}6-7初始化列表// 常量构造方法void main() {  var a = {'name': '1', 'age': 2, 'gender': '3'};  var person = new Person.withmap(a);  print(person.name);}class Person {  String name;  int age;  final String gender;  Person(this.name, this.age, this.gender);// 列表构造方法 执行时间 在构造方法前面 可以对 final属性赋值  Person.withmap(Map map) : gender = map['gender'] {    this.name = map["name"];    this.age = map['age'];  }  void work() {    print('working');  }}6-8静态成员1非静态的方法可以访问静态属性2静态方法不能使用实例化对象来访问 只能通过类名加方法名访问3在类中声明常量 static const int age=10; 6-9对象操作符import 'constructors.dart';void main() {// Person person=new Person();// 条件运算符 解决空指针问题// 对象不为空 执行// person?.work();// var person;// person='';// person=new Person();// person.work();// 把对象转换成xx类型 强转// (person as Person).work();// 判断对象是否为Person对象类型// if(person is Person){//   person.work();// }// 判断对象不是Person对象类型// else if(person is! Person){// print('11111');// }  var person = new Person();// person.name='aa';// person.age=30;// 级联操作  person    ..name = 'aa'    ..age = 30;  print(person.name);}class Person {  String name;  int age;  void work() {    print('working');  }}6-10对象call方法void main() {  var person = new Person();// person.name='aa';// person.age=30;//   print(person.name);// name is null// 执行类中的call方法  person('gggggg');}class Person {  String name;  int age;  void work() {    print('working');  }  // 也可以写成有返回值的 名称必须为call  void call(String name) {    print('name is $name');  }}

July 7, 2020 · 1 min · jiezi

dart控制语句和方法

控制语句4-1if语句void main() {  int score = 100;  if (score >= 90) {    if (score == 100) {      print('完美');    } else {      print('优秀');    }  } else if (score > 60) {    print('良好');  } else if (score == 60) {    print('及格');  } else {    print('不及格');  }}4-2for语句void main() {  var list = [1, 2, 3, 4, 5];  // for循环  for (var index = 0; index < list.length; index++) {    print(list[index]);  }  // 不需要使用下标时 可以使用这种方法遍历列表的元素  for (var item in list) {    print(item);  }}4-3while语句void main() {  int c = 0;  // 当c<5时就循环打印c 打完加1  while (c < 5) {    print(c++);  }// 先执行do再进行while循环  do {    print(c--);  } while (c > 0 && c < 5);}4-4break和continuevoid main() {  var list = [1, 2, 3];  for (var i in list) {    // if (i != 2) {    //   print(i);    // }    if (i == 2) {      //跳出当前循环// break;// 打印1,3会跳过当前这种情况继续执行      continue;    }    print(i);  }  print('----------');  var list2 = [4, 5, 6];  for (var a in list2) {    if (a == 5) {      //只会终止自己所在的for循环 外边还有嵌套的for循环 不会终止      break;    }    print(a);  }}4-5switch...case语句void main() {  String lan = 'java';  // switch case 每个case后面要跟一个break;默认是default  switch (lan) {    case 'dart':      print('dart is my fav');      break;    case 'java':      print('java is my fav');      break;    default:      print('none');  }  switch (lan) {    D:    case 'dart':      print('dart is my fav');      break;    case 'java':      print('java is my fav');      // 先执行当前case中的 然后跳转到D中的case继续执行      continue D;    // break;    default:      print('none');  }}方法5-1方法的定义// void是没有返回值的 返回值类型:方法名(参数){return:返回值}//"f:\vscodexm\dartlearn\chapter5\function_declaration.dart" 1 'test' true// 方法也是对象 并且有具体类型的function// 返回值类型  参数类型 都可以省略// 箭头语法简化代码 只适用于一个表达式// 方法都有返回值 没指定 return nullvoid main(List args) {  print(args);  print(geta('张三', 18));  printp('李四', 20);}// String geta(String name,int age){// return 'name=$name,age=$age';// }// =>箭头可以表示返回值 箭头后面可以跟表达式int gender = 2;geta(name, age) => gender == 1 ? 'name=$name,age=$age' : 'test';// 返回值类型 和参数类型可以省略 默认为void// printp(name,age){//   print('name=$name,age=$age');// }void printp(String name, int age) {  print('name=$name,age=$age');}5-2可选参数// void是没有返回值的 返回值类型:方法名(参数){return:返回值}//"f:\vscodexm\dartlearn\chapter5\function_declaration.dart" 1 'test' true// 方法也是对象 并且有具体类型的function// 返回值类型  参数类型 都可以省略// 箭头语法简化代码 只适用于一个表达式// 方法都有返回值 没指定 return nullvoid main(List args) {  print(args);  print(geta('张三', 18));  printp('李四', 20);}// String geta(String name,int age){// return 'name=$name,age=$age';// }// =>箭头可以表示返回值 箭头后面可以跟表达式int gender = 2;geta(name, age) => gender == 1 ? 'name=$name,age=$age' : 'test';// 返回值类型 和参数类型可以省略 默认为void// printp(name,age){//   print('name=$name,age=$age');// }void printp(String name, int age) {  print('name=$name,age=$age');}5-3默认参数值void main() {  // name=xxx,gender=null,age=null调用这个方法后两个参数可不传  printp('xxx');// name=xxx,gender=male,age=20{}根据命名来  printp('xxx', gender: 'male');  printp('xxx', age: 20);}// {int age=30,String gender='female'}设置默认参数printp(String name, {int age = 30, String gender = 'female'}) {  print('name=$name,gender=$gender,age=$age');}### 5-4方法对象void main() {  var func = printhello;  Function func1 = printhello;  func();  func1();// 方法作为一个参数进行传递  var list = [1, 2, 3, 4];  list.forEach(print);  var list2 = ['a', 'b', 'c'];// [aaa, bbb, ccc一个方法 作为其他方法参数进行传递  print(listtimes(list2, times));}// dart中方法也都是对象 可以赋值给其他对象或变量void printhello() {  print('hello');}List listtimes(List list, String times(str)) {  for (var i = 0; i < list.length; i++) {    list[i] = times(list[i]);  }  return list;}String times(str) {  return str * 3;}5-5匿名方法void main() {  // 匿名方法 可以通过赋值给对象或者方法来调用  // (){  //   print('hello');  // };  // 匿名方法也可以传参  var func = (str) {    print('hello----$str');  };  func(30);  // 直接调用匿名方法 不推荐使用  (() {    print('test不推荐');  })();  var list2 = ['a', 'b', 'c'];  var result = listtimes(list2, (str) => str * 3);  print(result);  // listtimes(list2, (str){return str*3;});}List listtimes(List list, String times(str)) {  for (var i = 0; i < list.length; i++) {    list[i] = times(list[i]);  }  return list;}5-6闭包// 闭包是一个方法(对象)// 闭包定义在其他方法内部// 闭包能够访问外部方法中的局部变量,并持有其状态void main() {  var func = a();  func();  func();  func();  func();}a() {  int count = 0;  //printCount是一个闭包 可以持有a中count的状态 也可以使用匿名方法  // printCount(){  //   print(count++);  // }  // return printCount;  // 匿名方法  return () {    print(count++);  };}

July 5, 2020 · 1 min · jiezi

dart数据类型和运算符

数据类型2-1变量与常量使用var生命变量 未初始化默认为nullvoid main() {  // 既可以赋值数字也可以字符串  var a;  // 打印出null 由于声明未赋值  print(a);  a = 10;  // 打印出10  print(a);  // 打印出字符串hello dart  a = 'hello dart';  print(a);  // 既可以声明的时候赋值 也可以声明后赋值 打出20  var b = 20;  print(b);  //  final c = 30;  // final只能赋值一次的变量 不能继续赋值了  // c=50;  print(c);  // const声明常量 必须是编译器常亮  const d = 20;  // 常亮也是不能修改的  // d=50;  print(d);}2-2数值型void main() {  // num是数值型的总称 包括浮点型(带小数点)和整形  num a = 10;  a = 12.5;  print(a);  int b = 20;  // double的值不能赋给int  // b=20.5  print(b);  double c = 10.5;  print(b + c);  print(b - c);  print(b * c);  print(b / c);  // 取整 1 20除以10.5取了整就是1  print(b ~/ c);  // 取余 9.5  print(b % c);  // 非数字 定义在double方法中 0.0/0.0=NAN  print(0.0 / 0.0);  // 打印b是否为偶数true  print(b.isEven);  //  打印b是否为奇数false  print(b.isOdd);  int e = -100;  //  abs()方法取绝对值  print(e.abs());  double f = 10.5;  //  四舍五入11  print(f.round());  //  向下取整10  print(f.floor());  //  向上取整11  print(f.ceil());  //  转化为整形10  print(f.toInt());  //  转化为浮点型10.5  print(f.toDouble());}2-3字符串void main() {  //  String str1 = 'hello';  // 三个引号代表打印多行字符串 或者三个双引号  String str2 = '''hello  dart''';  print(str2);  // \n换行  String str3 = 'hello \n dart';  print(str3);  // 字符串前面加个r可以避免字符被转义 打出hello \n dart  String str4 = r'hello \n dart';  print(str4);  String a1 = 'this is my fav lan';  // + this is my fav lannew  print(a1 + 'new');  // *代表打印5次  print(a1 * 5);  // 比较两字符串是否相同  print(str3 == a1);// 取字符串第几位  print(a1[0]);  int a = 1;  int b = 2;// 插值表达式 ${}可以转义为计算a+b的值 a+b=3  print('a+b=${a + b}');// 如果插值表达式直接取变量值 就可以把大括号省略掉  print('a=$a');  // 取字符串长度  print(a1.length);  // 判断字符串是否为空  print(a1.isEmpty);  // 是否包含某个字符串  print(a1.contains('this'));  // 从0开始截取3位  print(a1.substring(0, 3));  // 是否是以a开头的 false  print(a1.startsWith('a'));  //  是否已a结尾  print(a1.endsWith('a'));  //  第一个i对应的下标  print(a1.indexOf('i'));  // 最后一个i对应的下标  print(a1.lastIndexOf('i'));  // 去前后的空格 left right可以单独去  print(a1.trim());  // 大写  print(a1.toUpperCase());  // 以空格进行分隔 返回数组  var a2 = a1.split(' ');  print(a2);  // 替换  print(a1.replaceAll('this', 'that'));}2-4布尔型void main() {  // 声明方法 只有true或者false  bool a = true;  bool b = false;  print('hello'.isEmpty);}2-5列表void main() {  // list元素可以是不同类型的  var list1 = [1, 2, 3, 'dart', true];  print(list1);  // 取第0个元素  print(list1[0]);  // 更改第1个元素  list1[1] = 'hello';  print(list1);  // 内容不可变的list  var list2 = const [1, 2, 3];  // 报错 不能修改常量数组  // list2[0]=5;  // list的创建  var list3 = new List();  // 打印[]  print(list3);  var a = ['heloo', 'dart'];//  长度  print(a.length);//  最后一位插入元素  a.add('new');  print(a);// 在第二个元素插入字符串  a.insert(1, 'java');  print(a);//  在数组中移除元素  a.remove('java');  print(a);//  清空数组//  a.clear();  print(a);//  是否包含某个元素 包含的话会打出元素下标 不包含打出-1  print(a.indexOf('dart'));  print(a.indexOf('dart1'));//  按字母或者数值进行排序 [dart, heloo, new]  a.sort();  print(a);// 从第二个元素开始截取[heloo, new]  print(a.sublist(1));// 把元素逐个打印 传入的是一个方法  a.forEach(print);}2-6Mapvoid main() {  // 每一组元素都有键和值true:'2'不建议这样做  var map1 = {"first": "dart", 1: true, true: '2'};  print(map1);  // 打印key=first的value值   dart  print(map1['first']);  print(map1[true]);  // 修改map值  map1[1] = false;  print(map1);  var map2 = const {1: 'dart', 2: 'java'};  // 不能给不可变得map修改值  // map2[1]='1';  // 初始化  var map3 = new Map();  var a = {'first': 'dart', 'second': 'java', 'third': 'ph'};  // 长度  print(a.length);  print(a.isEmpty);  print(a.isNotEmpty);  //(first, second, third)  print(a.keys);  // (dart, java, ph)  print(a.values);  // 是否包含某个key  print(a.containsKey('first'));// 是否包含某个value  print(a.containsValue('a'));// 移除key=third的那一项  a.remove('third');  print(a);// 传入一个方法 方法用插值表达式 取$key $value 结果:key=first,value=dart key=second,value=java  a.forEach(f);// list转成map 下标作为key 元素作为值  var list = ['1', '2', '3'];  print(list.asMap());}void f(key, value) {  print('key=$key,value=$value');}2-7dynamicvoid main() {  // a是动态类型 所以可以赋予不同的值  var a;  a = 10;  a = 'dart';  // 声明动态类型 new List<dynamic>();创建一个泛型 list中的类型随意  dynamic b = 20;  b = 'java';  var list = new List<dynamic>();  list.add(1);  list.add('hello');  list.add(true);  print(list);}运算符3-1算数运算符void main() {  int a = 10;  int b = 20;  print(a + b);  print(a - b);  print(a * b);  print(a / b);  print(a ~/ b);  print(a % b);  // 先使用a的值进行打印方法 再去给a加1 10  print(a++);  // 先进行+1 再把a的值传入print方法打印 12  print(++a);  // 12 先操作 再减1  print(a--);  // 10  print(--a);}3-2关系运算符void main() {  int a = 5;  int b = 3;  // 判断内容是否相等 不判断类型  print(a == b);  print(a != b);  print(a > b);  print(a < b);  print(a >= b);  print(a <= b);  String a1 = '111';  String b1 = '321';  print(a1 == b1);}3-3逻辑运算符void main() {  bool ist = true;  bool isf = false;  print(!ist);  // &&都为true才为true  print(ist && isf);  // ||一个为真就真  print(ist || isf);  // String a;  // 声明后不赋值不能判断 为null  // print(a.isEmpty);  String a = '';  // true  print(a.isEmpty);  // false  print(!a.isEmpty);}3-4赋值运算符void main() {  int a = 10;  int b = 5;  // b??=10; b没有值会赋值 有值则无效  b ??= 10;  // 5  print(b);  // 12  a += 2;  print(a);  // 7  a -= 5;  print(a);// 相当于a*b=? 35  print(a *= b);// 7  print(a ~/= b);// 2  print(a %= b);// print(a/=b);}3-5条件表达式void main() {  int gender = 0;  // 三目运算符  // String a=gender==0?'male':'female';  String a = gender == 0 ? 'male=$gender' : 'female=$gender';  print(a);  String b;  String c = 'java';// b如果为空把c给b  String d = b ?? c;  print(d);}

July 3, 2020 · 1 min · jiezi

Flutter开发必备Dart基础Dart快速入门

本文首发于微信公众号「Android开发之旅」,欢迎关注 ,获取更多技术干货概述Dart从2.0开始变为强类型语言,静态类型。这点和Java、C#等比较相似。也就是说在编译时就已经知道变量的类型那么就是静态类型语言。开发人员在开发的时候需要指定变量的类型。这有什么优点呢? 就是所有类型检查都可以通过编译器来完成。可以提前预报一些琐碎的错误。同时Dart还是面向对象的编程语言。像python、Java、Koltin、PHP等等都是面向对象的语言。 dart的特性:JIT:即时编译,开发期间做到更快的编译和更快的代码重载。 但也有劣势就是在运行时需要将代码编译为机械码,那么直观感受就是慢,所以我们在开发期间有时候会发现卡顿,但是打release包之后效果会变好的原因。AOT:事前编译,release期间已经编译为二进制代码,所以加载会更快更流畅。 常用数据类型任何语言的基础都是数据类型,dart也一样。dart中主要有数字、字符串、布尔、集合和var类型。 num类型num是dart中的数字类型的父类,它有两个子类:int和double。当类型指定为num的时候可以赋值为小数也可以赋值为整数,但是一旦指定了某一个具体的类型,就只能赋值这一类型的值。 void _numType() { num num1 = -2.0; //定义为小数 num num2 = 4; //定义num为你太 int num3 = 5; //只能是整数 double num4 = 6.0; //只能是双精度 }其中这些类型之间是可以相互转换的,如: print(num1.abs()); // 取绝对值 输出 2.0 print(num2.toDouble()); // 转为小数 输出4.0 print(num3.toString()); // 转为字符串 输出"5" print(num4.toInt()); // 转为整数 输出 6String类型String的定义也比较简单,它可以使用单引号也可以使用双引号定义,定义一个String类型后面可以通过逗号隔开定义多个变量。在dart中拼接字符串可以是 + 号链接,还可以使用 $ 符号进行拼接,如果是变量则直接使用$xx,如果是表达式则使用${xxx}将其包裹起来。 void _stringType() { String str1 = 'str1', str2 = "str2"; //定义字符串str1和str2 String str3 = "字符串数据类型"; //定义字符串str3 String str4 = 'str1=$str1;str2=$str2'; //通过$符拼接字符串 String str5 = "str1=" + str1 + " str3=" + str3; //通过+号拼接字符串 }字符串还有一些常用的API,比如字符串截取,获取指定字符串位置,匹配字符串开头等等。 ...

November 5, 2019 · 4 min · jiezi

fluuter制作微信简单介绍

ps: 由于是商业代码,不会将代码公开,只将开发过程中遇到的坑以及经验分享出来,所以请不要问代码地址; 部分截图: 未完待续

October 18, 2019 · 1 min · jiezi

前端转Flutter-对照Javascript学Dart

最近在学flutter,发现flutter的编程语言Dart和Javascript有诸多相似,对于前端开发者而已好处在于有JavaScript经验学习Dart会快一些,缺点在于容易搞混这两种语言。因此在学习的过程中记录了一下Javascript和Dart的对比,方便记忆。 1. 程序入口(Entry Point)Javascript: JS不像很多语言有一个main()函数作为程序入口,JS并没有标准的程序入口,JS会从代码的第一行开始执行(在执行之前会有一些预处理的工作,比如变量提升和函数提升)。在实际项目中我们通常会有一个index.js这样的入口文件。 Dart: Dart有一个标准的程序入口: main(){}2. 数据类型(Data Types)Javascript: JS 有 8 种内置数据类型,分别为: 基本类型: Boolean:布尔类型,有两个值true和falseNull:空类型,只有一个值nullUndefined:变量未初始化则为Undefined类型Number:数字类型,取值范围为-(2^53-1) ~ 2^53 - 1,可以为整数和小数Bigint:表示任意精度的整数,如const x = 2983479827349701793697123nString:字符串类型,可用 "", '', ``表示。其中``用于字符串模板,比如:`1 + 2 = ${1+2}`Symbol:符号类型,用于定义匿名且唯一的值,一般用作 Object 属性的 keyObject其中 7 个基本类型的值是不可变的(immutable value)。Object 用来定义复杂数据类型,JS内置了一些复杂类型比如:Function、Date、Array、Map、Set等。 Dart: Dart 也有 8 种内置数据类型: Boolean:布尔类型,有两个值true和falseNumber:数字类型,又分为int和double类型 int:整型,取值范围为-2^63 ~ 2^63 - 1double:64位双精度浮点型String:字符串类型,可用"", ''表示。与 JS 类似,可使用模板语法'${expression}',当expression是一个标识符时可省略{}List:相当于 Javascript 中的 Array,例如:var arr = [1, 2, 3]Set:与 JavaScript 的 Set 类似,表示无序且无重复元素的集合,例如:var countries = {'china', 'usa', 'russia', 'german'}Map:与 JavaScript 的 Map 类似,表示一组键值对的集合,其中键必须唯一,键和值都可以为任意类型,例如: ...

October 14, 2019 · 6 min · jiezi

dart知识体系

October 7, 2019 · 0 min · jiezi

flutter-SharedPreferences桌面插件

flutter可以构建跨平台的多端应用, 正好开发的应用需要桌面版本, 那就尝试传说中的无缝移植. 然而刚开始就遇到了大麻烦: 移动端普遍使用的SharedPreferences在桌面端只有macOS有实现! 虽然引入shared_preferences: ^0.5.3+4在编译时没有问题, 但windows和linux平台在运行时会抛出[ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: MissingPluginException(No implementation found for method getAll on channel plugins.flutter.io/shared_preferences)的异常. 这"无缝"来的太猛, 有点措手不及...等着官方出正式版本断然不行的, 必须得自行添加在平台层的实现了. 好在桌面端可以以插件的方式与shared_preferences对接上, 结合在macOS上的实现及提供的示例程序总算给搞出来了! 以linux为例, 写一下久违的C++. 开发环境: 之前尝试最新的flutter1.9运行集成的桌面应用,但失败了, 所以开发环境在flutter1.8, 这是确定可以运行起来的 flutterSDK: v1.8.0@stable flutter Desktop: c183d46798b9642b8d908710de1e7d14a8573c86@master pubspec.yaml: dependencies: shared_preferences: ^0.5.3+4运行以下命令确保可以运行起来或者参照这篇文章 (flutterSDK安装不再另行说明): git clone https://github.com/google/flutter-desktop-embedding.git desktopcd desktop/exampleflutter run我们就是基于example应用把SharedPreferences插件开发出来. 插件结构所有的插件位于desktop仓库根目录下的plugins, 其中的flutter_plugins特指的是flutter在其它端(android/iOS/web)也可以用的插件, 其余的表示只在桌面端(macOS/linux/windows)用到的插件, 需要实现的SharedPreferences就在plugins/flutter_plugins/shared_preferences_fde下,可以看到只有macos的目录.所以开始新建linux平台上的插件: 创建目录及文件借助已经有url_launcher_fde mkdir -p plugins/flutter_plugins/shared_preferences_fde/linux && cd plugins/flutter_plugins/shared_preferences_fde/linuxcp ../../url_launcher_fde/linux/Makefile .cp ../../url_launcher_fde/linux/url_launcher_fde_plugin.{cc,h} .插件命名将Makefile中的url_launcher_fde_plugin改成shared_preferences_fde_plugin, 这是编译插件所需要的Makefile, 只需改这一个名称即可.本地cpp文件改成shared_preferences_fde_plugin.{cc,h}, 同时类名和宏也改成相应的名称, 最好用sed搜索一起替换 FLUTTER_PLUGIN_EXPORT void SharedPreferencesRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar);class SharedPreferencesPlugin : public flutter::Plugin { virtual ~SharedPreferencesPlugin();private: SharedPreferencesPlugin();}...RegisterWithRegistrar方法里有个通道注册的名称"plugins.flutter.io/shared_preferences", 这和异常抛出时的名称是一致的. ...

October 3, 2019 · 4 min · jiezi

Flutter九之Flutter的布局Widget

Flutter布局前言一:接下来一段时间我会陆续更新一些列Flutter文字教程更新进度: 每周至少两篇; 更新地点: 首发于公众号,第二天更新于掘金、思否、开发者头条等地方; 更多交流: 可以添加我的微信 372623326,关注我的微博:coderwhy 希望大家可以 帮忙转发,点击在看,给我更多的创作动力。 前言二:为了实现界面内组件的各种排布方式,我们需要进行布局,和其他端不同的是,Flutter中因为万物皆Widget,所以布局也是使用Widget来完成的。 Flutter中的布局组件非常多,有31个用于布局的组件,Flutter布局组件; 在学习的过程中,我们没必要一个个全部掌握,掌握最常用的,一些特殊的组件用到时去查文档即可; Flutter将布局组件分成了 单子布局组件(Single-child layout widgets) 和 多子布局组件(Multi-child layout widgets) 一. 单子布局组件单子布局组件的含义是其只有一个子组件,可以通过设置一些属性设置该子组件所在的位置信息等。比较常用的单子布局组件有:Align、Center、Padding、Container。 1.1. Align组件1.1.1. Align介绍看到Align这个词,我们就知道它有我们的对齐方式有关。在其他端的开发中(iOS、Android、前端)Align通常只是一个属性而已,但是Flutter中Align也是一个组件。 我们可以通过源码来看一下Align有哪些属性: const Align({ Key key, this.alignment: Alignment.center, // 对齐方式,默认居中对齐 this.widthFactor, // 宽度因子,不设置的情况,会尽可能大 this.heightFactor, // 高度因子,不设置的情况,会尽可能大 Widget child // 要布局的子Widget})这里我们特别解释一下widthFactor和heightFactor作用: 因为子组件在父组件中的对齐方式必须有一个前提,就是父组件得知道自己的范围(宽度和高度);如果widthFactor和heightFactor不设置,那么默认Align会尽可能的大(尽可能占据自己所在的父组件);我们也可以对他们进行设置,比如widthFactor设置为3,那么相对于Align的宽度是子组件跨度的3倍;1.1.2. Align演练我们简单演练一下Align class MyHomeBody extends StatelessWidget { @override Widget build(BuildContext context) { return Align( child: Icon(Icons.pets, size: 36, color: Colors.red), alignment: Alignment.bottomRight, widthFactor: 3, heightFactor: 3, ); }} ...

October 1, 2019 · 4 min · jiezi

Flutter必备语言Dart教程04-异步库

现在我们来看看如何在Dart中处理异步代码。使用Flutter时,会执行各种操作,例如网络调用和数据库访问,这些操作都应该异步执行。 在Dart中导入库在Dart中使用异步,需要先导入异步库。 Future异步库包含一个名为Future的类,Future是基于观察者模式的。如果您熟悉Javascript中的Rxjs或Promises,那么理解起来会很容易。 简单来说,Future定义的是“未来”发生的事情,也会在未来某个时刻返回一个值给我们。让我们看看如何使用Future。 Future是一个泛型类型,即 Future <T>,你必须指定返回值的类型。 我们定义了一个名为getAJoke的函数,它返回一个Future <String>。使用new关键字创建Future,Future构造函数接收一个返回值类型为T的函数参数。无论您在匿名函数中返回什么,都会被转化为Future。 在main中,我们调用getAJoke函数,该函数返回 Future<String>。我们通过调用then函数来订阅Future,这些函数注册了一个回调,当Future发出值时调用它。我们还注册了一个catchError来处理在执行Future期间发生的任何异常。在我们的示例中,我们没有发生任何异常。 以下是发生异常的示例。 在这个例子中,结果会立即返回。但在实际业务中,会使用Future来执行一些需要时间的代码,例如网络调用。我们可以使用 Future.delayed() 来模拟该行为。 现在,如果运行该程序,等待2秒钟后才出结果。让我们看另一个例子。 如您所见,我在调用函数后添加了一个print语句。在这种情况下,首先执行print语句,然后打印从Future返回的值。 但是,如果我们有一个Future,我们想先执行它,然后再执行print语句。这就需要使用 async/await 了。 Async/Await 首先在第3行的main函数的大括号之前添加async关键字。 然后我们在调用getAJoke函数之前添加await关键字,它的作用是等待从Future返回结果。后边的代码也会一直等待着被执行。 我们将代码包装在 try/catch 块中,来捕获任何异常(之前使用catchError回调来捕获)。要使用关键字await,就必须使用async关键字标记该函数,否则它将无法工作。 总结这就是本教程系列的内容,更多语法细节和功能特性,强烈推荐阅读官方语言文档。接下来让我们一起探索Flutter开发之旅。

September 20, 2019 · 1 min · jiezi

Flutter三之搞定Dart一

前言一:接下来一段时间我会陆续更新一些列Flutter文字教程更新进度: 每周至少两篇; 更新地点: 首发于公众号,第二天更新于掘金、思否等地方; 更多交流: 可以添加我的微信 372623326,关注我的微博:coderwhy 希望大家可以 帮忙转发,点击在看,给我更多的创作动力。 前言二:若干年后,你会发现,选错了公司和行业的损失远比选错了语言来的大。而且计算机这个行业,只会一种编程语言显然是不现实的,接收一门新的语言,并没有你想象中的难! 一. Dart介绍和安装1.1. 认识DartGoogle为Flutter选择了Dart就已经是既定的事实,无论你多么想用你熟悉的语言,比如JavaScript、Java、Swift、C++等来开发Flutter,至少目前都是不可以的。 在讲解Dart的过程中,我会假定你已经有一定的编程语言基础,比如JavaScript、Java、Python、C++等。 其实如果你对编程语言足够的自信,Dart的学习过程甚至可以直接忽略: 因为你学过N种编程语言之后,你会发现他们的差异是并不大;无非就是语法上的差异+某些语言有某些特性,而某些语言没有某些特性而已;在我初次接触Flutter的时候,并没有专门去看Dart的语法,而是对于某些语法不太熟练的时候回头去了解而已;所以,如果你对编程语言已经足够了解,可以跳过我们接下来的Dart学习: 我也并不会所有特性都一一罗列,我会挑出比较重要的语言特性来专门讲解;某些特性可能会等到后面讲解Flutter的一些知识的时候单独拿出来讲解;下面,我们就从安装Dart开始吧! 1.2. 安装Dart为什么还需要安装Dart呢?事实上在安装Flutter SDK的时候,它已经内置了Dart了,我们完全可以直接使用Flutter去进行Dart的编写并且运行。 但是,如果你想单独学习Dart,并且运行自己的Dart代码,最好去安装一个Dart SDK。 下载Dart SDK 到Dart的官方,根据不同的操作系统下载对应的Dart 官方网站:https://dart.dev/get-dart无论是什么操作系统,安装方式都是有两种:通过工具安装和直接下载SDK,配置环境变量 1.通过工具安装 Windows可以通过ChocolateymacOS可以通过homebrew具体安装操作官网网站有详细的解释2.直接下载SDK,配置环境变量 下载地址:https://dart.dev/tools/sdk/ar...我采用了这个安装方式。下载完成后,根据路径配置环境变量即可。1.3. VSCode配置学习Dart过程中,我使用VSCode作为编辑器 一方面编写代码非常方便,而且界面风格我也很喜欢另一方面我可以快速在终端看到我编写代码的效果使用VSCode编写Dart需要安装Dart插件:我目前给这个VSCode装了四个插件 Dart和Flutter插件是为Flutter开发准备的Atom One Dark Theme是我个人比较喜欢的一个主题Code Runner可以点击右上角的按钮让我快速运行代码 二. Hello Dart2.1. Hello World接下来,就可以步入正题了。学习编程语言,从祖传的Hello World开始。 在VSCode中新建一个helloWorld.dart文件,添加下面的内容: main(List<String> args) { print('Hello World');}然后在终端执行dart helloWorld.dart,就能看到Hello World的结果了。 完成了这个执行过程之后,以你之前学习的编程语言来看,你能得到多少信息呢? 2.2. 程序的分析接下来,就是我自己的总结: 一、Dart语言的入口也是main函数,并且必须显示的进行定义;二、Dart的入口函数main是没有返回值的;三、传递给main的命令行参数,是通过List<String>完成的。 从字面值就可以理解List是Dart中的集合类型。其中的每一个String都表示传递给main的一个参数;四、定义字符串的时候,可以使用单引号或双引号;五、每行语句必须使用分号结尾,很多语言并不需要分号,比如Swift、JavaScript;三. 定义变量3.1. 明确声明(Explicit)明确声明变量的方式, 格式如下: 变量类型 变量名称 = 赋值;示例代码: String name = 'coderwhy';int age = 18;double height = 1.88;print('${name}, ${age}, ${height}'); // 拼接方式后续会讲解注意事项: 定义的变量可以修改值, 但是不能赋值其他类型 ...

September 9, 2019 · 4 min · jiezi

Flutter循环滚动首尾相连停留在中间的视图

天长地久有时尽此恨绵绵无绝期 前言设计来自项目中搜索模块的更多筛选功能,筛选宜居人数主要功能:支持循环滚动、且每次都停留在屏幕中间位置首尾相连点击滚动到屏幕中间位置 默认样式 滚动之后样式 设计思路ListView.builder滚动视图NotificationListener监听开始滚动和结束滚动时候的位置ScrollController控制视图滚动到中间位置 核心逻辑一NotificationListener监听ListView滚动,ScrollController滚动到视图中间位置,isScrollEndNotification解决由于内部_controller.jumpTo方法会无限调用滚动结束事件 // 监听事件 NotificationListener<ScrollNotification>( child: ListView.builder( controller: _controller, itemCount: _list.length * 10000,// 初始化10000个item itemExtent: width / 7, itemBuilder: (BuildContext context, int index) { // index % _list.length 无限轮播 return _listViewItem(_list[index % _list.length], index, singleItemWidth); }, scrollDirection: Axis.horizontal, ), onNotification: (ScrollNotification notification) { // 开始滚动的监听事件 if(notification is ScrollStartNotification) { isScrollEndNotification = false; _startLocation = notification.metrics.pixels; } // 滚动结束的监听事件 if (notification is ScrollEndNotification && !isScrollEndNotification) { _endLocation = notification.metrics.pixels; isScrollEndNotification = true; double differ = _endLocation-_startLocation; double offset = 0; if(differ>0) { offset = (differ.abs()~/singleItemWidth)*singleItemWidth; if(differ%singleItemWidth >= singleItemWidth/2) { offset += singleItemWidth; } // _controller滚到中间的位置, _controller.jumpTo(_startLocation + offset); } else if(differ<0){ differ = differ.abs(); offset = ((differ~/singleItemWidth)*singleItemWidth); if((differ%singleItemWidth) >= (singleItemWidth/2)) { offset += singleItemWidth; } // _controller滚到中间的位置 _controller.jumpTo(_startLocation - offset); } } double result = notification.metrics.pixels/singleItemWidth; int round = result.round();// 四舍五入 // 计算索引并返回给外部 widget.slideAction(round%12);// 取余之后返回索引 return true; }, ),核心逻辑二每个item对应的widget点击item滚动到视图中间位置 ...

August 27, 2019 · 3 min · jiezi

我为什么认为Flutter是移动应用程序开发的未来

原文:https://medium.com/free-code-camp/why-i.... 译者:前端技术小哥几年前,我使用Java和Objective-C在Android和iOS开发中有一些涉足。在实际工作中我花了将近一个月时间,我决定继续学习深挖。但是我发现很难深入。 在最近,我了解到Flutter,并决定再为移动应用程序开发提供支持。我立刻爱上了它,因为它使开发多平台应用程序变得非常有趣。自从了解它以来,我已经使用它创建了一个应用程序和一个库。Flutter似乎是一个非常有前途的一步,我想解释为什么我相信这一点有一下几个不同的原因。 由Dart提供技术支持Flutter使用谷歌开发的Dart语言。如果您之前使用过Java,那么您将非常熟悉Dart的语法,因为它们非常相似。除了语法之外,Dart是一种完全不同的语言。我不打算深入讨论Dart,因为它有点超出范围,但我想讨论一下我认为最有用的功能。此功能支持异步操作。Dart不仅支持它,而且非常容易。如果您正在进行IO或其他耗时的操作(例如查询数据库),那么您最有可能在所有Flutter应用程序中使用这些内容。如果没有异步操作,任何耗时的操作都会导致程序冻结直到完成。为了防止这种情况,Dart为我们提供了async和await这让我们的节目继续执行,同时等待这些较长的操作完成的关键字。让我们看看几个例子:一个没有异步操作,一个是有。 // Without asynchronous operationsimport 'dart:async';main() { longOperation(); printSomething();}longOperation() { Duration delay = Duration(seconds: 3); Future.delayed(delay); print('Waited 3 seconds to print this and blocked execution.');}printSomething() { print('That sure took a while!');}并输出: Waited 3 seconds to print this and blocked execution.That sure took a while! 这不太理想。没有人想要使用在执行长时间操作时冻结的应用程序。所以让我们稍微修改一下并使用async和await关键字 // With asynchronous operationsimport 'dart:async';main() { longOperation(); printSomething();}Future<void> longOperation() async { var retVal = await runLongOperation(); print(retVal);}const retString = 'Waited 3 seconds to return this without blocking execution.';Duration delay = Duration(seconds: 3);Future<String> runLongOperation() => Future.delayed(delay, () => retString);printSomething() { print('I printed right away!');}并再次输出: ...

August 7, 2019 · 2 min · jiezi

Flutter网格型布局-GridView篇

1. 前言Flutter作为时下最流行的技术之一,凭借其出色的性能以及抹平多端的差异优势,早已引起大批技术爱好者的关注,甚至一些闲鱼,美团,腾讯等大公司均已投入生产使用。虽然目前其生态还没有完全成熟,但身靠背后的Google加持,其发展速度已经足够惊人,可以预见将来对Flutter开发人员的需求也会随之增长。 无论是为了现在的技术尝鲜还是将来的潮流趋势,都9102年了,作为一个前端开发者,似乎没有理由不去尝试它。正是带着这样的心理,笔者也开始学习Flutter,同时建了一个用于练习的仓库,后续所有代码都会托管在上面,欢迎star,一起学习。 经过上一篇对ListView组件的学习,我们已经对滚动型组件的使用有了初步认识,这对今天要学习的GridView组件十分有帮助。因为两者都继承自BoxScrollView,所以两者的属性有80%以上是相同的,用法非常相似。 而且如下图所示可见,GridView网格布局在app中的使用频率其实非常高,所以接下来就让我们来看看在Flutter中如何使用吧~ 2. 初识GridView今天我们的主角GridView一共有5个构造函数:GridView,GridView.builder,GridView.count,GridView.extent和GridView.custom。但是不用慌,因为可以说其实掌握其默认构造函数就都会了~ 来看下GridView构造函数(已省略不常用属性): GridView({ Key key, Axis scrollDirection = Axis.vertical, bool reverse = false, ScrollController controller, ScrollPhysics physics, bool shrinkWrap = false, EdgeInsetsGeometry padding, @required this.gridDelegate, double cacheExtent, List<Widget> children = const <Widget>[],})虽然又是一大堆属性,但是大部分都很熟悉,老朋友嘛~除了一个必填参数gridDelegate外,全和ListView默认构造函数的参数一样,这也是文章开头为什么说掌握了ListView再学GridView非常容易的原因。 那么接下来,就让我们来重点关注下gridDelegate这个参数,它其实是GridView组件如何控制排列子元素的一个委托。跟踪源码我们可以在scroll_view.dart中看到,gridDelegate的类型是SliverGridDelegate,进一步跟踪进sliver_grid.dart可以看到SliverGridDelegate其实是一个抽象类,而且一共有两个实现类: SliverGridDelegateWithFixedCrossAxisCount:用于固定列数的场景;SliverGridDelegateWithMaxCrossAxisExtent:用于子元素有最大宽度限制的场景;2.1 SliverGridDelegateWithFixedCrossAxisCount我们先来看下SliverGridDelegateWithFixedCrossAxisCount,根据类名我们也能大概猜它是干什么用的:如果你的布局中每一行的列数是固定的,那你就应该用它。 来看下其构造函数: SliverGridDelegateWithFixedCrossAxisCount({ @required this.crossAxisCount, this.mainAxisSpacing = 0.0, this.crossAxisSpacing = 0.0, this.childAspectRatio = 1.0,})crossAxisCount:列数,即一行有几个子元素;mainAxisSpacing:主轴方向上的空隙间距;crossAxisSpacing:次轴方向上的空隙间距;childAspectRatio:子元素的宽高比例。 想必看到上面的示例图,你就秒懂其中各个参数的含义了。不过,这里有一点需要特别注意:如果你的子元素宽高比例不为1,那么你一定要设置childAspectRatio属性。 2.2 SliverGridDelegateWithMaxCrossAxisExtentSliverGridDelegateWithMaxCrossAxisExtent在实际应用中可能会比较少,来看下其构造函数: SliverGridDelegateWithMaxCrossAxisExtent({ @required this.maxCrossAxisExtent, this.mainAxisSpacing = 0.0, this.crossAxisSpacing = 0.0, this.childAspectRatio = 1.0,})可以看到除了maxCrossAxisExtent外,其他参数和SliverGridDelegateWithFixedCrossAxisCount都是一样的。那么maxCrossAxisExtent是干什么的呢?我们来看个例子: ...

July 15, 2019 · 1 min · jiezi

在Flutter中使用自定义Icon

1. 前言Flutter作为时下最流行的技术之一,凭借其出色的性能以及抹平多端的差异优势,早已引起大批技术爱好者的关注,甚至一些闲鱼,美团,腾讯等大公司均已投入生产使用。虽然目前其生态还没有完全成熟,但身靠背后的Google加持,其发展速度已经足够惊人,可以预见将来对Flutter开发人员的需求也会随之增长。 无论是为了技术尝鲜还是以后可能的工作机会,都9102年了,作为一个前端开发者,似乎没有理由不去尝试它。正是带着这样的心理,笔者也开始学习Flutter,同时建了一个用于练习的仓库,后续所有代码都会托管在上面,欢迎star,一起学习。 今天要分享的内容其实非常简单,我们都知道Flutter内置了一套Material Design风格的Icon图标,但对于一个成熟的App而言,通常情况下还是远远不够的。为此,我们需要在项目中引入自定义的Icon图标。 本文就将以Ant Design图标库为例,介绍如何在Flutter中引入自定义图标。 2. 准备工作:字体文件正所谓“巧妇难为无米之炊”,要想引入自定义图标,首先我们得准备好图标字体文件(.ttf后缀)。对于大公司而言,找视觉同学切就可以了。但如果是自己做的业余项目或者没有资源的时候,我们可以上阿里巴巴矢量图标库pick自己心仪的图标。 这里就以Ant Design官方图标库为例(一共有600个图标),通过以下操作,我们将图标字体文件加入到项目中: 添加购物车 --> 点击购物车 --> 下载代码 --> 解压 --> 拷贝至项目(可重命名) 3. 声明自定义字体仅仅将字体文件复制到项目中还不够,我们需要通过声明的方式来告诉Flutter有新字体可用。打开项目根目录下的pubspec.yaml文件,找到fonts这一段: To add custom fonts to your application, add a fonts section here, in this "flutter" section. Each entry in this list should have a "family" key with the font family name, and a "fonts" key with a list giving the asset and other descriptors for the font.注释就是让我们在该段文字下方添加自定义字体的声明,结合其注释掉的例子和当前的项目目录,我们可以这样配置: ...

July 15, 2019 · 1 min · jiezi

开发经验Flutter避免代码嵌套写好build方法

本文适合使用Flutter开发过一段时间的开发者阅读,旨在分享一种避免Flutter的UI代码嵌套太深问题的方法。如果对本文内容或观点有相关疑问,欢迎在评论中指出。优化效果(缩略图): 距离我接触Flutter已经过去了九个月,在Flutter代码编写的过程中,很多开发者都遇到了“回调地狱”的问题。在Flutter中,称之为回调并不准确,准确的说,是因为众多Widget互相嵌套在一起,导致反括号部分堆积严重,极度影响代码可读性。 本文将介绍一种代码编写风格,最大限度减少嵌套对代码阅读的影响。 初步介绍我们先来简单看一下,Flutter的UI代码: 使用build方法Flutter的Widget使用build方法来创建UI组件,然后通过注入child属性的方式为组件添加子组件,子组件可以继续包含child,通过调用每一个child的build方法,就形成了类似DOM结构的组件树,然后由渲染引擎渲染图形。 一个常见的定义组件的例子如下: class DeleteText extends StatelessWidget { // 我们在build方法中渲染自定义Widget @override Widget build(BuildContext context) { return Text('Delete'); }}组件属性必须为final要在Flutter中定义(继承)一个Widget,则它的属性必须都是final的。final意味着属性必须在构造函数中就被初始化完成,不接受提前定义,也不接受更改。所以,在生命周期中动态的改变Widget对象的属性是不可能的,必须使用框架的build方法来为构造函数动态指定参数,从而达到改变组件属性的功能。 class Avatar extends StatelessWidget { // 如果url属性不是final的,编译器会报出警告 final String url; // 这个构造方法很长,但是主要你写了final属性,VSCode就会帮我们自动生成 const Avatar({Key key, this.url}) : super(key: key); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), ), child: Image.network(url), ); }}Tips:自动创建构造方法,只要是构造方法没有的final属性,点击“快速修复”,就可以自动生成构造方法。 Flutter语法与HTML/CSS嵌套正是DOM树的特点,正如HTML其实也会无限嵌套一样(大多数前端可能看HTML看习惯了,都忘了HTML其实也经常会写成嵌套很深的形式),Flutter的UI代码嵌套本质是不可避免的,这正是Flutter UI代码的编写特点——一次成型,而不是通过addView之类的方法来手动管理每一个视图的生命周期。在此基础上,Flutter可以高效的反复重建Widget,在渲染效率上展现出了非常大的优势。 <!-- html的嵌套其实也很深 --><div> <div> <div> <div> <article> <h1></h1> <li></li> </article> </div> </div> </div></div>嵌套代码难以阅读当我们评判一串代码的时候,一个显而易见的点,就是代码距离左边的距离,如果一行代码距离左边达到了十多个tab,可想而知它被嵌套在了多么深的位置。 ...

July 14, 2019 · 5 min · jiezi

flutter-窗口初始与绘制流程

环境: flutter sdk v1.7.8+hotfix.3@stable 对应 flutter engine: 54ad777f 这里关注的是C++层面的绘制流程,平台怎样驱动和响应绘制与渲染的过程,并不是Dart部分的渲染。 结合之前的分析,在虚拟机实例的构造函数中调用了一个重要方法DartUI::InitForGlobal(), 调用流程再罗列一下: DartVMRef::Create DartVMRef::DartVMRef DartVM::Create DartVMData::Create DartVM::DartVM DartUI::InitForGlobal()实现体很明了,注册了各种类对象的方法,也就是说,这些在dart语言继承NativeFieldWrapperClass2的类都有一份在C++层的实现,也说明了DartSDK是如何提供接口绑定与C++层的实现,相当于java语言中的jni。另外还有针对Isolate的初始化,不过只是设置了一个可以import的路径,并不重要: DartIsolate::CreateRootIsolate DartIsolate::CreateDartVMAndEmbedderObjectPair DartIsolate::LoadLibraries DartUI::InitForIsolate Dart_SetNativeResolver视口设置我们知道RuntimeController持有一个Window实例,看Window实例被创建之后做了哪些制作: RuntimeController::RuntimeController Window::Window DartIsolate::CreateRootIsolate DartIsolate::DartIsolate DartIsolate::SetWindow => UIDartState::SetWindow WindowClient::UpdateIsolateDescription => RuntimeController::UpdateIsolateDescription RuntimeDelegate::UpdateIsolateDescription => Shell::UpdateIsolateDescription ServiceProtocol::SetHandlerDescription Window::DidCreateIsolate library_.Set("dart:ui") RuntimeController::FlushRuntimeStateToIsolate RuntimeController::SetViewportMetrics Window::UpdateWindowMetrics library_, _updateWindowMetrics操作从最里层的Window一直传递到了Shell,最重要的一个作用是初始化了ViewPort(视口:用作画布的大小,分辨率等尺寸信息),再跟一下ViewPort被初始化后又如何被设置的: FlutterView.onSizeChanged FlutterView.updateViewportMetrics FlutterJNI.setViewportMetrics FlutterJNI.nativeSetViewportMetrics ::SetViewportMetrics AndroidShellHolder::SetViewportMetrics [async:ui]Engine::SetViewportMetrics RuntimeController::SetViewportMetrics Window::UpdateWindowMetrics Engine::ScheduleFrame这里从Java调用到C++,FlutterView.onSizeChanged这个操作是在FlutterView实例创建之后被系统调用的(而FlutterView的创建发生在Activity.onCreate时机),显然是响应平台层的通知,这符合我们的认知预期,因为画布的大小可能因为用户操作发生变化,dart层需要被动响应。 需要注意的是响应onSizeChanged在Platform线程,调用Engine::SetViewportMetrics切到了UI线程,铭记Engine的所有的操作都是在UI线程。 启动画帧Engine在通过RuntimeController设置了窗口的尺寸之后,调用了另一个重要方法ScheduleFrame,于是看它的实现: Engine::ScheduleFrame Animator::RequestFrame [async:ui]Animator::AwaitVSync VsyncWaiter::AsyncWaitForVsync callback_= {Animator::BeginFrame} VsyncWaiter::AwaitVSync => VsyncWaiterAndroid::AwaitVSync [async:platform]FlutterJNI.asyncWaitForVsync AsyncWaitForVsyncDelegate.asyncWaitForVsync => VsyncWaiter.asyncWaitForVsyncDelegate Choreographer.getInstance().postFrameCallback Delegate::OnAnimatorNotifyIdle => Shell::OnAnimatorNotifyIdle Engine::NotifyIdle通知VSync这里操作有些凌乱,首先切到UI线程,又切到Platform线程,其实就是为了调用平台接口,搞清这个最终目的。终于涉及到了绘制图像所需要的关键类Animator 和VSyncWaiter : ...

July 14, 2019 · 1 min · jiezi

flutter-加载与运行Dart

环境: flutter sdk v1.7.8+hotfix.3@stable 对应 flutter engine: 54ad777fd29b031b87c7a68a6637fb48c0932862 在建立异步线程与消息循环之后,自然就是运行应用脚本,也就是dart文件。这一部分感觉很庞大而且千头万绪:对dart不同模式的编译,不同参数的配置,从代码看还有热加载(hot reload)的机制,从里到外都是一团乱麻;有这种感觉只是因为不熟悉,刚刚接触陌生环境产生的畏惧,只要熟悉啥都不是事。所以先不贸然进入热加载之类的细节,以目前了解的通信与异步为基础,渐次深入对象关联关系为上。 在FlutterActivityDelegate.onCreate的最后容易发现一个比较重要的调用runBundle ,深入的调用序列如下: FlutterActivity.onCreate FlutterActivityDelegate.onCreate FlutterActivityDelegate.runBundle FlutterView.runFromBundle FlutterView.preRun FlutterNativeView.runFromBundle FlutterNativeView.runFromBundleInternal FlutterJNI.runBundleAndSnapshotFromLibrary FlutterJNI.nativeRunBundleAndSnapshotFromLibrary FlutterView.postRun与C++层的调用序列分开: ::RunBundleAndSnapshotFromLibrary AndroidShellHolder::Launch...[async:ui_thread]Engine::Run Engine::PrepareAndLaunchIsolate RuntimeController::GetRootIsolate IsolateConfiguration::PrepareIsolate IsolateConfiguration::DoPrepareIsolate => AppSnapshotIsolateConfiguration::DoPrepareIsolate DartIsolate::PrepareForRunningFromPrecompiledCode DartIsolate::Run DartIsolate::InvokeMainEntrypoint这里已经有点晕了,各种名称堆砌在一起:DartIsolate, Dart_Isolate, RootDartIsolate; RunConfiguration, IsolateConfigurationAppSnapshotIsolateConfiguration; 撇开这些名称至少我们知道: AndroidShellHolder异步调用了Engine的Run方法Engine的Run跑在flutter的ui线程中Engine获取成员RuntimeController的一个叫RootIsolate的对象并最终调用了其DartIsolate::Run方法DartIsolate进入到了主入口方法,在这里就是lib/main.dart中的main()方法(runFromBundle(bundlePath, defaultPath, "main", false); FlutterView.java:611)。调用封装显然最终调的是dartSDK提供的各种方法,虽然我们大概知道flutter的Engine不会具体做dart代码的解释与执行,但比较棘手的是我们很难分清Engine与DartSDK的界限;DartSDK的接口方法散落在各处,他们的先后调用关系,对象依赖关系,内部状态的变化与检查,对于初学者都增加理解上的难度。所以最好是针对DartSDK再有一层封装或者抽象,不仅初始化与运行调用序列清晰,让sdk可替换(如果以后有其它的dart实现呢?),也让引擎真正成为引擎。 所谓引擎所以这里也可以对引擎的含义做一个梳理:引擎自然是可插拨的一种形态,只要与引擎提供的接口一致可以更换别的实现如同灯泡座与灯泡的关系,在这里显然无法更换DartSDK, 所以Flutter的引擎是针对平台的引擎,我们可以将应用移植到各种平台或者操作系统。 文档理解这时候死看代码难有进展,我们最好先了解DartSDK本来有什么。但发现竟然很难找到一份针对DartSDK的使用教程与文档(不是Dart语言使用文档,是开发集成Dart虚拟机的C接口文档),它的初始化,运行,集成像一个巨大的黑盒。因为最终运行的还是Isolate的Run方法,核心还是理解Dart的Isolate。 一些资料Engine-architecture里的UI Task Runner提到: (root Dart isolate)runs the application's main Dart code. Bindings are set up on this isolate by the engine to schedule and submit frames. Terminating the root isolate will also terminate all isolates spawned by that root isolate. ...

July 12, 2019 · 2 min · jiezi

Flutter开发中的一些Tips二

接着上篇 Flutter开发中的一些Tips,今天再分享一些我遇到的问题,这篇较上一篇,细节方面更多,希望“引以为戒”,毕竟细节决定成败。本篇的所有例子,都在我开源的flutter_deer中。希望Star、Fork支持,有问题可以Issue。附上链接:https://github.com/simplezhli... 1. setState() called after dispose()这个是我偶然在控制台发现的,完整的错误信息如下: Unhandled Exception: setState() called after dispose(): _AboutState#9c33a(lifecycle state: defunct, not mounted)当然flutter在错误信息之后还有给出问题原因及解决方法: This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().大致的意思是,widget已经在dispose方法时销毁了,但在这之后却调用了setState方法,那么会发生此错误。比如定时器或动画回调调用setState(),但此时页面已关闭时,就会发生此错误。这个错误一般并不会程序崩溃,只是会造成内存的泄露。 ...

July 11, 2019 · 4 min · jiezi

flutter-如何实现文件读写使用篇

flutter文件读写可以对磁盘文件进行操作,实现某些业务场景,那么我们开始来讲下这个文件读写操作。 使用的库插件(package)dart:io(用于数据处理)path_provider (用于获取路劲) 操作步骤1.获取正确的本地路径2.创建指向文件位置的引用3.写入数据到文件内4.从文件读取数据 1.获取正确的本地路径 我们获取路劲用的是这个插件path_provider 可以看到里面提供了两个获取路劲的方式Example Directory tempDir = await getTemporaryDirectory();String tempPath = tempDir.path;Directory appDocDir = await getApplicationDocumentsDirectory();String appDocPath = appDocDir.path;getTemporaryDirectory:【临时文件夹】也就是系统可以随时清空的临时缓存文件夹,在IOS中对应NSTemporaryDirectory在安卓中对应getCacheDir()  我们来将信息储存在临时文件夹中,首先我们创建一个Storage类里面开始写 class Storage { Future<String> get _localPath async { final _path = await getTemporaryDirectory(); return _path.path; }}2.创建指向文件位置的引用 确定文件储存位置之后,导入我们的io库,使用包里面的File类做泛型,然后获取路劲并且指向我们的文件名 Future<File> get _localFile async { final path = await _localPath; return File('$path/counter.txt');}3.写入数据到文件内 现在有了可以使用的File,直接就可以来读写数据了,因为我们使用了计数器,所以只需将证书储存为字符串格式,使用“$counter”即可(解析成整数方法在下一步) Future<File> writeCounter(counter) async { final file = await _localFile; return file.writeAsString('$counter'); }4.从文件读取数据 现在可以直接用file类来读取文件数据,然后用int的自带解析方法来解析我们读取的String Future<int> readCounter() async { try { final file = await _localFile; var contents = await file.readAsString(); return int.parse(contents); } catch (e) { return 0; } }完整代码 ...

July 10, 2019 · 2 min · jiezi

Flutter滚动型容器组件-ListView篇

1. 前言Flutter作为时下最流行的技术之一,凭借其出色的性能以及抹平多端的差异优势,早已引起大批技术爱好者的关注,甚至一些闲鱼,美团,腾讯等大公司均已投入生产使用。虽然目前其生态还没有完全成熟,但身靠背后的Google加持,其发展速度已经足够惊人,可以预见将来对Flutter开发人员的需求也会随之增长。 无论是为了技术尝鲜还是以后可能的工作机会,都9102年了,作为一个前端开发者,似乎没有理由不去尝试它。正是带着这样的心理,笔者也开始学习Flutter,同时建了一个用于练习的仓库,后续所有代码都会托管在上面,欢迎star,一起学习。 在上一篇文章中,我们学习了Flutter中使用频率最高的一些基础组件。但是在一些场景中,当组件的宽度或高度超出屏幕边缘时,Flutter往往会给出overflow警告,提醒有组件溢出屏幕。为了解决这个问题,今天我们就来学习最常用的一个滚动型容器组件 — ListView组件。 2. ListView使用方法从功能比较上来看,Flutter中的ListView组件和RN中的ScrollView/FlatList非常相似,但是在使用方法上还是有点区别。接下来,就跟着我一起来看看ListView组件都有哪些常用的使用方法。 2.1 ListView()第一种使用方法就是直接调用其默认构造函数来创建列表,效果等同于RN中的ScrollView组件。但是这种方式创建的列表存在一个问题:对于那些长列表或者需要较昂贵渲染开销的子组件,即使还没有出现在屏幕中但仍然会被ListView所创建,这将是一项较大的开销,使用不当可能引起性能问题甚至卡顿。 不过话说回来,虽然该方法可能会有性能问题,但还是取决于其不同的应用场景而定。下面我们就来看下其构造函数(已省略不常用属性): ListView({ Axis scrollDirection = Axis.vertical, ScrollController controller, ScrollPhysics physics, bool shrinkWrap = false, EdgeInsetsGeometry padding, this.itemExtent, double cacheExtent, List<Widget> children = const <Widget>[],})scrollDirection: 列表的滚动方向,可选值有Axis的horizontal和vertical,可以看到默认是垂直方向上滚动;controller : 控制器,与列表滚动相关,比如监听列表的滚动事件;physics: 列表滚动至边缘后继续拖动的物理效果,Android与iOS效果不同。Android会呈现出一个波纹状(对应ClampingScrollPhysics),而iOS上有一个回弹的弹性效果(对应BouncingScrollPhysics)。如果你想不同的平台上呈现各自的效果可以使用AlwaysScrollableScrollPhysics,它会根据不同平台自动选用各自的物理效果。如果你想禁用在边缘的拖动效果,那可以使用NeverScrollableScrollPhysics;shrinkWrap: 该属性将决定列表的长度是否仅包裹其内容的长度。当ListView嵌在一个无限长的容器组件中时,shrinkWrap必须为true,否则Flutter会给出警告;padding: 列表内边距;itemExtent: 子元素长度。当列表中的每一项长度是固定的情况下可以指定该值,有助于提高列表的性能(因为它可以帮助ListView在未实际渲染子元素之前就计算出每一项元素的位置);cacheExtent: 预渲染区域长度,ListView会在其可视区域的两边留一个cacheExtent长度的区域作为预渲染区域(对于ListView.build或ListView.seperated构造函数创建的列表,不在可视区域和预渲染区域内的子元素不会被创建或会被销毁);children: 容纳子元素的组件数组。上面的属性介绍一大堆,都不如一个实际例子来得实在。我们可以用一个ListView组件来包裹上篇文章中实现的银行卡,宠物卡片,朋友圈这三个例子: 代码(文件地址) class NormalList extends StatelessWidget { const NormalList({Key key}) : super(key: key); @override Widget build(BuildContext context) { return ListView( children: <Widget>[ CreditCard(data: creditCardData), PetCard(data: petCardData), FriendCircle(data: friendCircleData), ], ); }}预览 ...

July 10, 2019 · 3 min · jiezi

Flutter之自定义封装轮播图

黄沙百战穿金甲不破楼兰终不还 前言通过封装PageView+Timer实现无限轮播,手动拖拽时停止定时器功能,拖拽完成后开启定时器。 功能自动轮播手动轮播指示器功能展示 代码实现轮播组件的构造方法 class Carousel extends StatefulWidget { final List<BannerModel> banners; // BannerModel是每个item对应的模型 必传参数 final OnTapBannerItem onTap, // `必传参数` 传入点击每个item的方法 final Color indicatorNormalColor;// 指示器球的正常颜色 final Color indicatorCurrentColor;// 指示器球的当前颜色 final double indicatorWidth;// 指示器球的宽高 final double indicatorMargin;// 指示器球之间间距 final bool hiddenIndicator;// 是否影藏指示器 final bool hiddenIndicatorForSingle;// 单个图片是否影藏指示器 final bool autoScroll; // 是否循环 final int seconds; // 轮播间隔 Carousel( {Key key, @required this.banners, @required this.onTap, this.seconds = 5, // 不传 默认5秒 轮播一次 this.autoScroll = true, this.hiddenIndicator = false, this.hiddenIndicatorForSingle = true, this.indicatorWidth = 6, this.indicatorMargin = 1.5, this.indicatorCurrentColor = Colors.white, this.indicatorNormalColor = Colors.grey}) : super(key: key); @override State<StatefulWidget> createState() { return _BannerState(); }}组件状态实现方法 ...

July 9, 2019 · 3 min · jiezi

用Flutter构建漂亮的UI界面-基础组件篇

1. 前言Flutter作为时下最流行的技术之一,凭借其出色的性能以及抹平多端的差异优势,早已引起大批技术爱好者的关注,甚至一些闲鱼,美团,腾讯等大公司均已开始使用。虽然目前其生态还没有完全成熟,但身靠背后的Google加持,其发展速度已经足够惊人,可以预见将来对Flutter开发人员的需求也会随之增长。 无论是为了现在的技术尝鲜还是将来的潮流趋势,都9102年了,作为一个前端开发者,似乎没有理由不去尝试它。正是带着这样的心理,笔者也开始学习Flutter,同时建了一个用于练习的仓库,后续所有代码都会托管在上面,欢迎star,一起学习。 今天分享的是Flutter中最常用到的一些基础组件,它们是构成UI界面的基础元素:容器,行,列,绝对定位布局,文本,图片和图标等。 2. 基础组件2.1 Container(容器组件) Container组件是最常用的布局组件之一,可以认为它是web开发中的div,rn开发中的View。其往往可以用来控制大小、背景颜色、边框、阴影、内外边距和内容排列方式等。我们先来看下其构造函数: Container({ Key key, double width, double height, this.margin, this.padding, Color color, this.alignment, BoxConstraints constraints, Decoration decoration, this.foregroundDecoration, this.transform, this.child,})2.1.1 width,height,margin,padding这些属性的含义和我们已经熟知的并没有区别。唯一需要注意的是,margin和padding的赋值不是一个简单的数字,因为其有left, top, right, bottom四个方向的值需要设置。Flutter提供了EdgeInsets这个类,帮助我们方便地生成四个方向的值。通常情况下,我们可能会用到EdgeInsets的4种构造方法: EdgeInsets.all(value): 用于设置4个方向一样的值;EdgeInsets.only(left: val1, top: val2, right: val3, bottom: val4): 可以单独设置某个方向的值;EdgeInsets.symmetric(horizontal: val1, vertical: val2): 用于设置水平/垂直方向上的值;EdgeInsets.fromLTRB(left, top, right, bottom): 按照左上右下的顺序设置4个方向的值。2.1.2 color该属性的含义是背景颜色,等同于web/rn中的backgroundColor。需要注意的是Flutter中有一个专门表示颜色的Color类,而非我们常用的字符串。不过我们可以非常轻松地进行转换,举个栗子: 在web/rn中我们会用'#FF0000'或'red'来表示红色,而在Flutter中,我们可以用Color(0xFFFF0000)或Colors.red来表示。 2.1.3 alignment该属性是用来决定Container组件的子组件将以何种方式进行排列(PS:再也不用为怎么居中操心了)。其可选值通常会用到: Alignment.topLeft: 左上Alignment.topCenter: 上中Alignment.topRight: 右上Alignment.centerLeft: 左中Alignment.center: 居中Alignment.centerRight: 右中Alignment.bottomLeft: 左下Alignment.bottomCenter: 下中Alignment.bottomRight: 右下2.1.4 constraints在web/rn中我们通常会用minWidth/maxWidth/minHeight/maxHeight等属性来限制容器的宽高。在Flutter中,你需要使用BoxConstraints(盒约束)来实现该功能。 // 容器的大小将被限制在[100*100 ~ 200*200]内BoxConstraints( minWidth: 100, maxWidth: 200, minHeight: 100, maxHeight: 200,)2.1.5 decoration该属性非常强大,字面意思是装饰,因为通过它你可以设置边框,阴影,渐变,圆角等常用属性。BoxDecoration继承自Decoration类,因此我们通常会生成一个BoxDecoration实例来设置这些属性。 ...

July 8, 2019 · 5 min · jiezi

flutter手势动作有效区域占满容器

问题描述:自定义按钮时,GestureDetector明明是占满容器的,但是点击容器内空白区域不能触发点击事件,而点击容器内的文字可以正常触发。解决方案:GestureDetector有个behavior属性,设置behavior: HitTestBehavior.translucent即可,它默认是HitTestBehavior.deferToChild

July 1, 2019 · 1 min · jiezi

Flutter之BottomNavigationBar的封装

BottomNavigationBarBottomNavigationBar 是属于 Scaffold 中的一个位于底部的控件。通常和 BottomNavigationBarItem 配合使用。BottomNavigationBar构造方法: BottomNavigationBar({ Key key, @required this.items, this.onTap, this.currentIndex = 0, this.elevation = 8.0, BottomNavigationBarType type, Color fixedColor, this.backgroundColor, this.iconSize = 24.0, Color selectedItemColor, this.unselectedItemColor, this.selectedIconTheme = const IconThemeData(), this.unselectedIconTheme = const IconThemeData(), this.selectedFontSize = 14.0, this.unselectedFontSize = 12.0, this.selectedLabelStyle, this.unselectedLabelStyle, this.showSelectedLabels = true, bool showUnselectedLabels, })BottomNavigationBarItem底部导航栏要显示的Item,有图标和标题组成BottomNavigationBarItem构造方法: const BottomNavigationBarItem({ @required this.icon, this.title, Widget activeIcon, this.backgroundColor, })构建BottomNavigationBar // 创建底部BottomNavigationBar BottomNavigationBar _bottomNavigationBar(List <String>titles, List <String>icons){ return BottomNavigationBar( items: [ _bottomBarItem(titles[0], icons[0]), _bottomBarItem(titles[1], icons[1]), _bottomBarItem(titles[2], icons[2]), _bottomBarItem(titles[3], icons[3]), ], currentIndex: _currentIndex, type: BottomNavigationBarType.fixed,// 当items大于3时需要设置此类型 onTap: _bottomBarItemClick, selectedFontSize: 12, ); } // 创建item BottomNavigationBarItem _bottomBarItem(String title, String iconName,) { return BottomNavigationBarItem( icon: _image(iconName), title: Text(title), activeIcon: _image('${iconName}_select'), backgroundColor: Colors.white, ); } //image Widget _image(String iconName) { return Image.asset( 'assets/images/${iconName}@2x.png',// 在项目中添加图片文件夹 width: 24, height: 24, ); } //item点击事件 _bottomBarItemClick(int index) { setState(() { _currentIndex = index; }); }调用构建方法 ...

June 26, 2019 · 1 min · jiezi

Flutter-代码开发规范

Flutter 代码开发规范文档 仅做参考 标识符三种类型大驼峰类、枚举、typedef和类型参数 class SliderMenu { ... } class HttpRequest { ... } typedef Predicate = bool Function<T>(T value);包括用于元数据注释的类 class Foo { const Foo([arg]); } @Foo(anArg) class A { ... } @Foo() class B { ... }使用小写加下划线来命名库和源文件 library peg_parser.source_scanner; import 'file_system.dart'; import 'slider_menu.dart';不推荐如下写法: library pegparser.SourceScanner; import 'file-system.dart'; import 'SliderMenu.dart';使用小写加下划线来命名导入前缀 import 'dart:math' as math; import 'package:angular_components/angular_components' as angular_components; import 'package:js/js.dart' as js;不推荐如下写法: ...

June 25, 2019 · 4 min · jiezi

flutter如何打开第三方应用

如何打开第三方应用 彩蛋:如果走完安装流程后运行Flutter时提示: export LANG=en_US.UTF-8 Error running pod install需要在配置文件.bash_profile中加上: export LANG=en_US.UTF-81.flutter开发者网站下载url_launcher插件 下载链接 2.在 pubspec.yaml 文件中添加依赖: dependencies: url_launcher: ^5.0.33.安装: flutter pub get4.导入: import 'package:url_launcher/url_launcher.dart';5.使用: 一:打开浏览器 _launchURL、_openMapApp为自定义方法名 可以根据自己的场景自定义名称 _launchURL() async { const url = 'https://flutter.io'; if (await canLaunch(url)) { await launch(url); } else { throw 'Could not launch $url'; } }二:打开外部APP,如 打开地图: 打开外部APP 需要外部APP提供跳转的schema _openMapApp() async { const url = 'geo:52.32,4.917'; //APP提供的schema if (await canLaunch(url)) { await (launch(url)); //安卓中打开 } else { //iOS中打开 const url = 'http://maps.apple.com/?ll=52.32,4.917'; if (await canLaunch(url)) { await launch(url); } else { throw 'Could not launch $url'; } } }

June 21, 2019 · 1 min · jiezi

flutter的路由与导航

定义路由 // 在Scaffold中定义路由,如下 routes: <String, WidgetBuilder>{ // less 路由名称 ,StatelessGroupPage() 要跳转的实例page 'less': (BuildContext context) => StatelessGroupPage(), 'full': (BuildContext context) => StateFullGroupPage(), }路由跳转 // 根据路由名称跳转 routerName:'less' 'full' Navigator.pushNamed(context, routerName);导航跳转 // 导航直接跳转 page实例: StatelessGroupPage() StateFullGroupPage() Navigator.push(context, MaterialPageRoute(builder:(context)=>page))

June 21, 2019 · 1 min · jiezi

flutter加载图片几种方式

加载本地图片 1.在项目根目录下创建名为 assets 的文件夹2.将装有本地图片的自定义文件夹(假设取名为images)放在assets文件夹的目录下3.配置pubspec.yaml文件,如下: flutter: uses-material-design: true assets: - assets/images/ //切记 切记 切记 assets和-之间有个空格 4.配置pubspec.yaml文件之后在命令行执行 flutter pub get 或者 点击pubspec.yaml文件上方的 Packages get按钮,用来生效pubspec.yaml文件5.在项目中使用本地图片,如下两种方式: 1.Image(image: AssetImage('assets/images/***.jpg’)2.Image.asset('assets/images/***.jpeg’)加载网络图片 Image.network('https://...')

June 20, 2019 · 1 min · jiezi

在vscode中开发flutter常用快捷键

热加载 -- r点击热加载,直接查看预览效果 热重启 -- R点击热重启 如果修改了状态相关的代码则需要hot restart,否则只需要hot reload即可显示布局网格 -- P在虚拟机中显示网格,工作中经常使用 切换Andriod和ios风格 -- O切换两者操作系统的预览模式 退出调试 -- Q退出调试预览模式

June 3, 2019 · 1 min · jiezi

试玩Flutter写了个无聊的App附Flutter感受

今日份的XGithub:https://github.com/shuiRong/T... (今日份的X) 这是一个Flutter写的一个无聊的App每天推荐一个不同的:图片、诗歌、名言、音乐、乐评、高等数学、两种配色、化学方程式、Github Repo、知乎问题。 (P.S. 除图片来自Bing首页,每天仅有一张外,其他页面支持下拉刷新来更新内容) 下载APK Flutter初试感受:去玩Swift了 :p如何运行:首先确保你本地有 Flutter 项目所需环境,官方文档 英文教程 中文文档 下载项目: git clone https://github.com/shuiRong/TodayXcd ./TodayX运行项目前需要通过 USB 将手机连接到电脑上或者启动模拟器,遵循官方教程即可。 然后运行项目: (如果你使用 Visual Studio Code 来开发,也可以不通过下述命令行,而是通过全程通过VSC来操作:请参看第2、3小节) // 安装项目依赖flutter packages get// 运行项目flutter run项目中用到的第三方数据感谢作者 诗歌:https://api.gushi.ci/all.json // 20190526231929{ "content": "墉集欺猫鼠,林藏逐雀鹯。", "origin": "中秋咏怀借杜子美秋日述怀一百韵和寄柳州假鸣桑先生", "author": "徐威", "category": "古诗文-动物-写猫"}名言:https://v1.hitokoto.cn/ // 20190526232311{ "id": 14, "hitokoto": "用你的笑容去改变这个世界,别让这个世界改变了你的笑容。", "type": "a", "from": "网络", "creator": "酱七", "created_at": "1468605909"}Bing首页图:https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1 // 20190526232348{ "images": [ { "startdate": "20190525", "fullstartdate": "201905251600", "enddate": "20190526", "url": "/th?id=OHR.MarathonduMont_ZH-CN5049722437_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp", "urlbase": "/th?id=OHR.MarathonduMont_ZH-CN5049722437", "copyright": "圣米歇尔山 (© Leroy Francis/Getty Images)", "copyrightlink": "http://www.bing.com/search?q=%E5%9C%A3%E7%B1%B3%E6%AD%87%E5%B0%94%E5%B1%B1&form=hpcapt&mkt=zh-cn", "title": "", "quiz": "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20190525_MarathonduMont%22&FORM=HPQUIZ", "wp": true, "hsh": "02a3db999f76ec114c45de97cecc30ac", "drk": 1, "top": 1, "bot": 1, "hs": [ ] } ], "tooltips": { "loading": "正在加载...", "previous": "上一个图像", "next": "下一个图像", "walle": "此图片不能下载用作壁纸。", "walls": "下载今日美图。仅限用作桌面壁纸。" }}音乐:https://music.aityp.com/playlist/detail?id=145787433 ...

May 27, 2019 · 2 min · jiezi

Flutter试玩小结

写在前面前段时间Google I/O 2019大会上flutter再次狠狠的刷了一波存在感,随便看看不管是微博,推上还是medium或者一些其它的技术社区上也是铺天盖地的讨论,优缺点搬运过来差不多是: 优点:贯彻始终的widget的设计思想和开发方式对各种背景还是很亲切的跨全平台的愿景很吸引人写界面和相关的逻辑快速简单文档还有编辑器,IDE的支持很完善和贴心社区很活跃教程也很多(包括官方的youtube channel)保持状态的hot reload,效率高到飞起缺点:Desktop, Web平台的支持还处在preview或试验阶段包的数量很少,质量也一般各平台要想实现复杂效果的支持度不行,需要花费大量时间目前采用flutter开发的上架App其实还是很少总结来说就是目前还在初级阶段,简单业务为主的项目可能比较合适,追求体验的App可能会碰到很多坑,实现复杂体验所发的时间基本会抵消掉跨平台复用所带来的成本优势,但是需要合适的场景和项目才有机会体会到这些全部的优缺点。 示例不过还是本着尝鲜还有无聊打发时间的想法,花了点时间学习和把以前做的一个iOS捷径写了一个Cat Catalog的简单App,支持: view cats of different breeds by tagssearch by simple keywordlike your favorite cat(s)read further information on Wikipedia page项目功能和逻辑都很简单,用到的API/widgets很少: JSON -serializingAnimation基础的布局控件使用外部包和字体 代码放在Github: https://github.com/gnehcc/cat... 上了,有兴趣的同学也可以看看,有用的话欢迎star。 使用感受最后就用到的功能谈几点简单感受: Dart学习起来非常简单,只要有任何其他语言的使用经验,花点时间看看language tour基本就可以开始了动画抽象程度高,用起来很简单,异步语法简洁,用的VS Code + Dart插件 + Flutter插件,代码写起来流畅到飞起HOT RELOAD 开发起来特别顺手,大部分时间只需要保存代码之后Simulator就直接更新了,实在不行hot restart也挺快的Widget的嵌套用法让代码有点杂乱,如果Code Extraction做的勤快点的话勉强好点第三方库的数量还有功能丰富度目前确实还比较原始一个Code Base 基本无缝的跑在iOS和Andoid平台,但Flutter的控件其实有两套(Material 和 Cupertino),抛开平台特定功能,也需要至少需要特别处理。个人觉得如果有时间完全可以一遍学一边写写玩玩(主要是也花不了多少时间,写着写着发现动漫也追完了...),如果满意的话上传到App商店也是件不错的事情。 发布在个人博客上的文章可能未及时同步过来,欢迎直接访问 https://gnehc.me 链接捷径个人主页: https://sharecuts.cn/user/eOz... Flutter Dev: https://flutter.dev/docs Flutter boring show A tour of the Dart language: https://dart.dev/guides/langu...

May 21, 2019 · 1 min · jiezi

Flutter-15-发布正式成为全平台-UI-框架

一. 序在 Google I/O 2019 上,Dart 团队宣布推出新的 Flutter 稳定版本 1.5,这是 Flutter 迄今为止最大的一次版本发布。 伴随着 Flutter 1.5 的发布,同期也宣布发布 Flutter for Web 的 Preview 版本,正式开启了 Flutter 的全平台 UI 框架之路。 早在年初发布的 Flutter 2019 Roadmap 中,就有提到,会在今年支持移动设备之外的平台,对 Web 的支持,算是完成了一个新的里程碑吧。 二. Flutter for WebFlutter 之所以能够在移动平台上运行,主要是依赖的 Flutter Engine,就是 Flutter 所依赖的运行环境。这就导致在移动平台,只要你使用了 Flutter,哪怕只用混合开发的模式写了一个页面,这也将为你的 App 增大大约 4MB 的体积。 而 Flutter for Web,完全是一种全新的模式,它可以将 Dart 编写的现有 Flutter 代码,编译成可嵌入浏览器并部署到任何 Web 服务器的代码。 编译后的代码,完全是基于 HTML、CSS 和 JavaScript 这些标准的 Web 技术,所以它也不需要任何浏览器插件的支持。 ...

May 9, 2019 · 1 min · jiezi

学起来Flutter将支持桌面应用开发

英文原文 Flutter团队正在扩展Flutter,支持创建macOS、Windows和Linux应用程序。从长远来看,这项工作将提供一个完全继承的解决方案,flutter create,flutter run 和 flutter build 在桌面平台开发上的表现将和现在的移动平台开发中一样,但是目前这项工作还在进行中。 当前的状况下面提供了平台状况的高级概述。 详细信息请参阅 源码仓库 重要提示:Flutter桌面API仍处于早期阶段,如有更改,恕不另行通知。不会提供API或ABI的向后兼容性。Flutter更新之后,所有使用了Flutter的项目的代码都需要做更新并且重新编译。macOS系统这是最成熟的桌面平台(出于一些原因,它非常接近于我们已经支持的iOS)。 桌面版中以Flutter开头的类与iOS通用,所以应该基本稳定。以FLE开头的类仍处于早期阶段。 Windows系统当前的 Windows shell 只是 GLFW 占位符, 以便与前期实验. 未来它将被 Win32 或者 UWP shell 替代,因为Win32 或者 UWP shell 允许在Flutter应用程序中嵌入view-level。 预计,最终版本的shell APIs和当前实现的方式完全不同。 Linux和Windows一样,当前 Linux shell 只是 GLFW 占位符。我们想创建一个库,让开发可以任何部分嵌入Flutter,无论你使用GTK+, Qt, wxWidgets, Motif, 还是其他任意工具包。但是我们还没有确定一个好方法。 插件所有平台都支持编写插件(例如 flutter-desktop-embedding 这些插件),但是,目前依然很少有插件实际上具有桌面支持。 工具Flutter支持桌面的工具开发还在进行中。要使用任何桌面支持工具(例如用flutter devices列出主机)目前必须满足两点: 你不能使用稳定的Flutter channel。因为桌面支持还没有被认为是稳定的和适合生产环境的你必须设置ENABLE_FLUTTER_DESKTOP环境变量为true。这是为了避免在指定长期解决方案时影响现有的移动开发工作流程(参见:#30724。预构建Shell库默认情况下,桌面库未下载,可以通过运行运行flutter precache下载,根据你的你的操作系统带上参数 --linux,--macos或 --windows。 C++ WrapperWindows和Linux库提供C语言API。为了更容易使用他们,可以使用C++包装器,将其构建到应用程序,中以便与提供更高级的API调用。上面提到的flutter precache命令会将这个包装器的源码下载到与该库同目录下的cpp_client_wrapper文件夹中。 使用Shells由于目前没有桌面shell工具的支持,你需要自己写一个应用的运行工具,并且在库里链接,就像任何你使用的插件那样。这将需要做一些你熟悉所使用的桌面系统的原生开发。如果你在桌面系统系统开发方面没有经验,你需要等到flutter桌面开发工具支持可用。 所以,使用Shells请参阅你所使用的操作系统的库的头。将来会补充更多的文档。至于现在,可以参考flutter-desktop-embedding示例,也许会有启发。 另外,你的Flutter桌面应用程序还需要bundle Flutter assets(由flutter build bundle创建)。在Windows和Linux你将还会需要Flutter引擎的ICU数据。(在你的Flutter目录中下的bin/cache/artifacts/engine查找icudtl.dat) macOS 注意目前你必须在XIB中设置FLEView,而不是在代码中设置(以后会改)。如下: 拖入一个OpenGL视图修改类型为FLEView.选中Double Buffer选项. 如果你的视图没有被绘制出来,可能是因为忘记这个步骤.选中Hi-Res Backing支持选项. 如果在高DPI显示器上只显示部分程序,那么可能是因为忘记这个步骤。

April 28, 2019 · 1 min · jiezi

Flutter - Widget-Context-Stage

读前须知:此篇文章基本上是Widget - State - Context - InheritedWidget的翻译并且删减了部分我个人觉得没有意义的文字,保留下来的部分也不会逐字逐句精确翻译,所以其实强烈推荐阅读英文原文。以下文章里面除了第一张思维导图是本人所作之外,凡是出现的示例代码和图片都是上面提到的英文文章里面的。本人在这里对此表示感激和愧疚,如果涉及到侵权,本人会立即删掉整篇文章。All the example codes and images used in this article are from Widget - State - Context - InheritedWidget excepting the first one. If this is of tort, I will delete this article immediately.正文开始:Widget, State 和Context是每一个flutter开发者必须要完全理解的概念,但是具体来说要理解哪些知识呢?这篇文章会就以下几个知识点进行讲解:1: Stateful和Stateless widget的差别2:什么是Context3: 什么是State以及怎么使用4:一个context和他的state之间的关系5:InheritedWidget以及在在Widget树里面怎么传递信息6:rebuild的概念接下来文章会按照以下结构去展开:PS:由于此篇的篇幅很长,此思维导图里面的第二部分(左边的’怎样获取State‘)的篇幅也会很长,所以第二部分会放到下一篇文章讲解。一:基本概念解释1:什么是WidgetWidget即组件。在flutter里面几乎万物都是Widget,跟我们常说的component是同一个概念。2: 什么是Widget treeWidget按照树形结构组合的产物就是Widget tree。这个Widget tree的每个节点上又是一个Widget。包含其他组件的组件叫做父组件,被其他组件包含的组件叫做子组件。比如下面一段代码:@overrideWidget build(BuildContext){ return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( ‘You have pushed the button this many times:’, ), new Text( ‘$_counter’, style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: new FloatingActionButton( onPressed: _incrementCounter, tooltip: ‘Increment’, child: new Icon(Icons.add), ), );}上面的一段代码,如果我们用图像表示它的widget tree的话就如下图所示: 3:什么是Context一个context标识了一个widget在widget tree的结构中是在哪个地方被build的。简而言之就是一个context标明了一个widget是挂载在widget tree的那个节点的。一个context只属于一个widget。假如一个widget A有子组件,那么widget A的context会变成子组件的父context。以上文提到的widget tree为例子,假如我们画出它的context,如下图所示(一个颜色代表一个context):Context Visibility(上下文可见性)只在其自己的context或者在其父context可见。从以上描述,我们可以轻易地找到一个widget的父widget,例如:Scaffold > Center > Column > Text:context.ancestorWidgetOfExtractType(Scaffold) => 会找到第一个沿着Text的context一路向上而遇到的第一个Scaffold.从一个父组件,也能找到子组件,但是不建议这么做(稍后会讨论到)。4:Widget的分类在Flutter里面有2类widget:Stateless WidgetStateful Widget从字面意思上可以理解Stateless Widget就是无状态的组件,Stateful Widget是有状态的组件,接下来我们对二者做更具体的讲解。Stateless Widget有些组件只依赖于他们自己的配置信息,这些配置信息一般是由他们的父组件在build他们的时候提供。换句话说,这些组件一旦创建之后,就不用担心任何变化。这类型的组件就是Stateless Widget.典型的例子比如Text, Row, Column, Container等,对于这些组件来说在build的时候,我们只需要传一些参数给他们就好。一个Stateless Widget只能被build一次,意思就是一旦build,不会因为任何的事件或者用户行为而重新build。Stateless Widget lifecycle下面是一个典型的Stateless Widget的代码结构。如我们所见,我们传递一些阐述给它的构造函数,但是请记住,这些参数不会再之后被改变了,所以一般你也会看到这些参数是被定义为final的。class MyStatelessWidget extends StatelessWidget { MyStatelessWidget({ Key key, this.parameter, }): super(key:key); final parameter; @override Widget build(BuildContext context){ return new … }}虽然有另一个方法(createElement)可以被overridden,但是一般没人会用到它。唯一一个需要被override的方法就是build().Stateless widget的生命周期是直接而简单的,如下所示:初始化通过build()方法渲染Stateful Widget一些其他的组件所拥有的数据会在组件生命周期内产生变化,这些数据就变成了dynamic(动态的)的。这些被组件拥有的会在组件的生命周期内改变的数据的列表,我们叫做State。而拥有以上特点的组件,我们就叫做Stateful Widget。Stateful Widget的例子就好比一个用户可以选择的Checkboxes的列表或者一个会根据某种条件而disabled的Button。5: 什么是State一个State定义了一个StatefulWidget的“行为”部分。State包含了以下旨在与一个组件交互的:行为(behaviour)UI布局(layout)任何对State的改变都会导致这个组件的rebuild。6:State和Context之间的关系对Stateful Widgets而言,一个State是和一个Context是绑定的,而且这种绑定关系是永久的,一个State永远不会改变他的Context。即使一个组件的Context在Widget tree上发生了移动,这个State还是会依然和那个Context绑定在一起。非常重要的知识点:因为一个State对象和一个Context是绑定的,这就意味着这个State对象不能从其他的Context下直接被获取到(这一点之后会讨论到)7:StatefulWidget的生命周期(lifecycle)前面已经介绍和很多StatefulWidget的相关概念和基础知识,接下来我们来了解一下StatefulWidget的生命周期,这里不会介绍全部的生命周期,先只挑几个重要的,与本篇文章的主旨相关的几个来讲。先看下下面一个StatefulWidget的例子:class MyStatefulWidget extends StatefulWidget { MyStatefulWidget({ Key key, this.parameter, }): super(key: key); final parameter; @override _MyStatefulWidgetState createState() => new _MyStatefulWidgetState();}class _MyStatefulWidgetState extends State<MyStatefulWidget> { @override void initState(){ super.initState(); // Additional initialization of the State } @override void didChangeDependencies(){ super.didChangeDependencies(); // Additional code } @override void dispose(){ // Additional disposal code super.dispose(); } @override Widget build(BuildContext context){ return new … }}下面的这个图展示了(一个简化的版本)一个StatefuleWidget在创建的时候,内部的一些列行为。在这个图的右边你可以看到一个State对象的内部状态变化以及最右边的Context是在什么时候和State产出联系而因此变为可用的。initState()initState()是在构造函数之后的第一个被调用的生命周期方法。当你需要再初始化阶段做一个额外的操作的时候,你需要override它。典型的在initState()方法里额外的操作比如动画,或者某些数据准备。假如你override initState(),记得调用super.initState()并且这行代码要放在initState()方法体的最前面。意思就是你得让super.initState()执行了之后再去执行你额外的初始化工作。在这个阶段,一个context是存在的,但是你并不能真正地使用它,因为这时候context和state还没有完成绑定。一旦initState()执行完毕,State对象就初始化好了,context也就可以被使用了。initState()在整个生命周期内只会被调用一次。didChangeDependencies()didChangeDependencies()是在生命周期里第二个被调用的方法。这个阶段,context已经可以被使用了。假如你的组件是链接到了InheritedWidget,根据context你需要初始化一些listeners(监听者),通常你需要override这个方法。注意,如果某个组件是链接了InheritedWidget,那么这个组件每次重建(rebuild),didChangeDependencies()都会被调用。假如你要override这个方法,你应该首先调用super.didChangeDependencies().build()build()跟在didChangeDependencies()(和didUpdateWidget())之后被调用。这个方法就是用来构建你的组件的。每一次State对象发生变化(或者InheritedWidget需要通知它的注册者)时,build()方法都会被调用。通常,我们通过调用setState((){…})来改变State对象,强制build()被调用,从而重新build我们的widget。dispose()dispose()在这个组件被销毁的时候被调用。一般我们override这个方法,可以在组件被销毁的时候做一个清理工作。override dispose()记得调用super.dispose()并且把它放在方法体的最后。8: StatelessWidget和StatefulWidget之间的抉择既然在Flutter里面有StatelessWidget和StatefulWidget这两种类型的组件,那在这二者之间如何抉择呢?记住标准就是:在这个组件的生命周期内,是否有会变化的数据,这个组件是否需要rebuild?如果答案是yes,那你就需要一个StatefulWidget而不会StatelessWidget。9:StatefulWidget的2个组成部分组件的构造函数部分class MyStatefulWidget extends StatefulWidget { MyStatefulWidget({ Key key, this.color, }): super(key: key); final Color color; @override _MyStatefulWidgetState createState() => new _MyStatefulWidgetState();}这部分是一个StatefulWidget的public部分。这部分不会在一个组件的生命周期内发生改变,它只是接收一些参数以便它的State可以使用。比如上面这个例子里面的color这个参数。Widget State定义部分class _MyStatefulWidgetState extends State<MyStatefulWidget> { … @override Widget build(BuildContext context){ … }}_MyStatefulWidgetState是这个Widget在其生命周期内变化的部分,也是使得这个Widget能够rebuild的部分。_MyStatefulWidgetState通过widget.{name of the variable}可以获取存在MyStatefulWidget内的任意变量。例如这里,可以通过widget.color获取color变量。10:Widget的唯一标识-key在Flutter里面,每一个组件都是唯一标识的,这个唯一标识是在build的时候被定义的。这个唯一的标识就是组件的可选参数:Key. 加入key缺省了,系统会默认给你创建一个。在某些情形下,你必须强制制定key,以便你可以通过这个key获取到这个组件。你可以通过下面的一些helper来达到上面的目的:GlobalKey, LocalKey, UniqueKey或者ObjectKey。GlobalKey保证这个key在整个application里面都是唯一的。以下的例子就是保证myKey在整个application都是唯一的:GlobalKey myKey = new GlobalKey(); … @override Widget build(BuildContext context){ return new MyWidget( key: myKey ); }PS:由于此篇的篇幅已经够长,之前的思维导图里面的第二部分的篇幅也会很长,所以第二部分会放到下一篇文章讲解。 ...

April 14, 2019 · 2 min · jiezi

Dart编译技术在服务端的探索和应用

前言最近闲鱼技术团队在Flutter+Dart的多端一体化的基础上,实现了FaaS研发模式。Dart吸取了其它高级语言设计的精华,例如Smalltalk的Image技术、JVM的HotSpot和Dart编译技术又师出同门。由Dart实现的语言容器,它可以在启动速度、运行性能有不错的表现。Dart提供了AoT、JIT的编译方式,JIT拥有Kernel和AppJIT的运行模式,此外服务端应用有各自不同的运行特点,那么如何选择合理的编译方法来提升应用的性能?接下来我们用一些有典型特点的案例来引入我们在Dart编译方案的实践和思考。案例详情相应的,我们准备了短周期应用(EmptyMain & Fibonnacci & faas_tool),长周期应用(HttpServer)分别来说明不同的编译方法在各种场景下的性能表现测试环境参考#实验机1Mac OS X 10.14.3 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz * 4 / 16GB RAM#实验机2Linux x86_64Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz * 4 / 8GB RAM#Dart版本Dart Ver. 2.2.1-edge.eeb8fc8ccdcef46e835993a22b3b48c0a2ccc6f1 #Java HotSpot版本Java build 1.8.0_121-b13 Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)#GCC版本Apple LLVM version 10.0.1 (clang-1001.0.46.3)Target: x86_64-apple-darwin18.2.0Thread model: posix短周期应用Case1. EmptyMain例子是一个空函数实现,以此来评估语言平台本身的启动性能,我们使用默认参数编译一个snapshot#1.默认条件下的app-jit snapshot生成dart snapshot-kind=app-jit snapshot=empty_main.snapshot empty_main.dart测试结果作为现代高级语言Dart和Java在启动速度上在同一水平线C语言的启动速度是其它语言的20x,基本原因是C没有Java、Dart语言平台的RuntimeKernel和AppJIT方式运行有稳定的微小差异,总体AppJIT优于KernelCase2. Fibonacci数列我们分别用C、Java、Dart用递归实现Fibonacci(50)数列,来考察编译工作对性能的影响。long fibo(long n){ if(n < 2){ return n; } return fibo(n - 1) + fibo(n - 2);}AppJIT使用优化阈值实现激进优化,这样编译器在Training Run中立即获得生成Optimized代码#2.执行激进优化 dart –no-background-compilation \ –optimization-counter-threshold=1 \ –snapshot-kind=app-jit \ –snapshot=fibonacci.snapshot fibonacci.dart将Fibonacci编译成Kernel#3.生成Kernel snapshotdart –snapshot=fibonacci.snapshot fibonacci.dartAoT的Runtime不在Dart SDK里,需要自行编译AoT Runtime#4.AoT编译pkg/vm/tools/precompiler2 fibonacci.dart fibonacci.aot#5.AoT的方式执行out/ReleaseX64/dart_precompiled_runtime fibonacci.aot测试结果Dart JIT对比下,AppJIT在激进优化后性能稍好于Kernel,差距微小,编译的成本占比可以忽略不计Dart AoT模式下的性能约为JIT的1/6不到JIT运行模式下,HotSpot的执行性能最优,优于Dart AppJIT 25%以上包括C语言在内的AoT运行模式性能均低于JIT,Dart AppJIT性能优于25%问题AoT由于自身的特性(和语言无关),无法在运行时基于Profile实现代码优化,峰值性能在此场景下要差很多,但是为何Dart VM比HotSpot有25%的差距?接下来我们针对Fibonacci做进一步优化#6.编译器调优,调整递归内联深度dart –inlining_recursion_depth_threshold=5 fibonacci.snapshot 50#7.编译器调优,HotSpot调整递归内联深度java -XX:MaxRecursiveInlineLevel=5 Fabbonacci 50测试结果HotSpot VM性能全面领先于Dart VM;两者在最优情况下HotSpot VM的性能优于Dart 9%左右Dart VM 借助JIT调优,性能有大幅提升,相比默认情况有40%左右的提升Dart AppJIT 性能微弱领先Kernel也许也不难想象JVM HotSpot目前在服务器开发领域上的相对Dart成熟,相比HotSpot,DartVM的“出厂设置”比较保守,当然我们也可以大胆猜测,在服务端应用下应该还有除JIT的其它优化空间;和Case1相同,Kernel模式的性能依然低于AppJIT,主要原因是Kernel在运行前期需要把AST转换为堆数据结构、经历Compile、Compile Optimize等过程,而在适当Training run后的AppJIT snapshot在VM启动时以优化后的IL(中间代码)执行,但很快Kernel会追上App-jit,最后性能保持持平。有兴趣的读者可以参阅Vyacheslav Egorov Dart VM的文章。Case3. FaaS容器编译工具在前面我们提到过Dart版本的FaaS语言容器,为追求极致的研发体验,我们需要缩短用户Function打包到部署运行的时间。就语言容器层面而言,Dart提供的Snapshot技术可以大大提升启动速度,但是从用户Function到Snapshot(如下图)生成所产生的编译时间在不做优化的情况下超过10秒,还远远达不到极致体验的要求。我们这里通过一些测试,来寻找提升性能的途径faas_tool是一个完全用Dart编写的代码编译、生成工具。依托于faas_tool, Function的编写者不用关心如何打包、接入中间件,faas_tool提供一系列的模版及代码生成工具可以将用户的使用成本降低,此外faas_tool还提供了HotReload机制可以快速响应变更。这次我们提供了基于AoT、Kernel、AppJIT的用例来执行Function构建流程,分别记录时间消耗、中间产物大小、产物生成时间。为了验证在JIT场景下DartVM是否可通过调整Complier的行为带来性能提升,我们增加了JIT的测试分组测试结果AoT>AppJIT>kernel,其中AoT比优化后的AppJIT有3倍左右性能提升,性能是Source的1000倍JIT(Kernel, AppJIT)分组下,通过在运行时减少CompilerOptimize或暂停PGO可以提升性能很显然faas_tool最终选择了AoT编译,但是性能结果和Case2大相径庭,为了搞清楚原因我们进一步做一下CPU ProfileCPU ProfileAppJITDart App-jit模式 43%以上的时间参与编译,当然取消代码优化,可以让编译时间大幅下降,在优化情况下可以将这个比率下降到13%KernelKernel模式有61%以上的CPU时间参与编译工作, 如果关闭JIT优化代码生成,性能有15%左右提升,反之进行激进优化将有1倍左右的性能损耗AoT下的编译成本AoT模式下在运行时几乎编译和优化成本(CompileOptimized、CompileUnoptimized、CompileUnoptimized 占比为0),直接以目标平台的代码执行,因此性能要好很多。P.S. DartVM 的Profile模块在后期的版本升级更改了Tag命名, 有需要进一步了解的读者参考VM Tags附:DartVM调优和命令代码#8.模拟单核并执行激进优化 dart –no-background-compilation \ –optimization-counter-threshold=1 \ tmp/faas_tool.snapshot.kernel #9.JIT下关闭优化代码生成dart –optimization-counter-threshold=-1 \ tmp/faas_tool.snapshot.kernel #10. Appjit verbose snapshotdart –print_snapshot_sizes \ –print_snapshot_sizes_verbose \ –deterministic \ –snapshot-kind=app-jit \ –snapshot=/tmp/faas_tool.snapshot faas_tool.dart #11.Profile CPU 和 timeline dart –profiler=true \ –startup_timeline=true \ –timeline_dir=/tmp \ –enable-vm-service \ –pause-isolates-on-exit faas_tool.snapshot长周期应用HttpServer我们用一个简单的Dart版的HttpServer作为典型长周期应用的测试用例,该用例中有JsonToObject、ObjectToJson的转换,然后response输出。我们分别用Source、Kernel以及AppJIT的方式在一定的并发量下运行一段时间void processReq(HttpRequest request){ try{ final List<Map<String,dynamic>> buf = <Map<String,dynamic>>[]; final Boss boss = new Boss(numOfEmployee: 10); //Json反序列化对象 getHeadCount(max: 20).forEach((hc){ boss.hire(hc.idType, hc.docId); buf.add(hc.toJson()); }); request.response.headers.add(‘cal’,’${boss.calc()}’); //Json对象转JsonString request.response.write(jsonEncode(buf)); request.response.close() .then((v) => counter_success ++) .timeout(new Duration(seconds:3)) .catchError((e) => counter_fail ++)); } catch(e){ request.response.statusCode = 500; counter_fail ++; request.response.close(); }}测试结果 上面三种无论是何种方式启动,最终的运行时性能趋向一致,编译成本在后期可以忽略不计,这也是JIT的运行特点在AppJIT模式下在应用启动起初就有接近峰值的性能,即使在Kernel模式下也需要时间预热达到峰值性能,Source模式下VM启动需要2秒以上,因此需要相对更长时间达到峰值性能。从另一方面看应用很快完成了预热,不久达到了峰值性能P.S. 长周期的应用Optimize Compiler会经过Optimize->Deoptimize->Reoptimize的过程, 由于此案例比较简单,没体现Deoptimize到Reoptimize的表现附:VM调优脚本#12.调整当前isolate的新生代大小,默认2M最大32M的新生代大小造成频繁的YGCdart –new_gen_semi_max_size=512 \ –new_gen_semi_initial_size=512 \ http_server.dart \ –interval=2 总结和展望Dart编译方式的选择编译成本为主导的应用,应优先考虑AoT来提高应用性能长周期的应用在启动后期编译成本可忽略,应该选择JIT方式并开启Optimize Compiler,让优化器介入长周期的应用可以选择Kernel的方式来提升启动速度,通过AppJIT的方式进一步缩短warmup时间AppJIT减少了编译预热的成本,这个特性非常适合对一些高并发应用在线扩容。Kernel作为Dart编译技术的前端,其平台无关性将继续作为整个Dart编译工具链的基础。在FaaS构建方案的选择通过CPU Profile得出faas_tool是一个编译成本主导的应用,最终选择了AoT编译方案,结果大大提升了语言容器的构建的构建速度,很好满足了faas对开发效率的诉求仍需改进的地方从JIT性能表现来看,DartVM JIT的运行时性和HotSpot相比有提升余地,由于Dart语言作为服务端开发的历史不长,也许随着Dart在服务端的技术应用全面推广,相信DarVM在编译器后端技术上对服务器级的处理器架构做更多优化。本文作者:闲鱼技术-无浩阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 11, 2019 · 2 min · jiezi

Dart VS code 开发环境搭建

最近发现flutter 有公司在生产环境用了,研究跟进一下新技术,因为flutter 开发要用的是JavaScript 的超集Dart语言 ,要学习flutter ,先学一下Dart, 那么开发运行环境是学习的第一步。dart vs code 开发环境访问Dart 官网 https://www.dartlang.org/tools下载 Dart SDKvscode添加 launch.json运行代码

March 31, 2019 · 1 min · jiezi

Flutter 实现原理及在马蜂窝的跨平台开发实践

一直以来,跨平台开发都是困扰移动客户端开发的难题。在马蜂窝旅游 App 很多业务场景里,我们尝试过一些主流的跨平台开发解决方案,比如 WebView 和 React Native,来提升开发效率和用户体验。但这两种方式也带来了新的问题。比如使用 WebView 跨平台方式,优点确实非常明显。基于 WebView 的框架集成了当下 Web 开发的诸多优势:丰富的控件库、动态化、良好的技术社区、测试自动化等等。但是缺点也同样明显:渲染效率和 JavaScript 的执行能力都比较差,使页面的加载速度和用户体验都不尽如人意。而使用以 React Native(简称 RN)为代表的框架时,维护又成了大难题。RN 使用类 HTML+JS 的 UI 创建逻辑,生成对应的原生页面,将页面的渲染工作交给了系统,所以渲染效率有很大的优势。但由于 RN 代码是通过 JS 桥接的方式转换为原生的控件,所以受各个系统间的差异影响非常大,虽然可以开发一套代码,但对各个平台的适配却非常的繁琐和麻烦。为什么是 Flutter2018 年 12 月初,Google 正式发布了开源跨平台 UI 框架 Flutter 1.0 Release 版本,马蜂窝电商客户端团队进行了调研与实践,发现 Flutter 能很好的帮助我们解决开发中遇到的问题。跨平台开发,针对 Android 与 iOS 的风格设计了两套设计语言的控件实现(Material & Cupertino)。这样不但能够节约人力成本,而且在用户体验上更好的适配 App 运行的平台。重写了一套跨平台的 UI 框架,渲染引擎是依靠 Skia 图形库实现。Flutter 中的控件树直接由渲染引擎和高性能本地 ARM 代码直接绘制,不需要通过中间对象(Web 应用中的虚拟 DOM 和真实 DOM,原生 App 中的虚拟控件和平台控件)来绘制,使它有接近原生页面的性能,帮助我们提供更好的用户体验。同时支持 JIT 和 AOT 编译。JIT 编译方式使其在开发阶段有个备受欢迎的功能——热重载(HotReload),这样在开发时可以省去构建的过程,提高开发效率。而在 Release 运行阶段采用 AOT 的编译方式,使执行效率非常高,让 Release 版本发挥更好的性能。于是,电商客户端团队决定探索 Flutter 在跨平台开发中的新可能,并率先应用于商家端 App 中。在本文中,我们将结合 Flutter 在马蜂窝商家端 App 中的应用实践,探讨 Flutter 架构的实现原理,有何优势,以及如何帮助我们解决问题。Flutter 架构和实现原理Flutter 使用 Dart 语言开发,主要有以下几点原因:Dart 一般情况下是运行 DartVM 上,但是也可以编译为 ARM 代码直接运行在硬件上。Dart 同时支持 AOT 和 JIT 两种编译方式,可以更好的提高开发以及 App 的执行效率。Dart 可以利用独特的隔离区(Isolate)实现多线程。而且不共享内存,可以实现无锁快速分配。分代垃圾回收,非常适合 UI 框架中常见的大量 Widgets 对象创建和销毁的优化。在为创建的对象分配内存时,Dart 是在现有的堆上移动指针,保证内存的增长是程线性的,于是就省了查找可用内存的过程。Dart 主要由 Google 负责开发和维护。目前 Dart 最新版本已经是 2.2,针对 App 和 Web 开发做了很多优化。并且对于大多数的开发者而言,Dart 的学习成本非常低。Flutter 架构也是采用的分层设计。从下到上依次为:Embedder(嵌入器)、Engine、Framework。<center>图 1: Flutter 分层架构图</center>Embedder是嵌入层,做好这一层的适配 Flutter 基本可以嵌入到任何平台上去; Engine层主要包含 Skia、Dart 和 Text。Skia 是开源的二位图形库;Dart 部分主要包括 runtime、Garbage Collection、编译模式支持等;Text 是文本渲染。Framework在最上层。我们的应用围绕 Framework 层来构建,因此也是本文要介绍的重点。Framework1.【Foundation】在最底层,主要定义底层工具类和方法,以提供给其他层使用。2.【Animation】是动画相关的类,可以基于此创建补间动画(Tween Animation)和物理原理动画(Physics-based Animation),类似 Android 的 ValueAnimator 和 iOS 的 Core Animation。3.【Painting】封装了 Flutter Engine 提供的绘制接口,例如绘制缩放图像、插值生成阴影、绘制盒模型边框等。4.【Gesture】提供处理手势识别和交互的功能。5.【Rendering】是框架中的渲染库。控件的渲染主要包括三个阶段:布局(Layout)、绘制(Paint)、合成(Composite)。从下图可以看到,Flutter 流水线包括 7 个步骤。<center>图 2: Flutter 流水线</center>首先是获取到用户的操作,然后你的应用会因此显示一些动画,接着 Flutter 开始构建 Widget 对象。Widget 对象构建完成后进入渲染阶段,这个阶段主要包括三步:布局元素:决定页面元素在屏幕上的位置和大小;绘制阶段:将页面元素绘制成它们应有的样式;合成阶段:按照绘制规则将之前两个步骤的产物组合在一起。最后的光栅化由 Engine 层来完成。在渲染阶段,控件树(widget)会转换成对应的渲染对象(RenderObject)树,在 Rendering 层进行布局和绘制。在布局时 Flutter 深度优先遍历渲染对象树。数据流的传递方式是从上到下传递约束,从下到上传递大小。也就是说,父节点会将自己的约束传递给子节点,子节点根据接收到的约束来计算自己的大小,然后将自己的尺寸返回给父节点。整个过程中,位置信息由父节点来控制,子节点并不关心自己所在的位置,而父节点也不关心子节点具体长什么样子。<center>图 3: 数据流传递方式</center>为了防止因子节点发生变化而导致的整个控件树重绘,Flutter 加入了一个机制——Relayout Boundary,在一些特定的情形下 Relayout Boundary 会被自动创建,不需要开发者手动添加。例如,控件被设置了固定大小(tight constraint)、控件忽略所有子视图尺寸对自己的影响、控件自动占满父控件所提供的空间等等。很好理解,就是控件大小不会影响其他控件时,就没必要重新布局整个控件树。有了这个机制后,无论子树发生什么样的变化,处理范围都只在子树上。<center>图 4: Relayout Boundary 机制</center>在确定每个空间的位置和大小之后,就进入绘制阶段。绘制节点的时候也是深度遍历绘制节点树,然后把不同的 RenderObject 绘制到不同的图层上。这时有可能出现一种特殊情况,如下图所示节点 2 在绘制子节点 4 时,由于其节点 4 需要单独绘制到一个图层上(如 video),因此绿色图层上面多了个黄色的图层。之后再需要绘制其他内容(标记 5)就需要再增加一个图层(红色)。再接下来要绘制节点 1 的右子树(标记 6),也会被绘制到红色图层上。所以如果 2 号节点发生改变就会改变红色图层上的内容,因此也影响到了毫不相干的 6 号节点。<center>图 5: 绘制节点与图层的关系</center>为了避免这种情况,Flutter 的设计者这里基于 Relayout Boundary 的思想增加了Repaint Boundary。在绘制页面时候如果遇见 Repaint Boundary 就会强制切换图层。如下图所示,在从上到下遍历控件树遇到 Repaint Boundary 会重新绘制到新的图层(深蓝色),在从下到上返回的时候又遇到 Repaint Boundary,于是又增加一个新的图层(浅蓝色)。<center>图 6: Repaint Boundary 机制</center>这样,即使发生重绘也不会对其他子树产生影响。比如在 Scrollview 上,当滚动的时候发生内容重绘,如果在 Scrollview 以外的地方不需要重绘就可以使用 Repaint Boundary。Repaint Boundary 并不会像 Relayout Boundary 一样自动生成,而是需要我们自己来加入到控件树中。6.【Widget】控件层。所有控件的基类都是 Widget,Widget 的数据都是只读的, 不能改变。所以每次需要更新页面时都需要重新创建一个新的控件树。每一个 Widget 会通过一个 RenderObjectElement 对应到一个渲染节点(RenderObject),可以简单理解为 Widget 中只存储了页面元素的信息,而真正负责布局、渲染的是 RenderObject。在页面更新重新生成控件树时,RenderObjectElement 树会尽量保持重用。由于 RenderObjectElement 持有对应的 RenderObject,所有 RenderObject 树也会尽可能的被重用。如图所示就是三棵树之间的关系。在这张图里我们把形状当做渲染节点的类型,颜色是它的属性,即形状不同就是不同的渲染节点,而颜色不同只是同一对象的属性的不同。<center>图 7:Widget、Element 和 Render 之间的关系</center>如果想把方形的颜色换成黄色,将圆形的颜色变成红色,由于控件是不能被修改的,需要重新生成两个新的控件 Rectangle yellow 和 Circle red。由于只是修改了颜色属性,所以 Element 和 RenderObject 都被重用,而之前的控件树会被释放回收。<center>图 8: 示例</center>那么如果把红色圆形变成三角形又会怎样呢?由于这里发生变化的是类型,所以对应的 Element 节点和 RenderObject 节点都需要重新创建。但是由于黄色方形没有发生改变,所以其对应的 Element 节点和 RenderObject 节点没有发生变化。<center>图 9: 示例</center>7. 最后是【Material】 & 【Cupertino】,这是在 Widget 层之上框架为开发者提供的基于两套设计语言实现的 UI 控件,可以帮助我们的 App 在不同平台上提供接近原生的用户体验。Flutter 在马蜂窝商家端App 中的应用实践<center>图 10: 马蜂窝商家端使用 Flutter 开发的页面</center>开发方式:Flutter + Native由于商家端已经是一款成熟的 App,不可能创建一个新的 Flutter 工程全部重新开发,因此我们选择 Native 与 Flutter 混编的方案来实现。 在了解 Native 与 Flutter 混编方案前,首先我们需要了解在 Flutter 工程中,通常有以下 4 种工程类型:1. Flutter Application标准的 Flutter App 工程,包含标准的 Dart 层与 Native 平台层。2. Flutter ModuleFlutter 组件工程,仅包含 Dart 层实现,Native 平台层子工程为通过 Flutter 自动生成的隐藏工程(.ios /.android)。3. Flutter PluginFlutter 平台插件工程,包含 Dart 层与 Native 平台层的实现。4. Flutter PackageFlutter 纯 Dart 插件工程,仅包含 Dart 层的实现,往往定义一些公共 Widget。了解了 Flutter 工程类型后,我们来看下官方提供的一种混编方案(https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps),即在现有工程下创建Flutter Module 工程,以本地依赖的方式集成到现有的 Native 工程中。官方集成方案(以 iOS 为例)a. 在工程目录创建 FlutterModule,创建后,工程目录大致如下:b. 在 Podfile 文件中添加以下代码:flutter_application_path = ‘../flutter_Moudule/‘该脚本主要负责:pod 引入 Flutter.Framework 以及 FlutterPluginRegistrant 注册入口pod 引入 Flutter 第三方 plugin在每一个 pod 库的配置文件中写入对 Generated.xcconfig 文件的导入修改 pod 库的 ENABLE_BITCODE = NO(因为 Flutter 现在不支持 bitcode)c. 在 iOS 构建阶段 Build Phases 中注入构建时需要执行的 xcode_backend.sh (位于 FlutterSDK/packages/flutter_tools/bin) 脚本:"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build该脚本主要负责:构建 App.framework 以及 Flutter.framework 产物根据编译模式(debug/profile/release)导入对应的产物编译 flutter_asset 资源把以上产物 copy 到对应的构建产物中d. 与 Native 通信方案一:改造 AppDelegate 继承自 FlutterAppDelegate方案二:AppDelegate 实现 FlutterAppLifeCycleProvider 协议,生命周期由 FlutterPluginAppLifeCycleDelegate 传递给 Flutter以上就是官方提供的集成方案。我们最终没有选择此方案的原因,是它直接依赖于 FlutterModule 工程以及 Flutter 环境,使 Native 开发同学无法脱离 Flutter 环境开发,影响正常的开发流程,团队合作成本较大;而且会影响正常的打包流程。(目前 Flutter 团队正在重构嵌入 Native 工程的方式)最终我们选择另一种方案来解决以上的问题:远端依赖产物。<center>图 11 :远端依赖产物</center>iOS 集成方案通过对官方混编方案的研究,我们了解到 iOS 工程最终依赖的其实是 FlutterModule 工程构建出的产物(Framework,Asset,Plugin),只需将产物导出并 push 到远端仓库,iOS 工程通过远端依赖产物即可。依赖产物目录结构如下:App.framework: Flutter 工程产物(包含 Flutter 工程的代码,Debug 模式下它是个空壳,代码在 flutter_assets 中)。Flutter.framework:Flutter 引擎库。与编译模式(debug/profile/release)以及 CPU 架构(arm*, i386, x86_64)相匹配。lib.a & .h 头文件: FlutterPlugin 静态库(包含在 iOS 端的实现)。flutter_assets: 包含 Flutter 工程字体,图片等资源。在 Flutter1.2 版本中,被打包到 App.framework 中。Android 集成方案Android Nativite 集成是通过 Gradle 远程依赖 Flutter 工程产物的方式完成的,以下是具体的集成流程。a.创建 Flutter 标准工程$ flutter create flutter_demo默认使用 Java 代码,如果增加 Kotlin 支持,使用如下命令:$ flutter create -a kotlin flutter_demob.修改工程的默认配置修改 app module 工程的 build.gradle 配置 apply plugin: ‘com.android.application’ => apply plugin: ‘com.android.library’,并移除 applicationId 配置修改 root 工程的 build.gradle 配置在集成过程中 Flutter 依赖了三方 Plugins 后,遇到 Plugins 的代码没有被打进 Library 中的问题。通过以下配置解决(这种方式略显粗暴,后续的优化方案正在调研)。subprojects { project.buildDir = “${rootProject.buildDir}/app”}app module 增加 maven 打包配置c. 生成 Android Flutter 产物$ cd android$ ./gradlew uploadArchives官方默认的构建脚本在 Flutter 1.0.0 版本存在 Bug——最终的产物中会缺少 flutter_shared/icudtl.dat 文件,导致 App Crash。目前的解决方式是将这个文件复制到工程的 assets 下(在 Flutter 最新 1.2.1 版本中这个 Bug 已被修复,但是 1.2.1 版本又出现了一个 UI 渲染的问题,所以只能继续使用 1.0.0 版本)。d.Android Native 平台工程集成,增加下面依赖配置即可,不会影响 Native 平台开发的同学implementation ‘com.mfw.app:MerchantFlutter:0.0.5-beta’Flutter 和 iOS、Android 的交互使用平台通道(Platform Channels)在 Flutter 工程和宿主(Native 工程)之间传递消息,主要是通过 MethodChannel 进行方法的调用,如下图所示:<center>图 12 :Flutter 与 iOS、Android 交互</center>为了确保用户界面不会挂起,消息和响应是异步传递的,需要用 async 修饰方法,await 修饰调用语句。Flutter 工程和宿主工程通过在 Channel 构造函数中传递 Channel 名称进行关联。单个应用中使用的所有 Channel 名称必须是唯一的; 可以在 Channel 名称前加一个唯一的「域名前缀」。Flutter 与 Native 性能对比我们分别使用 Native 和 Flutter 开发了两个列表页,以下是页面效果和性能对比:iOS 对比(机型 6P 系统 10.3.3):Flutter 页面:iOS Native 页面:可以看到,从使用和直观感受都没有太大的差别。于是我们采集了一些其他方面的数据。Flutter 页面:iOS Native 页面:另外我们还对比了商家端接入 Flutter 前后包体积的大小:39Mb → 44MB在 iOS 机型上,流畅度上没有什么差异。从数值上来看,Flutter 在 内存跟 GPU/CPU 使用率上比原生略高。Demo 中并没有对 Flutter 做更多的优化,可以看出 Flutter 整体来说还是可以做出接近于原生的页面。下面是 Flutter 与 Android 的性能对比。Flutter 页面:Android Native 页面:从以上两张对比图可以看出,不考虑其他因素,单纯从性能角度来说,原生要优于 Flutter,但是差距并不大,而且 Flutter 具有的跨平台开发和热重载等特点极大地节省了开发效率。并且,未来的热修复特性更是值得期待。混合栈管理首先先介绍下 Flutter 路由的管理:Flutter 管理页面有两个概念:Route 和 Navigator。Navigator 是一个路由管理的 Widget(Flutter 中万物皆 Widget),它通过一个栈来管理一个路由 Widget 集合。通常当前屏幕显示的页面就是栈顶的路由。路由 (Route) 在移动开发中通常指页面(Page),这跟 web 开发中单页应用的 Route 概念意义是相同的,Route 在 Android 中通常指一个 Activity,在 iOS 中指一个 ViewController。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是 Android 还是 iOS,导航管理都会维护一个路由栈,路由入栈 (push) 操作对应打开一个新页面,路由出栈 (pop) 操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。<center>图 14 :Flutter 路由管理</center>如果是纯 Flutter 工程,页面栈无需我们进行管理,但是引入到 Native 工程内,就需要考虑如何管理混合栈。并且需要解决以下几个问题:1. 保证 Flutter 页面与 Native 页面之间的跳转从用户体验上没有任何差异2. 页面资源化(马蜂窝特有的业务逻辑)3. 保证生命周期完整性,处理相关打点事件上报4. 资源性能问题参考了业界内的解决方法,以及项目自身的实际场景,我们选择类似于 H5 在 Navite 中嵌入的方式,统一通过 openURL 跳转到一个 Native 页面(FlutterContainerVC),Native 页面通过 addChildViewController 方式添加 FlutterViewController(负责 Flutter 页面渲染),同时通过 channel 同步 Native 页面与 Flutter 页面。每一次的 push/pop 由 Native 发起,同时通过 channel 保持 Native 与 Flutter 页面同步——在 Native 中跳转 Flutter 页面与跳转原生无差异一个 Flutter 页面对应一个 Native 页面(FlutterContainerVC)——解决页面资源化FlutterContainerVC 通过 addChildViewController 对单例 FlutterViewController 进行复用——保证生命周期完整性,处理相关打点事件上报由于每一个 FlutterViewController(提供 Flutter 视图的实现)会启动三个线程,分别是 UI 线程、GPU 线程和 IO 线程,使用单例 FlutterViewController 可以减少对资源的占用——解决资源性能问题Flutter 应用总结Flutter 一经发布就很受关注,除了 iOS 和 Android 的开发者,很多前端工程师也都非常看好 Flutter 未来的发展前景。相信也有很多公司的团队已经投入到研究和实践中了。不过 Flutter 也有很多不足的地方,值得我们注意:虽然 1.2 版本已经发布,但是目前没有达到完全稳定状态,1.2 发布完了就出现了控件渲染的问题。加上 Dart 语言生态小,学习资料可能不够丰富。关于动态化的支持,目前 Flutter 还不支持线上动态性。如果要在 Android 上实现动态性相对容易些,iOS 由于审核原因要实现动态性可能成本很高。Flutter 中目前拿来就用的能力只有 UI 控件和 Dart 本身提供能力,对于平台级别的能力还需要通过 channel 的方式来扩展。已有工程迁移比较复杂,以前沉淀的 UI 控件,需要重新再实现一套。最后一点比较有争议,Flutter 不会从程序中拆分出额外的模板或布局语言,如 JSX 或 XM L,也不需要单独的可视布局工具。有的人认为配合 HotReload 功能使用非常方便,但我们发现这样代码会有非常多的嵌套,阅读起来有些吃力。目前阿里的闲鱼开发团队已经将 Flutter 用于大型实践,并应用在了比较重要的场景(如产品详情页),为后来者提供了良好的借鉴。马蜂窝的移动客户端团队关于 Flutter 的探索才刚刚起步,前面还有很多的问题需要我们一点一点去解决。不过无论从 Google 对其的重视程度,还是我们从实践中看到的这些优点,都让我们对 Flutter 充满信心,也希望在未来我们可以利用它创造更多的价值和奇迹。路途虽远,犹可期许。本文作者:马蜂窝电商研发客户端团队。(马蜂窝技术原创内容,转载务必注明出处保存文末二维码图片,禁止商业用途,谢谢配合。)参考文献:Flutter’s Layered Designhttps://www.youtube.com/watch?v=dkyY9WCGMi0 Flutter’s Rendering Pipelinehttps://www.youtube.com/watch?v=UUfXWzp0-DU&t=1955s Flutter 原理与美团的实践https://juejin.im/post/5b6d59476fb9a04fe91aa778#comment 关注马蜂窝技术,找到更多你想要的内容 ...

March 26, 2019 · 4 min · jiezi

Flutter UI APP 低调上线

项目仓库 目前项目组件还在不断更新中…… 当github仓库更新后 教程会实时更新到app里面 apk下载 欢迎对组件疑问提出你的issue项目背景在经历了REACT NATIVE 弃用潮的大环境里,越来越多人寻找着可替代方案 Flutter 应该是目前比较热门的一项技术 —— 高效的开发效率,一套代码可以支持 Android/iOS 双端运行,Google 新的操作系统 Fuchsia 的默认 UI Toolkit 等等,都吸引了开发者社区大量的关注。 目前中国环境使用Flutter开发的案例并不多,作为一支尝鲜的团队,我们利用flutter很好地推动项目快速运转,也整理了部分开发经验!项目优势从开发到开源整理项目课程,我们经历了几次的迭代,汇集了react的开发经验,架构出适合开源教程的开源App动态更新,我们通过md的更新PR 可以动态实现app内文档更新,可以持续动态更新而不需要重新发版多语言埋点,为了更好跟国际接轨,我们实现了通过JSON达到了多语言切换的效果多主题预埋点,我们通过统一管理样式的方式进行编码,方便后续进行多主题切换Scope Model数据管理应用,达到了UI,数据,控制分离的目的预览最后感谢前期Efox 团队在完成项目的同时能够牺牲休息时间对项目作出的贡献 感谢我们的用户,感谢每一个提issue和pr的人 感谢每一个帮助过我们的人 需要获取更多动态的同学,可以加到仓库QQ群一起交流 希望一起参与建设中文社区的同学可以查看开发者如何参与完善控件

March 18, 2019 · 1 min · jiezi

vol.1 Flutter Coding Dojo

Flutter 开发者的道场,练习基本招式。精选 Stack Overflow 网站 flutter、dart 标签下的常见问题,总结为实用、简短的招式。Flutter 发布以来,受到越来越多的开发者和组织关注和使用。快速上手开发,需要了解 Dart 语言特性、Flutter 框架 SDK 及 API、惯用法、异常处理、辅助开发的工具使用等…。隐藏调试模式横幅。 return MaterialApp( title: ‘Flutter Demo’, debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: ‘Flutter Demo Home Page’), );打印日志调试应用。import ‘package:flutter/foundation.dart’;print(’logs’);debugPrint(’logs’);当日志输出过多时,Android 系统可能会丢弃一些日志行,为了避免这种情况使用 Flutter foundation 库提供的 debugPrint() 方法。指定 App 用户界面可以显示的方向集[…]。SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);获取手机屏幕宽高。double width = MediaQuery.of(context).size.width;double height = MediaQuery.of(context).size.height;获取 App 可以显示的矩形边界,排除被系统 UI(状态栏)或硬件凹槽遮挡部分。EdgeInsets devicePadding = MediaQuery.of(context).padding;添加、显示图片资源。Image.asset(‘images/cat.png’)确保在 pubspec.yaml 文件中显式标识资源路径,否则将会抛出异常。flutter: assets: - images/cat.pngflutter: ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞══flutter: The following assertion was thrown resolving an image codec:flutter: Unable to load asset: assets/heart_icon.png注意:在 .yaml 类型的文件中,正确的空格缩进至关重要。导航到新页面(Route)然后返回。Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => MyPage());Navigator.pop(context);使用自定义 Widget 替换红屏 ErrorWidget。ErrorWidget.builder = (FlutterErrorDetails details) { return Container();}确保 setState() 方法不会在 dispose() 方法之后调用。if (this.mounted){ setState((){ //Your state change code goes here });}setState() 方法可能会抛出异常:throw FlutterError( ‘setState() called after dispose(): $this\n’ ‘This error happens if you call setState() on a State object for a widget that ’ ’no longer appears in the widget tree (e.g., whose parent widget no longer ’ ‘includes the widget in its build). This error can occur when code calls ’ ‘setState() from a timer or an animation callback. The preferred solution is ’ ’to cancel the timer or stop listening to the animation in the dispose() ’ ‘callback. Another solution is to check the “mounted” property of this ’ ‘object before calling setState() to ensure the object is still in the ’ ’tree.\n’ ‘This error might indicate a memory leak if setState() is being called ’ ‘because another object is retaining a reference to this State object ’ ‘after it has been removed from the tree. To avoid memory leaks, ’ ‘consider breaking the reference to this object during dispose().’ );Dart 单例设计模式,使用工厂构造函数。class Singleton { static final Singleton _singleton = Singleton._internal(); factory Singleton() { return _singleton; } Singleton._internal();} ...

March 16, 2019 · 2 min · jiezi

Flutter单屏启动动画介绍页面制作【附视频】

Flutter单屏启动动画介绍页面制作【附视频】本文为本人原创,效果:视频链接:https://www.bilibili.com/vide…这节课主要讲的是一个单屏的启动动画,其实很简单的,之前以为大家都会就没讲,然后有位小伙伴私聊我,说让我讲一下,因为很多软件用的都是单屏或者单屏下面还有跳过按钮倒计时数字啥的,这个大家随机应变应该会感觉很简单的,看完我的这些教程的朋友,那我就不说那么多了直接开始文字教程。main等东西就不说了,home里面写了个SingleScreen()然后我们就创建文件之后导入了,SingleScreen是一个动态的widget类,我们在里面就写个充满屏幕的图片就行了,用的图片获取方式是network,然后我们写了个初始化,里面有个倒计时,void initState() { super.initState(); conutDown(); }然后倒计时里面我们写了个延时的东西,里面的参数是转到新页面的方法void conutDown() { var _duration = Duration(seconds: 3); Future.delayed(_duration, newPage); }之后新页面的方法写的就是给他替换路由名字为/newPagevoid newPage() { Navigator.of(context).pushReplacementNamed(’/NewPage’); }之后我们的main.dart的materialApp就接收一个新的路由并写东西,就写了给他跳转到新页面routes: <String, WidgetBuilder> { ‘/NewPage’ : (context) => NewPage() },然后新页面就很简单了,就是我们的想跳转到的页面了,Scaffold( appBar: AppBar( title: Text(‘单屏介绍’), centerTitle: true, ), body: Center( child: Text( ‘新页面’, style: Theme.of(context).textTheme.display2, ), ), )大概就是介个样子啦,那我们就来把源码呈上来了:main.dartimport ‘package:flutter/material.dart’;import ‘single_screen.dart’;import ’new_page.dart’;void main() => runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: ‘SingleScreen’, theme: ThemeData( primaryColor: Colors.blue, ), home: SingleScreen(), routes: <String, WidgetBuilder> { ‘/NewPage’ : (context) => NewPage() }, ); }}single_screen.dartimport ‘package:flutter/material.dart’;import ‘package:flutter/widgets.dart’;import ‘dart:async’;class SingleScreen extends StatefulWidget { @override _SingleScreenState createState() => _SingleScreenState();}class _SingleScreenState extends State<SingleScreen> { @override Widget build(BuildContext context) { return Container( color: Colors.white, child: Image.network( ‘http://img.wxcha.com/file/201606/30/1978c43117.jpg', fit: BoxFit.cover, ), ); } void initState() { super.initState(); conutDown(); } void conutDown() { var _duration = Duration(seconds: 3); Future.delayed(_duration, newPage); } void newPage() { Navigator.of(context).pushReplacementNamed(’/NewPage’); }}new_page.dartimport ‘package:flutter/material.dart’;class NewPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(‘单屏介绍’), centerTitle: true, ), body: Center( child: Text( ‘新页面’, style: Theme.of(context).textTheme.display2, ), ), ); }}顺便给大家推荐个免费的实用例子课程,Flutter实用例子进阶课程 ...

March 14, 2019 · 1 min · jiezi

Flutter Widgets入门(一):MaterialApp 和 Scaffold

1 MaterialApp1.1 什么是MaterialApp?MaterialApp是我们使用 Flutter开发中最常用的符合Material Design设计理念的入口Widget。你可以将它类比成为网页中的<html></html>,且它自带路由、主题色,<title>等功能。1.2 MaterialApp的几个属性1.2.1 titleStrig类型,该属性会在Android应用管理器的App上方显示,对于iOS设备是没有效果的。如下面代码所示:import ‘package:flutter/material.dart’;void main() { runApp(MaterialApp( title: ‘一个Flutter应用’, home: Text(‘hello flutter’, style: TextStyle( color: Colors.white, decoration: TextDecoration.none))));}1.2.2 homeWidget类型,这是在应用程序正常启动时首先显示的Widget,除非指定了initialRoute。如果initialRoute显示失败,也该显示该Widget。需要注意的是, 如果你指定了home属性,则在routes的路由表中不允许出现/的命名路由。import ‘package:flutter/material.dart’;void main() { runApp(MaterialApp( title: ‘一个Flutter应用’, home: Center( child: Text(‘hello flutter’, style: TextStyle( color: Colors.white, decoration: TextDecoration.none)), )));}1.2.3 routesMap<String, WidgetBuilder>类型,是应用的顶级路由表。当我们再使用Navigator.pushNamed进行命名路由的跳转时,会在此路表中进行查找并跳转。如果你的应用程序只有一个页面,则无需使用routes,直接指定home对应的Widget即可。下面的例子中,定义了两个路由:/home和/detail,并使用GestureDetector定义了点击事件已实现路由跳转:import ‘package:flutter/material.dart’;void main() { runApp(MaterialApp(title: ‘一个Flutter应用’, home: HomePage(), routes: { ‘/home’: (BuildContext context) => HomePage(), ‘/detail’: (BuildContext context) => DetailPage() }));}class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { Navigator.pushNamed(context, ‘/detail’); }, child: Text(‘首页,点击跳转详情页’, style: TextStyle( fontSize: 20.0, color: Colors.white, decoration: TextDecoration.none)))); }}class DetailPage extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { Navigator.pushNamed(context, ‘/home’); }, child: Text(‘详情页,点击跳转首页’, style: TextStyle( fontSize: 20.0, color: Colors.white, decoration: TextDecoration.none)))); }}2 Scaffold2.1 什么是Scaffold?Scaffold通常被用作MaterialApp的子Widget,它会填充可用空间,占据整个窗口或设备屏幕。Scaffold提供了大多数应用程序都应该具备的功能,例如顶部的appBar,底部的bottomNavigationBar,隐藏的侧边栏drawer等。2.2 Scaffold的几个属性2.2.1 appBarPreferredSizeWidget类型,显示在Scaffold的顶部区域。import ‘package:flutter/material.dart’;void main() { runApp(MaterialApp( title: ‘一个Flutter应用’, home: Scaffold( appBar: AppBar( title: Text(‘首页’)) ) ) );}2.2.2 drawerWidget drawer类型,通常用来形成一个汉堡包按钮显示其侧边栏。import ‘package:flutter/material.dart’;void main() { runApp(MaterialApp( title: ‘一个Flutter应用’, home: Scaffold( appBar: AppBar(title: Text(‘首页’)), drawer: Drawer( child: Column( children: <Widget>[ DrawerItem(1, ‘列表1’), DrawerItem(2, ‘列表2’), DrawerItem(3, ‘列表3’), DrawerItem(4, ‘列表4’), DrawerItem(5, ‘列表5’) ], )))));}class DrawerItem extends StatelessWidget { final int id; final String name; DrawerItem(this.id, this.name); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, border: Border(bottom: BorderSide(width: 0.5, color: Color(0xFFd9d9d9))), ), height: 52.0, child: FlatButton( onPressed: () {}, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[Text(id.toString()), Text(’ - ‘), Text(name)], )), ); }}2.2.3 bottomNavigationBarWidget bottomNavigationBar类型,用户显示底部的tab栏,items必须大于2个。import ‘package:flutter/material.dart’;void main() { runApp(MaterialApp( title: ‘一个Flutter应用’, home: Scaffold( appBar: AppBar( title: Text(‘首页’), ), bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: 1, items: [ new BottomNavigationBarItem( icon: Icon(Icons.account_balance), title: Text(‘银行’)), new BottomNavigationBarItem( icon: Icon(Icons.contacts), title: Text(‘联系人’)), new BottomNavigationBarItem( icon: Icon(Icons.library_music), title: Text(‘音乐’)) ], ))));}2.2.4 bodyWidget类型,Scaffold的主题内容。import ‘package:flutter/material.dart’;void main() { runApp(MaterialApp( title: ‘一个Flutter应用’, home: Scaffold( appBar: AppBar( title: Text(‘首页’), ), bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: 1, items: [ new BottomNavigationBarItem( icon: Icon(Icons.account_balance), title: Text(‘银行’)), new BottomNavigationBarItem( icon: Icon(Icons.contacts), title: Text(‘联系人’)), new BottomNavigationBarItem( icon: Icon(Icons.library_music), title: Text(‘音乐’)) ], ), body: Center( child: Text(‘这是联系人页面’), ), )));} ...

March 9, 2019 · 2 min · jiezi

flutter安装开发环境-问题记录

按照文档快速开始https://flutterchina.club/set…问题:brew install –HEAD libimobiledevice配置Flutter中的ios环境时,执行brew install –HEAD libimobiledevice时,报异常^CError: Calling needs :cxx11 is disabled! There is no replacement.Please report this to the homebrew/core tap: /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/cmake.rb:23 卸载brew,重新安装;1、卸载brew,执行命令:/usr/bin/ruby -e “$(curl -fsSL https://raw.githubusercontent…)“2、安装brew,执行命令:/usr/bin/ruby -e “$(curl -fsSL https://raw.githubusercontent…)" 之后重新执行,brew install –HEAD libimobiledevice,就没有错误了;原因是因为升级mac系统导致brew部分功能不能使用的解决方案出自 https://blog.csdn.net/LXFX110…

February 27, 2019 · 1 min · jiezi

开发了个 Flipper 调试工具的 Flutter 版本 SDK,让 Flutter 应用调试起来更容易

最近一直在持续的学习 Flutter,但一直没有发现有好用的网络调试工具,也不想太想使用 Charles 这个工具,后来发现了Facebook Flipper 这个工具,所以花了几天时间做了个 Flutter 版的 Flipper SDK。期间碰到了一些问题但 Flipper 项目的人迅速的帮忙。这个库可以让你能够在 Flipper 上查看你的 Flutter 应用的网络请求及 Preferences 数据,相比之前我之前使用 print 来输出请求数据来说,实在是方便了好多,如果你也在用 Flutter 开发你的应用,不妨来试一下吧。特性Network inspectorShared preferences (and UserDefaults) inspector集成到你的项目必备条件开始之前确保你已安装:已安装 Flipper Desktop安装添加以下内容到包的 pubspec.yaml 文件中:dependencies: flutter_flipperkit: ^0.0.2根据示例更改项目的 ios/Podfile 文件:Flipper 目前需要的 platform 为 8.0+source ‘https://github.com/facebook/flipper.git'+source ‘https://github.com/CocoaPods/Specs'# Uncomment this line to define a global platform for your project-# platform :ios, ‘9.0’+platform :ios, ‘9.0’根据示例更改项目的 android/app/build.gradle 文件:Flipper 目前需要的 sdkVersion 为 28android {- compileSdkVersion 27+ compileSdkVersion 28 defaultConfig {- targetSdkVersion 27+ targetSdkVersion 28 }}您可以通过命令行安装软件包:$ flutter packages get快速集成添加下列代码到 lib/main.dart 文件:import ‘package:flutter_flipperkit/flutter_flipperkit.dart’;void main() { FlipperClient flipperClient = FlipperClient.getDefault(); // 添加网络插件 flipperClient.addPlugin(new FlipperNetworkPlugin()); // 添加 Preferences 插件 flipperClient.addPlugin(new FlipperSharedPreferencesPlugin()); flipperClient.start(); runApp(MyApp());}Dio 集成示例:import ‘dart:io’;import ‘package:dio/dio.dart’;import ‘package:flutter_flipperkit/flutter_flipperkit.dart’;import ‘package:uuid/uuid.dart’;class DioClient { Dio _http; FlipperNetworkPlugin _flipperNetworkPlugin; DioClient() { _flipperNetworkPlugin = FlipperClient .getDefault().getPlugin(FlipperNetworkPlugin.ID); Options options = new Options( connectTimeout: 5000, receiveTimeout: 3000, headers: { “Accept”: “application/json”, “Content-Type”: “application/json” }, responseType: ResponseType.JSON, ); this._http = new Dio(options); // 在拦截器中添加和 Flipper 通讯的代码 this._http.interceptor.request.onSend = (Options options) async { // 发送请求数据到 Flipper this._reportRequest(options); return options; }; this._http.interceptor.response.onSuccess = (Response response) { // 发送响应数据到 Flipper this._reportResponse(response); return response; }; } Dio get http { return _http; } void _reportRequest(Options options) { String requestId = new Uuid().v4(); options.extra.putIfAbsent(“requestId”, () => requestId); RequestInfo requestInfo = new RequestInfo( requestId: requestId, timeStamp: new DateTime.now().millisecondsSinceEpoch, uri: ‘${options.baseUrl}${options.path}’, headers: options.headers, method: options.method, body: options.data, ); _flipperNetworkPlugin.reportRequest(requestInfo); } void _reportResponse(Response response) { Map<String, dynamic> headers = new Map(); for (var key in [] ..addAll(HttpHeaders.entityHeaders) ..addAll(HttpHeaders.requestHeaders) ..addAll(HttpHeaders.responseHeaders) ) { var value = response.headers.value(key); if (value != null && value.isNotEmpty) { headers.putIfAbsent(key, () => value); } } String requestId = response.request.extra[‘requestId’]; ResponseInfo responseInfo = new ResponseInfo( requestId: requestId, timeStamp: new DateTime.now().millisecondsSinceEpoch, statusCode: response.statusCode, headers: headers, body: response.data, ); _flipperNetworkPlugin.reportResponse(responseInfo); }}Dio 使用示例new DioClient().http.get(‘https://www.v2ex.com/api/topics/hot.json');运行程序这时,集成已经完成,启用应用后可在 Flipper Desktop 上实时看到你的网络请求了$ flutter run已知问题Flipper Desktop 中文乱码,但已解决并提交 PR 给官方,暂时可以使用我修改的版本 https://github.com/lijy91/fli…暂不支持 iOS 真机探讨如果您对此项目有任何建议或疑问,可以通过 Telegram 或我的微信进行讨论。相关链接https://github.com/blankapp/f…https://github.com/facebook/f… ...

February 25, 2019 · 2 min · jiezi

如何用Dart写一个单例

由于Dart拥有factory constructors,因此构建单例模式很容易。class Singleton { static final Singleton _singleton = new Singleton._internal(); factory Singleton() { return _singleton; } Singleton._internal();}我们可以使用new来构造代码如下:main() { var s1 = new Singleton(); var s2 = new Singleton(); print(identical(s1, s2)); // true print(s1 == s2); // true}

February 18, 2019 · 1 min · jiezi

Flutter使用Cipher2插件实现AES加解密

Flutter是当下最流行的新兴APP跨平台开发架构。学习需趁早。因为我的项目需要使用AES加解密,而flutter package中并没有支持Dart 2的AES加密库,所以写了Cipher2插件并拿出来开源给大家用。本文介绍如何使用Cipher2插件在Flutter app中实现AES加密解密。本文不讲述如何安装配置Flutter开发环境。如有需要我会写关于安装配置flutter开环环境的文章。Cipher2插件地址:https://pub.dartlang.org/pack…https://github.com/shyandsy/c…各位如果有其他加密算法需求,请在github发issue,我会尽快跟进。PR is welcome!创建项目打开cmd命令行,cd命令定位到你想要创建项目的目录。然后创建flutter app项目flutter create cipher2_test用vscode打开项目目录安装Cipher2插件打开上图中pubspec.yaml文件的dependencies中,添加如下内容。然后ctrl + s保存,vscode会自动为你安装好插件。dependencies: flutter: sdk: flutter cipher2: anyCipher2的API解释Cipher2插件目前支持AES加密的cbc(128位 padding7)模式和gcm模式(128位)。插件提供了5个方法来实现加密解密。本插件所有字符串均使用UTF8编码。在处理第三方密文的时候,请注意字符串编码,以免不必要的麻烦。AES cbc 128位padding7加密/Cipher2.encryptAesCbc128Padding7参数: plainText: 被加密字符串 key:128 bit字符串 iv: 128 bit字符串返回: 经过base64编码的密文字符串/String encryptedString = await Cipher2.encryptAesCbc128Padding7(plainText, key, iv);AES cbc 128位padding7解密/Cipher2.decryptAesCbc128Padding7参数: encryptedString: base64编码的密文字符串 key:128 bit字符串 iv: 128 bit字符串返回: 明文字符串/String decryptedString = await Cipher2.decryptAesCbc128Padding7(encryptedString, key, iv);生成GCM模式的nonceString nonce = Cipher2.generateNonce()AES gcm 128位加密/Cipher2.encryptAesGcm128参数: plainText: 被加密字符串 key:128 bit字符串 nonce: based4编码的92bit nonce,可以用Cipher2.generateNonce()生成返回: 经过base64编码的密文字符串/String encryptedString = await Cipher2.encryptAesGcm128(plaintext, key, nonce);AES gcm 128位解密/Cipher2.decryptAesGcm128参数: encryptedString: base64编码的密文字符串 key:128 bit字符串 nonce: based4编码的92bit nonce,可以用Cipher2.generateNonce()生成返回: 明文字符串/result = await Cipher2.decryptAesGcm128(encryptedString, key, nonce);使用Cipher2插件官方提供了非常简单明了的测试用例,方便加密解密和异常捕获https://github.com/shyandsy/c…// Platform messages are asynchronous, so we initialize in an async method. Future<void> initPlatformState() async { String encryptedString; String plainText = ‘我是shyandsy,never give up man’; String key = ‘xxxxxxxxxxxxxxxx’; String iv = ‘yyyyyyyyyyyyyyyy’; String decryptedString; // 测试AES 128bit cbc padding 7加密 await testEncryptAesCbc128Padding7(); // 测试AES 128bit cbc padding 7解密 await testDecryptAesCbc128Padding7(); // 测试AES 128bit gcm加解密 await testEncryptAesGcm128(); // GenerateNonce(); // 加密,然后解密 try { // 加密 encryptedString = await Cipher2.encryptAesCbc128Padding7(plainText, key, iv); // 解密 decryptedString = await Cipher2.decryptAesCbc128Padding7(encryptedString, key, iv); } on PlatformException catch(e) { encryptedString = “”; decryptedString = “”; print(“exception code: " + e.code); print(“exception message: " + e.message); } // If the widget was removed from the tree while the asynchronous platform // message was in flight, we want to discard the reply rather than calling // setState to update our non-existent appearance. if (!mounted) return; setState(() { _plainText = plainText; _encryptedString = encryptedString; _decryptedString = decryptedString; }); }读一遍test case就会用了。这里不再重复 ...

February 1, 2019 · 2 min · jiezi

Flutter尝鲜3——动画处理<并行和串行>

本例的代码参考这里。并行动画当多个动画定义同时指向某个组件,并使用动画控制器启动时,就产生了并行动画(Parallel Animation)。例如我们可以让一个组件:移动的同时改变大小旋转的同时边界颜色闪烁圆形图片模糊的同时形状越来越方总之,掌握了动画原理以后我们知道,只要能将一个动画抽象值与一个组件的某个外观属性值联系起来,那么就能在动画中展现出连续平滑的外观变化。这一点,任何平台(Web、Android)的原理都是一致的。例子接前一篇的例子,我们让一个移动的正方形在位移过程中逐渐变为圆形。在已有的animation基础上,再添加一个新的animation用以控制动画组件的边角半径。class ParallelDemoState extends State<ParallelDemo> with SingleTickerProviderStateMixin { … Tween<double> slideTween = Tween(begin: 0.0, end: 200.0); Tween<double> borderTween = Tween(begin: 0.0, end: 40.0); // 添加边角半径变动范围 Animation<double> slideAnimation; Animation<double> borderAnimation; // 添加边角半径动画定义 @override void initState() { … controller = AnimationController(duration: Duration(milliseconds: 2000), vsync: this); slideAnimation = slideTween.animate(CurvedAnimation(parent: controller, curve: Curves.linear)); borderAnimation = borderTween.animate(CurvedAnimation(parent: controller, curve: Curves.linear)); // 定义边角半径动画 } … @override Widget build(BuildContext context) { return Container( width: 200, alignment: Alignment.centerLeft, child: Container( margin: EdgeInsets.only(left: slideAnimation.value), decoration: BoxDecoration( borderRadius: BorderRadius.circular(borderAnimation.value), // 边角半径的属性上添加动画 color: Colors.blue, ), width: 80, height: 80, ), ); }}串行动画串行动画(Sequential Animation)顾名思义,多个动画像肉串一样一个接一个的发生。但这只是从现象上观察出的结果,实际的运行方式和并行动画差别不大。串行动画的关键之处在于,它为每个动画的发生设定了一个计时器,只有到特定时间点时,特定的动画效果才会发生。例如设计一个3秒钟的动画:移动动画从0秒开始,持续1秒旋转动画从1秒开始,持续1.5秒缩放动画从2秒开始,持续0.7秒那么,最后的动画效果便是:01秒,动画元素在移动12秒,动画元素在旋转22.5秒,动画既在旋转又在缩放2.52.7秒,动画在缩放2.7~3秒,动画静止不动例子在串行动画例子的基础上,我们加上计时器Interval的处理。Interval有三个参数,前两个参数指示了动画的开始和结束时间。这两个参数都是以动画控制器的Duration时长的比例来计算的。例如:Slide动画分别为0.0和0.5,表示动画从0秒(2000ms 0.0)这个时间点开始,至1秒(2000ms 0.5)这个时间点结束Border动画分别为0.5和1.0,表示动画从1秒(2000ms 0.5)这个时间点开始,至2秒(2000ms 1.0)这个时间点结束class SequentialDemoState extends State<ParallelDemo> with SingleTickerProviderStateMixin { … @override void initState() { … controller = AnimationController(duration: Duration(milliseconds: 2000), vsync: this); // slideAnimation = slideTween.animate(CurvedAnimation(parent: controller, curve: Curves.linear)); // borderAnimation = borderTween.animate(CurvedAnimation(parent: controller, curve: Curves.linear)); // 定义边角半径动画 // 换一种写法,加入Interval slideAnimation = slideTween.animate(CurveTween(curve: Interval(0.0, 0.5, curve: Curves.linear)).animate(controller)); borderAnimation = borderTween.animate(CurveTween(curve: Interval(0.5, 1.0, curve: Curves.linear)).animate(controller)); } … @override Widget build(BuildContext context) { return Container( width: 200, alignment: Alignment.centerLeft, child: Container( margin: EdgeInsets.only(left: slideAnimation.value), decoration: BoxDecoration( borderRadius: BorderRadius.circular(borderAnimation.value), // 边角半径的属性上添加动画 color: Colors.blue, ), width: 80, height: 80, ), ); }} ...

January 31, 2019 · 1 min · jiezi

flutter常用组件API(第三期)

内容如果对你有帮助,帮忙点下赞,你的点赞是我更新最大的动力,谢谢啦!如果在开发的过程遇到问题可以一起讨论,可以加我的QQ群!167646174!具体代码见github ,欢迎各位Star,以及提issues!1.CheckBox(说实话,这个组件有点小丑)API作用可选参数value复选框值自定义activeColor选中的颜色Colortristate如果为 true,那么复选框的值可以是 true,false 或 nulltrue/falsematerialTapTargetSize点击区域padded:向四周扩展48区域 shrinkWrap:控件区域onChanged改变后触发事件Func2.CheckboxListTileAPI作用可选参数title标题-subtitle副标题-secondary前缀-selected文字是否高亮true/falsedense标题字变小true/falseisThreeLine是否三行显示(第三行用啥显示目前还没看到用法)true/falsecontrolAffinity将控件放在何处相对于文本,leading 按钮显示在文字前面,platform,trailing 按钮显示在文字后面-(未完待续)具体代码见github ,欢迎各位Star,以及提issues不定期更新,根据工作繁忙程度决定.——————————-以下是相关文章—————————-flutter常见组件(第一期)flutter常见组件之Button(第二期)

January 30, 2019 · 1 min · jiezi

Flutter尝鲜2——动画处理<基础>

本例的代码参考这里。概述动画处理的基本原理是,对组件(widget)的某个或某组属性设置一组连续变化的值,这些值在一定时间间隔内不断被应用到该属性上,使得组件的外观看上去在进行平滑而连续的变动。例如2秒内每隔0.1s将一个组件的x轴坐标加1,那么该组件看上去就是从左至右移动了2秒共20个单位。处理组成部分具体到Flutter,动画处理主要分为三个部分:动画控制器(AnimationController),控制整个动画运行,包括开始结束和动画时长等。动画抽象(Animation),描述了动画运动的速率,例如组件是加速还是匀速,或者其它变化。变动范围(Tween),定义了动画组件属性值的变化范围,例如从坐标(0, 0)移动到(20, 0)处理流程上述三大组件,控制了整个动画的运行。用文字描述,其流程主要包括:初始化动画控制器,设定动画的时长,初始值等(如上例:2秒时长)初始化变动范围(如上例:Offset从[0, 0]到[20, 0])初始化动画抽象,定义它的运动速率(如上例:匀速变动)将动画描述的值,赋值到动画组件的对应属性上开始执行动画(调用动画控制器的开始方法)动画执行结束AnimationController定义AnimationController是一个特殊的Animation对象。创建一个AnimationController时,需要传递一个vsync参数。设置此参数的目的,是希望屏幕每一帧画面变化时能够被通知到。也就是说,屏幕刷新的每一帧,AnimationController都会生成一个新的值(同样也意味着,如果在屏幕外那么就不被触发)。这样动画组件就能够完成一个连续平滑的动画动作。Tickers can be used by any object that wants to be notified whenever a frame triggers。AnimationControler通常是在一个StatefulWidget中被声明,并且附带一个叫做SingleTickerProviderStateMixin的Mixin(原因就在上面说的,要设置vsync参数)。class AnimationDemo extends StatefulWidget { AnimationDemoState createState() => AnimationDemoState();}class AnimationDemoState extends State<AnimationDemo> with SingleTickerProviderStateMixin { AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(duration: Duration(milliseconds: 2000), vsync: this); … } @override void dispose() { controller.dispose(); // 离开时需要销毁controller super.dispose(); } …}当Animation和Tween的设置完成后,简单调用controller.forward()即可开始动画。Tween定义Tween就是要改变的属性值的变动范围。它可以是任意的属性类如Offset或者Color,最常见的是double。… AnimationController controller; Tween<double> slideTween = Tween(begin: 0.0, end: 20.0);…Animation定义Animation对象本身可以看做是动画中所有变化值的一个集合。它包含了变化区间内的所有可取值,并返回给动画组件当前的变动值。Animation在使用中要设置的,是他的变动速率,如Curves.linear(线性变化)。… AnimationController controller; Tween<double> slideTween = Tween(begin: 0.0, end: 20.0); Animation<double> animation; @override void initState() { super.initState(); … animation = slideTween.animate(CurvedAnimation(parent: controller, curve: Curves.linear)); }…动画组件定义为了说明简单,在build方法中嵌套两个Container组件,外部容器Container的paddingLeft跟随动画变动,达到移动内部Container的目的。class AnimationDemoState extends State<AnimationDemo> with SingleTickerProviderStateMixin { … @override Widget build(BuildContext context) { return Container( width: 200, alignment: Alignment.centerLeft, padding: EdgeInsets.only(left: animation.value), child: Container( color: Colors.blue, width: 80, height: 80, ), ); }}启动动画在启动动画之前有一个Flutter的基本概念要说明。做过React的同学很清楚,要想render方法重新执行,要么props有更新要么state有更新。在Flutter也同样如此,build方法同样依赖于state的更新才能重新执行。在AnimationController的说明中,我们知道因为设置了vsync所以屏幕刷新的每一帧都会更新它的值。所以可以在Controller上加上一个listener,每次有update都调用一下setState,以此达到重新渲染UI的目的。… @override void initState() { … animation.addListener(() => this.setState(() {})); controller.repeat(); // 动画重复执行 }调用controller.repeat()方法,动画会被反复执行。如果想只执行一次,那么可以使用controller.forward(); ...

January 25, 2019 · 1 min · jiezi

flutter常见组件之Button(第二期)

内容如果对你有帮助,帮忙点下赞,你的点赞是我更新最大的动力,谢谢啦!如果在开发的过程遇到问题可以一起讨论,可以加我的QQ群!167646174!也可以加我微信,在群里!具体代码见github ,欢迎各位Star,以及提issues!1.RaisedButtonAPI作用参数color背景色-padding与文字的内边距-textColor按钮内文字颜色-textTheme按钮主题-disabledColor按钮被禁用显示的颜色-disabledTextColor按钮被禁用时文字显示颜色-highlightColor击高亮的时候显示在控件上面,水波纹下面的颜色-splashColor水波纹颜色-colorBrightness按钮主题高亮-elevation按钮下面的阴影-highlightElevation高亮时候的阴影-disabledElevation按下时候的阴影clipBehavior抗锯齿能力-onHighlightChanged水波纹高亮时候回调-onPressed点击事件-shape拓展样式_icon小图标按钮只有IconButton才会使用到—扩展—1.1带斜角的按钮shape: BeveledRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20))),1.2圆按钮shape: CircleBorder( // 圆边颜色 side: BorderSide( color: Colors.black )),1.3圆角矩形按钮 shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)) ),1.4两端半圆按钮shape: StadiumBorder(),2.OutlineButtonAPI同RaisedButton默认边线且背景透明的按钮3.FlatButtonAPI同RaisedButton4.ButtonBarAPI作用参数alignment对齐方式-mainAxisSize主轴大小,默认MainAxisSize.max-5.FloatingActionButtonAPI作用backgroundColor背景色elevation未点击的阴影值highlightElevation点击时的阴影值tooltip长按文字提示foregroundColor按钮里面文字小图标颜色具体代码见github ,欢迎各位Star,以及提issues!不定期更新,根据工作繁忙度决定!以下是往期相关文章:flutter常见组件API(第一期)

January 25, 2019 · 1 min · jiezi

Flutter尝鲜1——3步骤使用自定义Icon

官方IconFlutter本身自带了MaterialDesign的图标集,在pubspec.yaml中有如下配置…flutter: users-material-design: true…通过以上配置,就可以在代码中引用任何MD的官方图标(需翻墙)。这些图片都定义在了IconDatas中。Icon(Icons.favorite)第三方Icon第三方图标库和MD的图片库在使用上没有区别,但需要手动引入和配置路径。为了方便复用,我们可以把图标制作为一个第三方库来调用。例如:…import ‘package:my_icon/my_icon.dart’;…Icon icon = Icon(MyIcon.zhihu); # 知乎LOGO制作Icon库1.制作ttf文件一般我们会在iconfont.cn上去寻找合适的图标集或自行绘制,完成后打包下载,压缩包里有制作好的ttf文件。2.编写配置文件作为示例,在/lib目录下创建一个名为my_font的文件夹,文件夹中的pubspec.yaml内容如下:name: my_fontdescription: The font for my applicationauthor: Lynx <lynx86@126.com>homepage: http://www.a-lightyear.com/version: 1.0.0environment: sdk: “>=2.0.0-dev.28.0 <3.0.0"dependencies: flutter: sdk: flutterdev_dependencies: recase: “^2.0.0+1"flutter: fonts: - family: MyIcon fonts: - asset: lib/fonts/iconfont.ttf weight: 400从配置文件看出,iconfont下载的ttf文件放在/lib/my_font/lib/fonts/下面,该路径可以自行设置。3.编写库文件library font_social_flutter; import ‘package:flutter/widgets.dart’;class MyIcon { static const IconData zhihu = const _MyIconData(0xe6a2); static const IconData wechat = const _MyIconData(0xe697); static const IconData alipay = const _MyIconData(0xe698); static const IconData weibo = const _MyIconData(0xe6ab); static const IconData wechat_friends = const _MyIconData(0xe6ae); static const IconData qq = const _MyIconData(0xe6ac);}class _MyIconData extends IconData { const _MyIconData(int codePoint) : super( codePoint, fontFamily: ‘MyIcon’, fontPackage: ‘my_icon’, );}这里的0xe6a2即为每个Icon的unicode字符。在iconfont下载包里有一个html文件,打开后可以看到每个图片的unicode值。使用Icon引入Icon库在使用之前,需要把该库引入到当前flutter工程中。编辑flutter项目的pubspec.yaml,添加如下内容:…dependencies: flutter: sdk: flutter … my_icon: path: lib/my_icon/ # 在这里引入第三方icon库 ……使用Icon如开篇所述,在做好以上准备工作后,即可以如MD图标一般方便的引入自制的图标集。…import ‘package:my_icon/my_icon.dart’;…Icon icon = Icon(MyIcon.zhihu); # 知乎LOGO ...

January 22, 2019 · 1 min · jiezi

mac上搭建flutter开发环境并运行第一个程序

什么是flutter官方是这么解释的:Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。(闲鱼APP就是用的flutter)一、安装flutter#切换到准备安装flutter的目录cd project#有两种方法安装flutter SDK#1、使用git clonegit clone -b beta https://github.com/flutter/flutter.git#2、直接在github下载压缩包,下载地址https://github.com/flutter/flutter/releases#在目录下解压zip文件#配置环境export PATH=pwd/flutter/bin:$PATH #如果下载太慢或者失败,那么需要先配置中国镜像,然后再clone项目export PUB_HOSTED_URL=https://pub.flutter-io.cnexport FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn#切换到项目目录cd ./flutterflutter doctor不出意外的话,应该会报错,提示你安装android studio、Xcode、install dart和flutter插件等。按照提示逐个安装就行。需要注意的是,你可能会遇见pod setup这个步骤,但是却一直卡着进度条不动,快速的解决办法是,打开手机热点,mac连接手机的热点进行下载安装,5分钟内能够安装好(大小应该在500多M)二、安装android studio及插件android studio 下载地址打开android studio, 打开plugin输入flutter搜索,点击中间的 Search in repositories点击install,顺利的话安装完毕之后重启android studio三、运行第一个项目新建一个Flutter打开android studio后 会看到可选项多了一个 Start a new Flutter project创建成功后在终端中输入open -a Simulator则可以启动ios模拟器,然后在android studio 控制台中输入 flutter run 就能够看到安卓真机和ios模拟器了flutter run -d <设备id>就能够启动对应的平台了如我这里启动ios模拟器就输入flutter run -d B21和运行android项目一样的操作流程,连接安卓真机后在手机上能看到默认的项目

January 21, 2019 · 1 min · jiezi

flutter常见组件API(持续更新)

ListTile效果如上: new ListTile( // 前缀 leading:Icon(Icons.navigation), // 标题 title: Text(“导航栏组件”,style:TextStyle(fontWeight:FontWeight.w500)), // 副标题 subtitle: Text(“常见的底部导航栏组件”), // 后缀 trailing: Icon(Icons.chevron_right), // 点击事件 onTap: (){ Navigator.push( context, MaterialPageRoute( builder:(context)=>new BottomNavigation() ) ); }, // 长按事件 onLongPress: (){ print(“object”); } )

January 17, 2019 · 1 min · jiezi

Flutter系列:3.APP基础设施搭建

前言在上一篇文章Flutter系列:2.实现一个简单的登录界面通过一个简单的登录页面带入了Flutter中页面构建的方式以及一些简单控件的使用;在开发一个app前首要的任务往往是搭建app需要的基础结构,比如底部菜单,路由导航,网络请求以及一些常用的颜色、图标、按钮、toast组件等。本次的demo将实现一个简单的app所需的基础结构,实现一个简单的app,基于底部TabBar的方式模块切分,实现网络层调用豆瓣api展示电影列表,任意界面登录验证,app如下图。[GitHub源码传送]TabBar菜单目前app设计中大部分app都是由底部TabBar菜单+顶部导航信息的方式构建的,在iOS开发中UITabBarController 和 UINavigationController 几乎是APP的标配, 同样在Flutter中基于Scaffold的构建方式也直接提供了appBar+body+bottomNavigationBar的方式来切分导航栏、内容和底部菜单,所以我们只需要在首页的Scaffold构造中传入bottomNavigationBar即可。在Flutter中为我们提供了material design风格的BottomNavigationBar和iOS风格的CupertinoTabBar,我们只需要选择其一稍作封装即可,本demo选择CupertinoTabBar,并封装到BottomNavWidget中,相关细节请看源码。body的切换虽然Scaffold提供了appBar+body+bottomNavigationBar的组合,但是并没有实现bottomNavigationBar点击切换body页面显示功能,所以需要开发者自己去处理bottomNavigationBar的点击回调来动态切换body中的内容。不同的bottomNavigationBarItem对应着不同的显示页面,电影tabBar对应显示电影列表页面,发现tabBar对应显示发现页面… 他们都在body中,他们之间有着频繁的切换但同时只能显示一个页面;基于此使用Stack布局的方式来实现,每个页面组成一个数组成为Stack Widget的children并缓存避免重复创建,使用Offstage组件来包装每个tab页面,并将bottomNavigationBar当前选择的index对应的页面的offstage设置为false, 这样只有当前选择的tab对应的页面显示在body中,而其他的界面并不会显示也不会接收事件占用空间。路由导航路由导航也是app常见的基础功能,服务器通过下发路由信息可以实现动态的控制app的页面跳转,常用于动态页面,push和web跳转。Flutter中的导航有点类似iOS的方式,都是通过栈的方式来管理路由页面。Navigator就是Flutter中管理导航路线的Widget,注意Navigator管理的是页面导航的路线,称为Route的东西而不是像iOS中直接管理的controller,而每个Route(CupertinoPageRoute)则可以通过builder来指定显示的Widget,同时Navigator也提供了对Route 栈操作的方法,push和pop。Navigator管理的对象是Route,Flutter提供了MaterialPageRoute和iOS风格的CupertinoPageRoute,MaterialPageRoute是根据手机平台自动调整页面的出现动画,本Demo选用CupertinoPageRoute以从右到左的页面出现动画,然后指定其builder即可实现页面的跳转。MaterialApp内置了一个顶层的导航器Navigator,routes属性支持配置静态的路由表,如果在routes中找不到对应的路由配置时则调用onGenerateRoute来支持动态的路由跳转,它的定义如下:所以我们需要通过一个函数来实现MaterialApp的onGenerateRoute就可以根据RouteSettings中的路由信息动态的生成页面的Route,同时以Uri的方式来指定Route的名称就可以实现动态传参了,具体详见Demo源码中RouteManager类。登录注册登录注册页面可能在app的任何页面推出,同时可能不支持返回需要强制登录的情况,在iOS中常常以present的方式出现,所以在Flutter中需要指定CupertinoPageRoute的fullscreenDialog属性为true即可页面的跳转在iOS的开发中基于UITabBarController 和 UINavigationController的构建方式中页面跳转是在UINavigationController内跳转的,同时通过设置Controller的hidesBottomBarWhenPushed属性支持动态的显示和隐藏底部的TarBar, 每个TabBar对应的是一个独立控制的UINavigationController,他们各自有自己路由的导航栈,在Flutter中提供的CupertinoTabScaffold通过为每个TabBar指定显示为CupertinoTabView来实现了同样的机制。往往在开发中进入二级界面后底部的导航栏都是隐藏的,所以我们完全可以只使用MaterialApp内置的顶层Navigator来实现我们的导航控制,本Demo也是如此。网络请求移动端的网络环境是千变万化的,所以app的网络请求应该是一个异步的过程,不能阻塞主线程,本Demo是基于Dart的第三方Http网络请求库dio。dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等…网络层实现了通过dio请求到网络数据然后反序列为Model对象,dart中的json反序列化要比其他语言麻烦,借助的是json_annotation这个库。从请求api到回调再到反序列为Model对象这个过程都应该是一个异步的过程,所以他们返回的都是一个Futrure对象,使用Completer就可以很方便的生成一个Future, 然后在恰当的时候传入数据或者错误来结束这个Future。列表列表的展示是基于FutureBuilder的方式,因为其依赖api请求返回的future,当future的状态变更时FutureBuilder会接收到最新的快照信息AsyncSnapshot,通过其当前快照来控制ListView或者CircularProgressIndicator的显示。其他App开发中还有许多其他的基础模块,比如和原生通信组件(channel)、图片组件、日志组件、其他公共的弹窗、上下拉刷新组件等,本Demo还来不及一一实现,随着学习的深入以后再慢慢总结吧,有不妥的地方还望指正。Demo源码地址:[GitHub源码传送]

January 10, 2019 · 1 min · jiezi

NATS的Dart客户端

作者:Chaitanya Munukutla记得是在2015年初,我第一次听到消息经纪人这个词。我正在攻读硕士学位,关于P2P网络,需要模拟稳定吞吐量的传入消息。那时我不是一个极客,所以解决方法是做一个无限的Thread.sleep()循环。不要不满,那时我才21岁。RabbitMQ是我与消息传递系统的第一次约会。我对这类关系很陌生,并没有太多可比性,所以RabbitMQ似乎很好。但是,自己托管它有点痛苦,而且我也厌倦了Java。那是我找到PubNub的时候,上帝是美丽的。我不需要处理任何事情,几乎所有你使用的环境都有驱动。并且,他们有一个免费等级可用,这是我透露我来自印度的时候,我们比任何人都更好地使用免费资源。免责声明:我没有试图去找NATS,这是一个意外。我看到了这个新的基金会,云原生计算基金会,它似乎是街头新的嘻哈孩子,所以我前往cncf.io,看看他们是否有他们吹嘘的东西。然后,我遇到这个名为NATS的新孵化项目。他们称之为“为微服务而设的消息传递”。它有类似于RabbitMQ、ActiveMQ、Pubnub等的消息代理抽象。那么我为什么要切换?为何选择NATS?惯用法。我一直在寻找令人兴奋的新语言,这些语言似乎无处不在。我试过Golang、Swift、Kotlin、Python、C#和Erlang(是的,我说过是真的)。新语言似乎带来了他们各自对编程的惯用方式。NATS似乎通过简单直接的驱动程序,带来了原生方式。NATS帮助我学习Golang频道、RxJava和Erlang主管模型。强大功能。NATS功能强大。它优雅地处理pub-sub、请求-响应、排队和流媒体,甚至可以同时处理!简单部署。我不能夸大这一点,NATS很容易部署和管理。只需在我的终端上运行./gnatsd即可!极其省资源。我已经对NATS进行了基准测试,它从未占用超过150MB的RAM来为单个主机上的100万有效负载流入提供服务。仅供参考,单个谷歌Chrome浏览器所需更高。惊人的表现。如果你到目前为止还没有使用NATS,请停止阅读并下载最新的二进制文件并尝试一下。试用PC可承受的最大负载,NATS让你满意。对于NATS的用户,你知道我在说什么。那么为什么我会在所有语言中,以Dart为NATS编写驱动程序?tl;dr - 必要是发明的母亲。实际原因也简单。我正在编写一个带有无服务器触发器的Flutter应用程序,NATS似乎没有Dart驱动程序。所以我写了!!过程直截了当吗?是,是的。NATS有非常直观的基于文本的协议。所以,我所要做的是打开TCP套接字,并开始发送字节流:)棘手的部分?好吧,集群。虽然我没有浏览其他驱动程序,当一个群集的主机出现故障时,将订阅从一个主机移植到另一个主机有点棘手。但Dart拥有Future和Stream类的最佳流式抽象,所以,一旦掌握了它,就变得轻松。为什么选择Dart?我个人认为Dart将在未来3-5年内成为编程语言的超人。谷歌正在大力投注Flutter;Dart现在可用共享代码库开发服务器、iOS、Android、MacOS、Linux和Windows应用程序;Flutter团队现在通过Hummingbird项目将Flutter带到网上。我猜谷歌不会让我失望。总结在短时间内,你将无法在消息传递领域中找到像NATS这样棒的东西。它强调云原生操作、群集和闪电般快速的消息传递,这简直令人兴奋。因此,我建议大家使用你选择的语言来尝试一下。任何Dart/Flutter的开发者,请在Github上查看nats-dart,并告诉我是否合适。

January 8, 2019 · 1 min · jiezi

Flutter-WeChat -- 学习Flutter期间仿做的一个微信App

Flutter-WeChat – 学习Flutter期间仿做的一个微信App全都是app静态布局与部分页面交互,没有调用后台接口(个人学习作品,侵权必删)还在学习中,后续会更新页面与交互…有问题还望各位指出…谢谢!!!github地址

December 25, 2018 · 1 min · jiezi

Flutter 插件开发:以微信SDK为例

就像 React Native 一样,在 Flutter 应用中,如果需要调用第三方库的方法或者有一些功能需要使用原生的开发来提供,使用 Flutter Plugin 是一种不错的方式,它本质上就是一个 Dart Package,但与其它的 package 不同点在于,Flutter 插件中一般都存在两个特殊的文件夹:android 与 ios,如果需要编写Java、Kotlin或者 Object-C 以及 Swift 代码,我们就需要在这两个文件夹项目中进行,然后通过相应的方法将原生代码中开发的方法映射到 dart 中。本文以开发一个微信插件为例,为Flutter应用提供微信分享、登录、支付等功能,项目代码可以直接在下方找到,也已经提交至Pub库:原文地址:https://pantao.onmr.com/press/flutter-wechat-plugin.htmlPub库:https://pub.dartlang.org/packages/wechat项目地址:https://github.com/pantao/flutter-wechat创建插件目录要开发插件,可以使用下面的代码快速基于 plugin 模板开始:flutter create –template=plugin wechat上面的代码中,表示以 plugin 模板创建一个名为 wechat 的 package,创建完成之后,整个项目的目录结构就都提供好了,并且官方还提供了一些基本开发示例。目录结构- android // Android 相关原生代码目录- ios // ios 相关原生代码目录- lib // Dart 代码目录- example // 一个完整的调用了我们正在开发的插件的 Flutter App- pubspec.yaml // 项目配置文件从 example/lib/main.dart 开始在开发我们的应用之后,先来了解一下 flutter 为我们生成的文件们,打开 example/lib/main.dart,代码如下:import ‘package:flutter/material.dart’;import ‘dart:async’;import ‘package:flutter/services.dart’;import ‘package:wechat/wechat.dart’;void main() => runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState();}class _MyAppState extends State<MyApp> { String _platformVersion = ‘Unknown’; @override void initState() { super.initState(); initPlatformState(); } // Platform messages are asynchronous, so we initialize in an async method. Future<void> initPlatformState() async { String platformVersion; // Platform messages may fail, so we use a try/catch PlatformException. try { platformVersion = await Wechat.platformVersion; } on PlatformException { platformVersion = ‘Failed to get platform version.’; } // If the widget was removed from the tree while the asynchronous platform // message was in flight, we want to discard the reply rather than calling // setState to update our non-existent appearance. if (!mounted) return; setState(() { _platformVersion = platformVersion; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text(‘Plugin example app’), ), body: Center( child: Text(‘Running on: $_platformVersion\n’), ), ), ); }}这里需要特别注意的就是 initPlatformState() 方法中对 Wechat.platformVersion 的调用,这里面的 Wechat 就是我们的插件,platformVersion 就是插件提供的 get 方法,跟着这个文件,找到 lib/wechat.dart 文件,代码如下:import ‘dart:async’;import ‘package:flutter/services.dart’;class Wechat { static const MethodChannel _channel = const MethodChannel(‘wechat’); static Future<String> get platformVersion async { final String version = await _channel.invokeMethod(‘getPlatformVersion’); return version; }}在该文件中,可以看到 class Wechat 定义了一个 get 方法 platformVersion,它的函数体有点特别:final String version = await _channel.invokeMethod(‘getPlatformVersion’);return version;我们的 version 是通过 _channel.invokeMethod(‘getPlatformVersion’) 方法的调用得到的,这个 _channel 就是我们 Dart 代码与 原生代码进行通信的桥了,也是 Flutter 原生插件的核心(当然,如果你编写的插件并不需要原生代码相关的功能,那么,_channel 就是可有可无的了,比如我们可以写一个下面这样的方法,返回 两个数字 a 与 b 的和:class Wechat { … static int calculate (int a, int b) { return a + b; }}之后,修改 example/lib/main.dart 代码:class _MyAppState extends State<MyApp> { String _platformVersion = ‘Unknown’; // 定义一个 int 型变量,用于保存计算结果 int _calculateResult; @override void initState() { super.initState(); initPlatformState(); } Future<void> initPlatformState() async { String platformVersion; try { platformVersion = await Wechat.platformVersion; } on PlatformException { platformVersion = ‘Failed to get platform version.’; } if (!mounted) return; // init 的时候,计算一下 10 + 10 的结果 _calculateResult = Wechat.calculate(10, 10); setState(() { _platformVersion = platformVersion; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text(‘Plugin example app’), ), body: Container( padding: EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( children: <Widget>[ Text(‘Running on: $_platformVersion\n’), // 输出该结果 Text(‘Calculate Result: $_calculateResult\n’), ], ), ), ), ), ); }}支持原生编码提供的方法很多时候,写插件,更多的是因为我们需要让应用能够调用原生代码提供的方法,怎么做呢?Android 系统打开 android/src/main/java/com/example/wechat/WechatPlugin.java 文件,看如下代码:package com.example.wechat;import io.flutter.plugin.common.MethodCall;import io.flutter.plugin.common.MethodChannel;import io.flutter.plugin.common.MethodChannel.MethodCallHandler;import io.flutter.plugin.common.MethodChannel.Result;import io.flutter.plugin.common.PluginRegistry.Registrar;/** WechatPlugin /public class WechatPlugin implements MethodCallHandler { /* Plugin registration. / public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), “wechat”); channel.setMethodCallHandler(new WechatPlugin()); } @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals(“getPlatformVersion”)) { result.success(“Android " + android.os.Build.VERSION.RELEASE); } else { result.notImplemented(); } }}还记得上面提到的 getPlatformVersion 吗?还记得 _channel 那么,是不是在这里面也看到的对应的存在?没错, dart 中的 getPlatformVersion 通过 _channel.invokeMethod 发起一次请求,然后,Java 代码中的 onMethodCall 方法回被调用,该方法有两个参数:MethodCall call:请求本身Result result:结果处理方法然后通过 call.method 可以知到 _channel.invokeMethod 中的方法名,然后通过 result.success 回调返回成功结果响应。registerWith在上面还有一小段代码 registerWith,可以看到里面有一个调用:final MethodChannel channel = new MethodChannel(registrar.messenger(), “wechat”);channel.setMethodCallHandler(new WechatPlugin());这里就是在注册我们的插件,将 wechat 注册成为我们的 channel 名,这样,才不会调用 alipay 插件的调用最后到了 wechat 插件这里。iOS 系统同样的,这次我们打开 ios/Classes/WechatPlugin.m 文件:#import “WechatPlugin.h”@implementation WechatPlugin+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>)registrar { FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@“wechat” binaryMessenger:[registrar messenger]]; WechatPlugin* instance = [[WechatPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel];}- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@“getPlatformVersion” isEqualToString:call.method]) { result([@“iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); } else { result(FlutterMethodNotImplemented); }}@end虽然语法有所不同,但是,可以看到,跟 android 的 Java 代码结构上几乎是一模一样的,首先 register 一个名为 wechat 的 channel,然后去 handleMethodCall,同样的通过 call.method 拿到方法名,通过 result 做出响应。小试牛刀接下来,我们将前面的 caculate 方法,移到原生代码中来提供(虽然这很没必要,但毕竟,只是为了演示嘛)。Android在前面打开的 android/src/main/java/com/example/wechat/WechatPlugin.java 文件中,修改 onMethodCall 方法: @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals(“getPlatformVersion”)) { result.success(“Android " + android.os.Build.VERSION.RELEASE); } else if (call.method.equals(“calculate”)) { int a = call.argument(“a”); int b = call.argument(“b”); int r = a + b; result.success(”” + r); } else { result.notImplemented(); } }添加了 call.method.equals(“calculate”) 判断,这里面具体的过程是:调用 call.argument() 方法,可以取得由 wechat.dart 传递过来的参数计算结果调用 result.success() 响应结果然后,我们需要在 lib/wechat.dart 中修改 calculate 方法的实现,代码如下: static Future<int> calculate (int a, int b) async { final String result = await _channel.invokeMethod(‘calculate’, { ‘a’: a, ‘b’: b }); return int.parse(result); }由于 _channel.invokeMethod 是一个异步操作,所以,我们需要将 calculate 的返回类型修改为 Future,同时加上 async,此时我们就可以直接使用 await 关键字了,跟 JavaScript 中的 await 一样,让我们用同步的方式编写异步代码,在新版的 calculate 代码中,我们并没有直接计算 a+b 的结果,而是调用 _channel.invokeMethod 方法,将 a 与 b 传递给了 Java 端的 onMethodCall 方法,然后返回该方法返回的结果。_channel.invokeMethod该方法接受两个参数,第一个定义一个方法名,它是一个标识,简单来说,它告诉原生端的代码,我们这次是要干什么,第二个参数是一个 Map<String, dynamic> 型数据,是参数列表,我们可以在原生代码中获取到。接着,我们需要更新一下对该方法的调用了,回到 example/lib/main.dart 中,修改成如下调用:_calculateResult = await Wechat.calculate(10, 10);因为我们现在的 calculate 方法已经是一个异步方法了。iOS如果我们的插件需要支持 Android 与 IOS 两端,那么需要同步的在 ios 中实现上面的方法,打开 ios/Classes/WechatPlugin.m 文件,作如下修改:- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSDictionary arguments = [call arguments]; if ([@“getPlatformVersion” isEqualToString:call.method]) { result([@“iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); } else if ([@“calculate” isEqualToString:call.method]) { NSInteger a = [arguments[@“a”] intValue]; NSInteger b = [arguments[@“b”] intValue]; result([NSString stringWithFormat:@"%d”, a + b]); } else { result(FlutterMethodNotImplemented); }}实现过程与 java 端保持一致即可。添加第三方 SDK我们的插件是可以提供微信的分享相关功能的,所以,肯定需要用到第三方SDK,还是从 Android 开始。Android 端 WechatSDK按 官方接入指南 所述,我们需要添加依赖:dependencies { compile ‘com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+’}或dependencies { compile ‘com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+’}前者带有统计功能,这很简单,打开 android/build.gradle 文件 ,在最下方粘贴以上片段即可:…android { compileSdkVersion 27 defaultConfig { minSdkVersion 16 testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner” } lintOptions { disable ‘InvalidPackage’ }}dependencies { compile ‘com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+’}然后,回到 WechatPlugin.java 文件,先添加一个 register 方法,它将我们的Appid 注册给微信,还是接着前面的 onMethodCall 中的 if 判断:…import com.tencent.mm.opensdk.openapi.WXAPIFactory;… else if (call.method.equals(“register”)) { appid = call.argument(“appid”); api = WXAPIFactory.createWXAPI(context, appid, true); result.success(api.registerApp(appid)); }…然后回到 lib/wechat.dart 添加相应调用:… /// Register app to Wechat with [appid] static Future<dynamic> register(String appid) async { var result = await _channel.invokeMethod( ‘register’, { ‘appid’: appid } ); return result; }…此时,在我们的 example 应该中,就可以调用 Wechat.register 方法,来注册应用了ios按照官方 ios 接入指南所述,我们可以通过 pod 添加依赖:pod ‘WechatOpenSDK’打开 ios/wechat.podspec ,可以看到如下内容:## To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html#Pod::Spec.new do |s| s.name = ‘wechat’ s.version = ‘0.0.1’ s.summary = ‘A new flutter plugin project.’ s.description = <<-DESCA new flutter plugin project. DESC s.homepage = ‘http://example.com’ s.license = { :file => ‘../LICENSE’ } s.author = { ‘Your Company’ => ’email@example.com’ } s.source = { :path => ‘.’ } s.source_files = ‘Classes/**/’ s.public_header_files = ‘Classes//*.h’ s.dependency ‘Flutter’ s.ios.deployment_target = ‘8.0’end留意到数第三行的 s.dependency,这就是在指定我们依赖 Flutter,如果有其它依赖在这里添加一行即可:… s.public_header_files = ‘Classes//*.h’ s.dependency ‘Flutter’ s.dependency ‘WechatOpenSDK’ s.ios.deployment_target = ‘8.0’end然后打开 ios/Classes/WechatPlugin.h 文件,修改如下:#import <Flutter/Flutter.h>#include “WXApi.h”@interface WechatPlugin : NSObject<FlutterPlugin, WXApiDelegate>@end再回到 ios/Classes/WechatPlugin.m,接着前面的 if 条件继续添加判断:… // Register app to Wechat with appid else if ([@“register” isEqualToString:call.method]) { [WXApi registerApp:arguments[@“appid”]]; result(nil); }…此时,我们的插件已经支持微信 SDK 的 注册至微信 功能了,更多实现,本文就不再讨论,有兴趣,可以直接下载完整项目,后面都是大同小异的实现,唯一需要的是,你需要有一定的 Java 编码与 Objective-C 编码能力。 ...

December 20, 2018 · 5 min · jiezi

基于 Redux + Redux Persist 进行状态管理的 Flutter 应用示例

好久没在 SegmentFault 写东西,唉,也不知道 是忙还是懒,以后有时间 再慢慢写起来吧,最近开始学点新东西,有的写了,个人博客跟这里同步。一直都在自己的 React Native 应用中使用 Redux,其实更大情况下也是使用它来管理应用的会话状态以及当前登录的用户信息等等简单的数据,很好用,自从 Google 发布 Flutter 之后,就一直想着拿它来做点啥,准备拿一个新项目开刀,先研究下怎么把以前在 React Native 中需要用到的一些技术在 Flutter 找到对应的实现方法,本文记录下 Flutter + Redux + Redux Persist 的实现。原文地址:Flutter + Redux + Redux Persist 应用项目地址:https://github.com/pantao/flutter-redux-demo-app<!–more–>第一步:创建一个新的应用:redux_demo_appflutter create redux_demo_appcd redux_demo_appcode .Flutter 项目必须是一个合法的 Dart 包,而 Dart 包要求使用纯小写字母(可包含下划线),这个跟 React Native 是不一样的。第二步:添加依懒我们依懒下面这些包:Redux : JavaScript Redux 的复刻版Flutter Redux:类似于 React Redux 一样,让我们在 Flutter 项目中更好的使用 ReduxRedux Persist:Redux 持久化Redux Persist Flutter:Flutter Redux Persist 引擎打开 pubspec.yaml,在 dependencies 中添加下面这些依懒:…dependencies: … redux: ^3.0.0 flutter_redux: ^0.5.2 redux_persist: ^0.8.0 redux_persist_flutter: ^0.8.0dev_dependencies: ……第三步:了解需求本次我想做的一个App有下面四个页面:首页个人中心页个人资料详情页登录页交互是下面这样的:应用打开之后,打开的是一个有两个底部 Tab 的应用,默认展示的是首页当用户点击(我的)这个Tab时:若当前用户已登录,则Tab切换为个人中心页若当前用户未登录,则以 Modal 的方式弹出登录页添加 lib/state.dart 文件内容如下:enum Actions{ login, logout}/// App 状态/// /// 状态中所有数据都应该是只读的,所以,全部以 get 的方式提供对外访问,不提供 set 方法class AppState { /// J.W.T String _authorizationToken; // 获取当前的认证 Token get authorizationToken => _authorizationToken; // 获取当前是否处于已认证状态 get authed => _authorizationToken.length > 0; AppState(this._authorizationToken);}/// ReducerAppState reducer(AppState state, action) { switch(action) { case Actions.login: return AppState(‘J.W.T’); case Actions.logout: return AppState(’’); default: return state; }}在上面的代码中,我们先声明了 Actions 枚举,以及一个 AppState 类,该类就是我们的应用状态类,使用 _authorizationToken 保证认证的值不可被实例外直接被访问到,这样用户就无法去直接修改它的值,再提供了两个 get 方法,提供给外部访问它的值。接着我们定义了一个 reducer 函数,用于更新状态。创建 app.dartimport ‘package:flutter/material.dart’;import ‘package:redux/redux.dart’;import ‘package:flutter_redux/flutter_redux.dart’;import ‘state.dart’;import ‘root.dart’;/// 示例Appclass DemoApp extends StatelessWidget { // app store final Store<AppState> store; DemoApp(this.store); @override Widget build(BuildContext context) { return StoreProvider<AppState>( store: store, child: new MaterialApp( title: ‘Flutter Redux Demo App’, // home 为 root 页 home: Root() ), ); }}在上面我们已经完成的 App 类的编码,现在需要完成 Root 页,也就是我们的App入口页。创建 Root 页import ‘package:flutter/material.dart’;import ‘package:redux/redux.dart’;import ‘package:flutter_redux/flutter_redux.dart’;/// 状态import ‘state.dart’;/// 登录页面import ‘auth.dart’;/// 我的页面import ‘me.dart’;/// 首页import ‘home.dart’;/// 应用入口页class Root extends StatefulWidget { @override State<StatefulWidget> createState() { return _RootState(); }}/// 入口页状态class _RootState extends State<Root> { /// 当前被激活的 Tab Index int _currentTabIndex; /// 所有 Tab 列表页 List<Widget> _tabPages; @override void initState() { super.initState(); // 初始化 tab 为第 0 个 _currentTabIndex = 0; // 初始化页面列表 _tabPages = <Widget>[ // 首页 Home(), // 我的 Me() ]; } @override Widget build(BuildContext context) { // 使用 StoreConnector 创建 Widget // 类似于 React Redux 的 connect,链接 store state 与 Widget return StoreConnector<AppState, Store<AppState>>( // store 转换器,类似于 react redux 中的 mapStateToProps 方法 // 接受参数为 store,再返回的数据可以被在 builder 函数中使用, // 在此处,我们直接返回整个 store, converter: (store) => store, // 构建器,第二个参数 store 就是上一个 converter 函数返回的 store builder: (context, store) { // 取得当前是否已登录状态 final authed = store.state.authed; return new Scaffold( // 如果已登录,则直接可以访问所有页面,否则展示 Home body: authed ? _tabPages[_currentTabIndex] : Home(), // 底部Tab航 bottomNavigationBar: BottomNavigationBar( onTap: (int index) { // 如果点击的是第 1 个Tab,且当前用户未登录,则直接打开登录 Modal 页 if (!authed && index == 1) { Navigator.push( context, MaterialPageRoute( builder: (context) => Auth(), fullscreenDialog: true ) ); // 否则直接进入相应页面 } else { setState(() { _currentTabIndex = index; }); } }, // 与 body 取值方式类似 currentIndex: authed ? _currentTabIndex : 0, items: [ BottomNavigationBarItem( icon: Icon(Icons.home), title: Text(‘首页’) ), BottomNavigationBarItem( icon: Icon(Icons.people), title: Text(‘我的’) ) ], ), ); }, ); }}创建 Home与 Root 页面类似,我们可以在任何页面方便的使用 AppStateimport ‘package:flutter/material.dart’;import ‘package:redux/redux.dart’;import ‘package:flutter_redux/flutter_redux.dart’;import ‘state.dart’;import ‘auth.dart’;class Home extends StatefulWidget { @override State<StatefulWidget> createState() => _HomeState();}class _HomeState extends State<Home> { @override Widget build(BuildContext context) { return StoreConnector<AppState, Store<AppState>>( converter: (store) => store, builder: (context, store) { return Scaffold( appBar: AppBar( title: Text(‘首页’), ), body: Center( child: store.state.authed ? Text(‘您已登录’) : FlatButton( child: Text(‘去登录’), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => Auth(), fullscreenDialog: true ) ); }, ) ), ); }, ); }}完成 Auth在前面的所有页面中,都只是对 store 中状态树的读取,现在的 Auth 就需要完成对状态树的更新了,看下面代码:import ‘package:flutter/material.dart’;import ‘package:redux/redux.dart’;import ‘package:flutter_redux/flutter_redux.dart’;import ‘state.dart’;class Auth extends StatefulWidget { @override State<StatefulWidget> createState() => _AuthState();}class _AuthState extends State<Auth> { @override Widget build(BuildContext context) { return StoreConnector<AppState, Store<AppState>>( converter: (store) => store, builder: (context, store) { return Scaffold( appBar: AppBar( title: Text(‘登录’), ), body: Center( child: FlatButton( child: Text(‘登录’), onPressed: () { // 通过 store.dispatch 函数,可以发出 action(跟 Redux 是一样的),而 Action 是在 // AppState 中定义的枚举 Actions.login store.dispatch(Actions.login); // 之后,关闭当前的 Modal,就可以看到应用所有数据都更新了 Navigator.pop(context); }, ) ), ); }, ); }}创建 Me有了登录之后,我们可以在做一个我的页面,在这个页面里面我们可以完成退出功能。import ‘package:flutter/material.dart’;import ‘package:redux/redux.dart’;import ‘package:flutter_redux/flutter_redux.dart’;import ‘state.dart’;class Me extends StatefulWidget { @override State<StatefulWidget> createState() => _MeState();}class _MeState extends State<Me> { @override Widget build(BuildContext context) { return StoreConnector<AppState, Store<AppState>>( converter: (store) => store, builder: (context, store) { return Scaffold( appBar: AppBar( title: Text(‘退出’), ), body: Center( child: FlatButton( child: Text(‘退出’), onPressed: () { store.dispatch(Actions.logout); // 此处我们不需要去更新Tab Index,在 Root 页面中,对 store 里面的 authed 值已经做了监听,如果 // Actions.logout 被触发后, authed 的值会变成 false,那么App将自动切换首页 }, ) ), ); }, ); }}添加状态持久化在上面,我们已经完成了一个基于 Redux 的同步状态的App,但是当你的App关闭重新打开之外,状态树就会被重置为初始值,这并不理想,我们经常需要一个用户完成登录之后,就可以在一断时间内一直保持这个登录状态,而且有一些数据我们并不希望每次打开App的时候都重新初始化一次,这个时候,可以考虑对状态进行持久化了。更新 state.dartclass AppState { … // 持久化时,从 JSON 中初始化新的状态 static AppState fromJson(dynamic json) => json != null ? AppState(json[‘authorizationToken’] as String) : AppState(’’); // 更新状态之后,转成 JSON,然后持久化至持久化引擎中 dynamic toJson() => {‘authorizationToken’: _authorizationToken};}这里我们添加了两个方法,一个是静态的 fromJson 方法,它将在初始化状态树时被调用,用于从 JSON 中初始化一个新的状态树出来, toJson 将被用于持久化,将自身转成 JSON。更新 main.dartimport ‘package:flutter/material.dart’;import ‘package:redux/redux.dart’;import ‘package:redux_persist/redux_persist.dart’;import ‘package:redux_persist_flutter/redux_persist_flutter.dart’;import ‘app.dart’;import ‘state.dart’;void main() async { // 创建一个持久化器 final persistor = Persistor<AppState>( storage: FlutterStorage(), serializer: JsonSerializer<AppState>(AppState.fromJson), debug: true ); // 从 persistor 中加载上一次存储的状态 final initialState = await persistor.load(); final store = Store<AppState>( reducer, initialState: initialState ?? AppState(’’), middleware: [persistor.createMiddleware()] ); runApp(new DemoApp(store));}重新 flutter run 当前应用,即完成了持久化,可以登录,然后退出应用,再重新打开应用,可以看到上一次的登录状态是存在的。 ...

December 17, 2018 · 4 min · jiezi

Flutter要火!Dart你会了吗?

从Flutter问世,人们对他的关注一直不断,特别是前不久Flutter 1.0发布后,人们对他的关注更多了,Flutter要火!那就学习一下了,我呢,身为一个前端开发工作者,就以一个前端开发者的身份来学习Flutter,由于Flutter是使用的Dart语言,那就从Dart开始吧!语言特性Dart所有的东西都是对象, 即使是数字numbers、函数function、null也都是对象,所有的对象都继承自Object类。Dart动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)。Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。Dart中的类和接口是统一的,类即接口,你可以继承一个类,也可以实现一个类(接口),自然也包含了良好的面向对象和并发编程的支持。Dart 提供了顶级函数(如:main())。Dart 没有 public、private、protected 这些关键字,变量名以"_“开头意味着对它的 lib 是私有的。没有初始化的变量都会被赋予默认值 null。final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];,这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。实例变量可以是 final,但不能是 const。编程语言并不是孤立存在的,Dart也是这样,他由语言规范、虚拟机、类库和工具等组成:SDK:SDK 包含 Dart VM、dart2js、Pub、库和工具。Dartium:内嵌 Dart VM 的 Chromium ,可以在浏览器中直接执行 dart 代码。Dart2js:将 Dart 代码编译为 JavaScript 的工具。Dart Editor:基于 Eclipse 的全功能 IDE,并包含以上所有工具。支持代码补全、代码导航、快速修正、重构、调试等功能。关键字(56个)abstract do import super as dynamic in switch assert else interface sync enum implements is this async export library throw await external mixin true break extends new try case factory null typedef catch false operator var class final part void const finally rethrow while continue for return with covariant get set yield default if static deferred变量与常量变量声明与初始化调用的变量name包含对String值为“张三” 的对象的引用,name推断变量的类型是String,但可以通过指定它来更改该类型,如果对象不限于单一类型(没有明确的类型),请使用Object或dynamic关键字。 // 没有明确类型,编译的时候根据值明确类型 var name = ‘Bob’; Object name = ‘张三’; dynamic name = ‘李四’; // 显示声明将被推断类型, 可以使用String显示声明字符串类型 String name = ‘Bob’ ;默认值未初始化的变量的初始值为null(包括数字),因此数字、字符串都可以调用各种方法 //测试 数字类型的初始值是什么? int lineCount; // 为false的时候抛出异常 assert(lineCount == null); print(lineCount); //打印结果为null,证明数字类型初始化值是nullfinal and const如果您从未打算更改一个变量,那么使用 final 或 const,不是var,也不是一个类型。一个 final 变量只能被初始化一次; const变量是一个编译时常量,(Const变量是隐式的final)final的顶级或类变量在第一次使用时被初始化。被final修饰的顶级变量或类变量在第一次声明的时候就需要初始化。// The final variable ‘outSideFinalName’ must be initialized.final String outSideFinalName被final或者const修饰的变量,变量类型可以省略,建议指定数据类型。 //可以省略String这个类型声明 final name = “Bob”; final String name1 = “张三”; const name2 = “alex”; const String name3 = “李四”;被 final 或 const 修饰的变量无法再去修改其值。 final String outSideFinalName = “Alex”; // outSideFinalName’, a final variable, can only be set once // 一个final变量,只能被设置一次。 outSideFinalName = “Bill”; const String outSideName = ‘Bill’; // 这样写,编译器提示:Constant variables can’t be assigned a value // const常量不能赋值 // outSideName = “小白”;flnal 或者 const 不能和 var 同时使用 // Members can’t be declared to be both ‘const’ and ‘var’ const var String outSideName = ‘Bill’; // Members can’t be declared to be both ‘final’ and ‘var’ final var String name = ‘Lili’;常量如果是类级别的,请使用 static const // 常量如果是类级别的,请使用 static const static const String name3 = ‘Tom’; // 这样写保存 // Only static fields can be declared as const // 只有静态字段可以声明为const //const String name3 = ‘Tom’;常量的运算 const speed = 100; //速度(km/h) const double distance = 2.5 * speed; // 距离 = 时间 * 速度 final speed2 = 100; //速度(km/h) final double distance2 = 2.5 * speed2; // 距离 = 时间 * 速度const关键字不只是声明常数变量,您也可以使用它来创建常量值,以及声明创建常量值的构造函数,任何变量都可以有一个常量值。 // 注意: [] 创建的是一个空的list集合 // const []创建一个空的、不可变的列表(EIL)。 var varList = const []; // varList 当前是一个EIL final finalList = const []; // finalList一直是EIL const constList = const []; // constList 是一个编译时常量的EIL // 可以更改非final,非const变量的值 // 即使它曾经具有const值 varList = [“haha”]; // 不能更改final变量或const变量的值 // 这样写,编译器提示:a final variable, can only be set once // finalList = [“haha”]; // 这样写,编译器提示:Constant variables can’t be assigned a value // constList = [“haha”];在常量表达式中,该运算符的操作数必须为’bool’、’num’、‘String’或’null’, const常量必须用conat类型的值初始化。const String outSideName = ‘Bill’;final String outSideFinalName = ‘Alex’;const String outSideName2 = ‘Tom’;const aConstList = const [‘1’, ‘2’, ‘3’];// In constant expressions, operands of this operator must be of type ‘bool’, ’num’, ‘String’ or ’null’// 在常量表达式中,该运算符的操作数必须为’bool’、’num’、‘String’或’null’。const validConstString = ‘$outSideName $outSideName2 $aConstList’;// Const variables must be initialized with a constant value// const常量必须用conat类型的值初始化const validConstString = ‘$outSideName $outSideName2 $outSideFinalName’;var outSideVarName=‘Cathy’;// Const variables must be initialized with a constant value.// const常量必须用conat类型的值初始化const validConstString = ‘$outSideName $outSideName2 $outSideVarName’;// 正确写法const String outSideConstName = ‘Joy’;const validConstString = ‘$outSideName $outSideName2 $outSideConstName’;数据类型numnum 是数字类型的父类,有两个子类 int 和 double。int 根据平台的不同,整数值不大于64位。在Dart VM上,值可以从-263到263 - 1,编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253 - 1。double 64位(双精度)浮点数,如IEEE 754标准所规定。 int a = 1; print(a); double b = 1.12; print(b); // String -> int int one = int.parse(‘1’); // 输出3 print(one + 2); // String -> double var onePointOne = double.parse(‘1.1’); // 输出3.1 print(onePointOne + 2); // int -> String String oneAsString = 1.toString(); // The argument type ‘int’ can’t be assigned to the parameter type ‘String’ //print(oneAsString + 2); // 输出 1 + 2 print(’$oneAsString + 2’); // 输出 1 2 print(’$oneAsString 2’); // double -> String 注意括号中要有小数点位数,否则报错 String piAsString = 3.14159.toStringAsFixed(2); // 截取两位小数, 输出3.14 print(piAsString); String aString = 1.12618.toStringAsFixed(2); // 检查是否四舍五入,输出1.13,发现会做四舍五入 print(aString);StringDart里面的String是一系列 UTF-16 代码单元。您可以使用单引号或双引号来创建一个字符串。单引号或者双引号里面嵌套使用引号。用 或{} 来计算字符串中变量的值,需要注意的是如果是表达式需要${表达式} String singleString = ‘abcdddd’; String doubleString = “abcsdfafd”; String sdString = ‘$singleString a “bcsd” ${singleString}’; String dsString = “abc ‘aaa’ $sdString”; print(sdString); print(dsString); String singleString = ‘aaa’; String doubleString = “bbb”; // 单引号嵌套双引号 String sdString = ‘$singleString a “bbb” ${doubleString}’; // 输出 aaa a “bbb” bbb print(sdString); // 双引号嵌套单引号 String dsString = “${singleString.toUpperCase()} abc ‘aaa’ $doubleString.toUpperCase()”; // 输出 AAA abc ‘aaa’ bbb.toUpperCase(), 可以看出 ”$doubleString.toUpperCase()“ 没有加“{}“,导致输出结果是”bbb.toUpperCase()“ print(dsString);boolDart 是强 bool 类型检查,只有bool 类型的值是true 才被认为是true。只有两个对象具有bool类型:true和false,它们都是编译时常量。Dart的类型安全意味着您不能使用 if(nonbooleanValue) 或 assert(nonbooleanValue) 等代码, 相反Dart使用的是显式的检查值。assert 是语言内置的断言函数,仅在检查模式下有效在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)。 // 检查是否为空字符串 var fullName = ‘’; assert(fullName.isEmpty); // 检查0 var hitPoints = 0; assert(hitPoints <= 0); // 检查是否为null var unicorn; assert(unicorn == null); // 检查是否为NaN var iMeantToDoThis = 0 / 0; assert(iMeantToDoThis.isNaN);List集合在Dart中,数组是List对象,因此大多数人只是将它们称为List。Dart list文字看起来像JavaScript数组文字 //创建一个int类型的list List list = [10, 7, 23]; // 输出[10, 7, 23] print(list); // 使用List的构造函数,也可以添加int参数,表示List固定长度,不能进行添加 删除操作 var fruits = new List(); // 添加元素 fruits.add(‘apples’); // 添加多个元素 fruits.addAll([‘oranges’, ‘bananas’]); List subFruits = [‘apples’, ‘oranges’, ‘banans’]; // 添加多个元素 fruits.addAll(subFruits); // 输出: [apples, oranges, bananas, apples, oranges, banans] print(fruits); // 获取List的长度 print(fruits.length); // 获取第一个元素 print(fruits.first); // 获取元素最后一个元素 print(fruits.last); // 利用索引获取元素 print(fruits[0]); // 查找某个元素的索引号 print(fruits.indexOf(‘apples’)); // 删除指定位置的元素,返回删除的元素 print(fruits.removeAt(0)); // 删除指定元素,成功返回true,失败返回false // 如果集合里面有多个“apples”, 只会删除集合中第一个改元素 fruits.remove(‘apples’); // 删除最后一个元素,返回删除的元素 fruits.removeLast(); // 删除指定范围(索引)元素,含头不含尾 fruits.removeRange(start,end); // 删除指定条件的元素(这里是元素长度大于6) fruits.removeWhere((item) => item.length >6); // 删除所有的元素 fruits.clear();注意事项:可以直接打印list包括list的元素,list也是一个对象。但是java必须遍历才能打印list,直接打印是地址值。和java一样list里面的元素必须保持类型一致,不一致就会报错。和java一样list的角标从0开始。如果集合里面有多个相同的元素“X”, 只会删除集合中第一个改元素Map集合一般来说,map是将键和值相关联的对象。键和值都可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。Dart支持map由map文字和map类型提供。初始化Map方式一: 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。 // Two keys in a map literal can’t be equal. // Map companys = {‘Alibaba’: ‘阿里巴巴’, ‘Tencent’: ‘腾讯’, ‘baidu’: ‘百度’, ‘Alibaba’: ‘钉钉’, ‘Tenect’: ‘qq-music’}; Map companys = {‘Alibaba’: ‘阿里巴巴’, ‘Tencent’: ‘腾讯’, ‘baidu’: ‘百度’}; // 输出:{Alibaba: 阿里巴巴, Tencent: 腾讯, baidu: 百度} print(companys);创建Map方式二:先声明,再去赋值。 Map schoolsMap = new Map(); schoolsMap[‘first’] = ‘清华’; schoolsMap[‘second’] = ‘北大’; schoolsMap[’third’] = ‘复旦’; // 打印结果 {first: 清华, second: 北大, third: 复旦} print(schoolsMap); var fruits = new Map(); fruits[“first”] = “apple”; fruits[“second”] = “banana”; fruits[“fifth”] = “orange”; //换成双引号,换成var 打印结果 {first: apple, second: banana, fifth: orange} print(fruits);Map API// 指定键值对的参数类型var aMap = new Map<int, String>();// Map的赋值,中括号中是Key,这里可不是数组aMap[1] = ‘小米’;//Map中的键值对是唯一的//同Set不同,第二次输入的Key如果存在,Value会覆盖之前的数据aMap[1] = ‘alibaba’;// map里面的value可以相同aMap[2] = ‘alibaba’;// map里面value可以为空字符串aMap[3] = ‘’;// map里面的value可以为nullaMap[4] = null;print(aMap);// 检索Map是否含有某Keyassert(aMap.containsKey(1));//删除某个键值对aMap.remove(1); print(aMap); 注意事项:map的key类型不一致也不会报错。添加元素的时候,会按照你添加元素的顺序逐个加入到map里面,哪怕你的key,比如分别是 1,2,4,看起来有间隔,事实上添加到map的时候是{1:value,2:value,4:value} 这种形式。map里面的key不能相同。但是value可以相同,value可以为空字符串或者为null。运算符一元后置操作符 expr++ expr– () [] . ?.一元前置操作符expr !expr ~expr ++expr –expr乘除 / % ~/加减 + -位移 << >>按位与 &按位异或 ^逻辑与 &&关系和类型判断 >= > <= < as is is!等 == !=如果为空 ??条件表达式 expr1 ? expr2 : expr3赋值 = = /= ~/= %= += -= <<= >>= &= ^= = ??=级联..流程控制语句(Control flow statements)if…elseforwhile do-whildbreak continueswitch…caseassert(仅在checked模式有效)异常(Exceptions)throw抛出固定类型的异常 throw new FormatException(‘Expected at least 1 section’);抛出任意类型的异常 throw ‘Out of llamas!’; 因为抛出异常属于表达式,可以将throw语句放在=>语句中,或者其它可以出现表达式的地方 distanceTo(Point other) => throw new UnimplementedError();catch将可能出现异常的代码放置到try语句中,可以通过 on语句来指定需要捕获的异常类型,使用catch来处理异常。 try { breedMoreLlamas(); } on OutOfLlamasException { // A specific exception buyMoreLlamas(); } on Exception catch (e) { // Anything else that is an exception print(‘Unknown exception: $e’); } catch (e, s) { print(‘Exception details:\n $e’); print(‘Stack trace:\n $s’); }rethrowrethrow语句用来处理一个异常,同时希望这个异常能够被其它调用的部分使用。 final foo = ‘’; void misbehave() { try { foo = “1”; } catch (e) { print(‘2’); rethrow;// 如果不重新抛出异常,main函数中的catch语句执行不到 } } void main() { try { misbehave(); } catch (e) { print(‘3’); } }finallyDart的finally用来执行那些无论异常是否发生都执行的操作。 final foo = ‘’; void misbehave() { try { foo = “1”; } catch (e) { print(‘2’); } } void main() { try { misbehave(); } catch (e) { print(‘3’); } finally { print(‘4’); // 即使没有rethrow最终都会执行到 } }函数 Function以下是一个实现函数的例子: bool isNoble(int atomicNumber) { return _nobleGases[atomicNumber] != null; } main()函数每个应用程序都必须有一个顶层main()函数,它可以作为应用程序的入口点。该main()函数返回void并具有List<String>参数的可选参数。void main() { querySelector(’#sample_text_id’) ..text = ‘Click me!’ ..onClick.listen(reverseText);}级联符号:允许您在同一个对象上进行一系列操作。除了函数调用之外,还可以访问同一对象上的字段。这通常会为您节省创建临时变量的步骤,并允许您编写更流畅的代码。querySelector(’#confirm’) // Get an object. ..text = ‘Confirm’ // Use its members. ..classes.add(‘important’) ..onClick.listen((e) => window.alert(‘Confirmed!’));上述例子相对于: var button = querySelector(’#confirm’); button.text = ‘Confirm’; button.classes.add(‘important’); button.onClick.listen((e) => window.alert(‘Confirmed!’));级联符号也可以嵌套使用。 例如: final addressBook = (AddressBookBuilder() ..name = ‘jenny’ ..email = ‘jenny@example.com’ ..phone = (PhoneNumberBuilder() ..number = ‘415-555-0100’ ..label = ‘home’) .build()) .build();当返回值是void时不能构建级联。 例如,以下代码失败:var sb = StringBuffer();sb.write(‘foo’) // 返回void write(‘bar’); // 这里会报错注意: 严格地说,级联的..符号不是操作符。它只是Dart语法的一部分。可选参数可选的命名参数, 定义函数时,使用{param1, param2, …},用于指定命名参数。例如: //设置[bold]和[hidden]标志 void enableFlags({bool bold, bool hidden}) { // … } enableFlags(bold: true, hidden: false);可选的位置参数,用[]它们标记为可选的位置参数: String say(String from, String msg, [String device]) { var result = ‘$from says $msg’; if (device != null) { result = ‘$result with a $device’; } return result; }下面是一个不带可选参数调用这个函数的例子: say(‘Bob’, ‘Howdy’); //结果是: Bob says Howdy 下面是用第三个参数调用这个函数的例子: say(‘Bob’, ‘Howdy’, ‘smoke signal’); //结果是:Bob says Howdy with a smoke signal 默认参数函数可以使用=为命名参数和位置参数定义默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为null。下面是为命名参数设置默认值的示例: // 设置 bold 和 hidden 标记的默认值都为false void enableFlags2({bool bold = false, bool hidden = false}) { // … } // 调用的时候:bold will be true; hidden will be false. enableFlags2(bold: true);下一个示例显示如何为位置参数设置默认值: String say(String from, String msg, [String device = ‘carrier pigeon’, String mood]) { var result = ‘$from says $msg’; if (device != null) { result = ‘$result with a $device’; } if (mood != null) { result = ‘$result (in a $mood mood)’; } return result; } //调用方式: say(‘Bob’, ‘Howdy’); //结果为:Bob says Howdy with a carrier pigeon;您还可以将list或map作为默认值传递。下面的示例定义一个函数doStuff(),该函数指定列表参数的默认list和gifts参数的默认map。 // 使用list 或者map设置默认值 void doStuff( {List<int> list = const [1, 2, 3], Map<String, String> gifts = const {‘first’: ‘paper’, ‘second’: ‘cotton’, ’third’: ’leather’ }}) { print(’list: $list’); print(‘gifts: $gifts’); }作为一个类对象的功能您可以将一个函数作为参数传递给另一个函数。 void printElement(int element) { print(element); } var list = [1, 2, 3]; // 把 printElement函数作为一个参数传递进来 list.forEach(printElement);您也可以将一个函数分配给一个变量。 var loudify = (msg) => ‘!!! ${msg.toUpperCase()} !!!’; assert(loudify(‘hello’) == ‘!!! HELLO !!!’);匿名函数大多数函数都能被命名为匿名函数,如 main() 或 printElement()。您还可以创建一个名为匿名函数的无名函数,有时也可以创建lambda或闭包。您可以为变量分配一个匿名函数,例如,您可以从集合中添加或删除它。一个匿名函数看起来类似于一个命名函数 - 0或更多的参数,在括号之间用逗号和可选类型标注分隔。下面的代码块包含函数的主体: ([[Type] param1[, …]]) { codeBlock; }; 下面的示例定义了一个具有无类型参数的匿名函数item,该函数被list中的每个item调用,输出一个字符串,该字符串包含指定索引处的值。 var list = [‘apples’, ‘bananas’, ‘oranges’]; list.forEach((item) { print(’${list.indexOf(item)}: $item’); });如果函数只包含一条语句,可以使用箭头符号=>来缩短它, 比如上面的例2可以简写成: list.forEach((item) => print(’${list.indexOf(item)}: $item’)); 返回值所有函数都返回一个值,如果没有指定返回值,则语句return null,隐式地附加到函数体。 foo() {} assert(foo() == null);类(Classes)对象Dart 是一种面向对象的语言,并且支持基于mixin的继承方式。Dart 语言中所有的对象都是某一个类的实例,所有的类有同一个基类–Object。基于mixin的继承方式具体是指:一个类可以继承自多个父类。使用new语句来构造一个类,构造函数的名字可能是ClassName,也可以是ClassName.identifier, 例如: var jsonData = JSON.decode(’{“x”:1, “y”:2}’); // Create a Point using Point(). var p1 = new Point(2, 2); // Create a Point using Point.fromJson(). var p2 = new Point.fromJson(jsonData);使用.(dot)来调用实例的变量或者方法。 var p = new Point(2, 2); // Set the value of the instance variable y. p.y = 3; // Get the value of y. assert(p.y == 3); // Invoke distanceTo() on p. num distance = p.distanceTo(new Point(4, 4));使用?.来确认前操作数不为空, 常用来替代. , 避免左边操作数为null引发异常。 // If p is non-null, set its y value to 4. p?.y = 4;使用const替代new来创建编译时的常量构造函数。 var p = const ImmutablePoint(2, 2);使用runtimeType方法,在运行中获取对象的类型。该方法将返回Type 类型的变量。 print(‘The type of a is ${a.runtimeType}’);实例化变量(Instance variables)在类定义中,所有没有初始化的变量都会被初始化为null。 class Point { num x; // Declare instance variable x, initially null. num y; // Declare y, initially null. num z = 0; // Declare z, initially 0. }类定义中所有的变量, Dart语言都会隐式的定义 setter 方法,针对非空的变量会额外增加 getter 方法。 class Point { num x; num y; } main() { var point = new Point(); point.x = 4; // Use the setter method for x. assert(point.x == 4); // Use the getter method for x. assert(point.y == null); // Values default to null. }构造函数(Constructors)声明一个和类名相同的函数,来作为类的构造函数。 class Point { num x; num y; Point(num x, num y) { // There’s a better way to do this, stay tuned. this.x = x; this.y = y; } }this关键字指向了当前类的实例, 上面的代码可以简化为: class Point { num x; num y; // Syntactic sugar for setting x and y // before the constructor body runs. Point(this.x, this.y); }构造函数不能继承(Constructors aren’t inherited)Dart 语言中,子类不会继承父类的命名构造函数。如果不显式提供子类的构造函数,系统就提供默认的构造函数。命名的构造函数(Named constructors)使用命名构造函数从另一类或现有的数据中快速实现构造函数。class Point { num x; num y; Point(this.x, this.y); // 命名构造函数Named constructor Point.fromJson(Map json) { x = json[‘x’]; y = json[‘y’]; }}构造函数不能被继承,父类中的命名构造函数不能被子类继承。如果想要子类也拥有一个父类一样名字的构造函数,必须在子类是实现这个构造函数。调用父类的非默认构造函数默认情况下,子类只能调用父类的无名,无参数的构造函数; 父类的无名构造函数会在子类的构造函数前调用; 如果initializer list 也同时定义了,则会先执行initializer list 中的内容,然后在执行父类的无名无参数构造函数,最后调用子类自己的无名无参数构造函数。即下面的顺序:initializer list(初始化列表)super class’s no-arg constructor(父类无参数构造函数)main class’s no-arg constructor (主类无参数构造函数)如果父类不显示提供无名无参数构造函数的构造函数,在子类中必须手打调用父类的一个构造函数。这种情况下,调用父类的构造函数的代码放在子类构造函数名后,子类构造函数体前,中间使用:(colon) 分割。class Person { String firstName; Person.fromJson(Map data) { print(‘in Person’); }}class Employee extends Person { // 父类没有无参数的非命名构造函数,必须手动调用一个构造函数 super.fromJson(data) Employee.fromJson(Map data) : super.fromJson(data) { print(‘in Employee’); }}main() { var emp = new Employee.fromJson({}); // Prints: // in Person // in Employee if (emp is Person) { // Type check emp.firstName = ‘Bob’; } (emp as Person).firstName = ‘Bob’;}初始化列表除了调用父类的构造函数,也可以通过初始化列表在子类的构造函数体前(大括号前)来初始化实例的变量值,使用逗号,分隔。如下所示:class Point { num x; num y; Point(this.x, this.y); // 初始化列表在构造函数运行前设置实例变量。 Point.fromJson(Map jsonMap) : x = jsonMap[‘x’], y = jsonMap[‘y’] { print(‘In Point.fromJson(): ($x, $y)’); } }注意:上述代码,初始化程序无法访问 this 关键字。静态构造函数如果你的类产生的对象永远不会改变,你可以让这些对象成为编译时常量。为此,需要定义一个 const 构造函数并确保所有的实例变量都是 final 的。class ImmutablePoint { final num x; final num y; const ImmutablePoint(this.x, this.y); static final ImmutablePoint origin = const ImmutablePoint(0, 0);}重定向构造函数有时候构造函数的目的只是重定向到该类的另一个构造函数。重定向构造函数没有函数体,使用冒号:分隔。class Point { num x; num y; // 主构造函数 Point(this.x, this.y) { print(“Point($x, $y)”); } // 重定向构造函数,指向主构造函数,函数体为空 Point.alongXAxis(num x) : this(x, 0);}void main() { var p1 = new Point(1, 2); var p2 = new Point.alongXAxis(4);}常量构造函数如果类的对象不会发生变化,可以构造一个编译时的常量构造函数。定义格式如下:定义所有的实例变量是final。使用const声明构造函数。class ImmutablePoint { final num x; final num y; const ImmutablePoint(this.x, this.y); static final ImmutablePoint origin = const ImmutablePoint(0, 0);}工厂构造函数当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。例如,工厂构造函数可能从缓存返回实例,或者它可能返回子类型的实例。 下面的示例演示一个工厂构造函数从缓存返回的对象:class Logger { final String name; bool mute = false; // _cache 是一个私有库,幸好名字前有个 _ 。 static final Map<String, Logger> _cache = <String, Logger>{}; factory Logger(String name) { if (_cache.containsKey(name)) { return _cache[name]; } else { final logger = new Logger._internal(name); _cache[name] = logger; return logger; } } Logger._internal(this.name); void log(String msg) { if (!mute) { print(msg); } } }注意:工厂构造函数不能用 this。方法方法就是为对象提供行为的函数。实例方法对象的实例方法可以访问实例变量和 this 。以下示例中的 distanceTo() 方法是实例方法的一个例子:import ‘dart:math’;class Point { num x; num y; Point(this.x, this.y); num distanceTo(Point other) { var dx = x - other.x; var dy = y - other.y; return sqrt(dx * dx + dy * dy); } }setters 和 Getters是一种提供对方法属性读和写的特殊方法。每个实例变量都有一个隐式的 getter 方法,合适的话可能还会有 setter 方法。你可以通过实现 getters 和 setters 来创建附加属性,也就是直接使用 get 和 set 关键词:class Rectangle { num left; num top; num width; num height; Rectangle(this.left, this.top, this.width, this.height); // 定义两个计算属性: right and bottom. num get right => left + width; set right(num value) => left = value - width; num get bottom => top + height; set bottom(num value) => top = value - height;}main() { var rect = new Rectangle(3, 4, 20, 15); assert(rect.left == 3); rect.right = 12; assert(rect.left == -8);}借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。注: 不论是否显式地定义了一个 getter,类似增量(++)的操作符,都能以预期的方式工作。为了避免产生任何向着不期望的方向的影响,操作符一旦调用 getter ,就会把他的值存在临时变量里。抽象方法Instance , getter 和 setter 方法可以是抽象的,也就是定义一个接口,但是把实现交给其他的类。要创建一个抽象方法,使用分号(;)代替方法体: abstract class Doer { // …定义实例变量和方法… void doSomething(); // 定义一个抽象方法。 } class EffectiveDoer extends Doer { void doSomething() { // …提供一个实现,所以这里的方法不是抽象的… } }枚举类型枚举类型,通常被称为 enumerations 或 enums ,是一种用来代表一个固定数量的常量的特殊类。声明一个枚举类型需要使用关键字 enum : enum Color { red, green, blue }在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。 assert(Color.red.index == 0); assert(Color.green.index == 1); assert(Color.blue.index == 2);要得到枚举列表的所有值,可使用枚举的 values 常量。 List<Color> colors = Color.values; assert(colors[2] == Color.blue); 你可以在 switch 语句 中使用枚举。如果 e 在 switch (e) 是显式类型的枚举,那么如果你不处理所有的枚举值将会弹出警告: enum Color { red, green, blue } // … Color aColor = Color.blue; switch (aColor) { case Color.red: print(‘Red as roses!’); break; case Color.green: print(‘Green as grass!’); break; default: // Without this, you see a WARNING. print(aColor); // ‘Color.blue’ }枚举类型有以下限制你不能在子类中混合或实现一个枚举。你不能显式实例化一个枚举。为类添加特征:mixinsmixins 是一种多类层次结构的类的代码重用。要使用 mixins ,在 with 关键字后面跟一个或多个 mixin 的名字。下面的例子显示了两个使用mixins的类: class Musician extends Performer with Musical { // … }class Maestro extends Person with Musical, Aggressive, Demented { Maestro(String maestroName) { name = maestroName; canConduct = true; } }要实现 mixin ,就创建一个继承 Object 类的子类,不声明任何构造函数,不调用 super 。例如: abstract class Musical { bool canPlayPiano = false; bool canCompose = false; bool canConduct = false; void entertainMe() { if (canPlayPiano) { print(‘Playing piano’); } else if (canConduct) { print(‘Waving hands’); } else { print(‘Humming to self’); } } }类的变量和方法使用 static 关键字来实现类变量和类方法。只有当静态变量被使用时才被初始化。静态变量, 静态变量(类变量)对于类状态和常数是有用的: class Color { static const red = const Color(‘red’); // 一个恒定的静态变量 final String name; // 一个实例变量。 const Color(this.name); // 一个恒定的构造函数。 } main() { assert(Color.red.name == ‘red’); }静态方法, 静态方法(类方法)不在一个实例上进行操作,因而不必访问 this 。例如: import ‘dart:math’; class Point { num x; num y; Point(this.x, this.y); static num distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } main() { var a = new Point(2, 2); var b = new Point(4, 4); var distance = Point.distanceBetween(a, b); assert(distance < 2.9 && distance > 2.8); }注:考虑到使用高阶层的方法而不是静态方法,是为了常用或者广泛使用的工具和功能。你可以将静态方法作为编译时常量。例如,你可以把静态方法作为一个参数传递给静态构造函数。抽象类使用 abstract 修饰符来定义一个抽象类,该类不能被实例化。抽象类在定义接口的时候非常有用,实际上抽象中也包含一些实现。如果你想让你的抽象类被实例化,请定义一个 工厂构造函数 。抽象类通常包含 抽象方法。下面是声明一个含有抽象方法的抽象类的例子: // 这个类是抽象类,因此不能被实例化。 abstract class AbstractContainer { // …定义构造函数,域,方法… void updateChildren(); // 抽象方法。 }下面的类不是抽象类,因此它可以被实例化,即使定义了一个抽象方法: class SpecializedContainer extends AbstractContainer { // …定义更多构造函数,域,方法… void updateChildren() { // …实现 updateChildren()… } // 抽象方法造成一个警告,但是不会阻止实例化。 void doSomething(); }类-隐式接口每个类隐式的定义了一个接口,含有类的所有实例和它实现的所有接口。如果你想创建一个支持类 B 的 API 的类 A,但又不想继承类 B ,那么,类 A 应该实现类 B 的接口。一个类实现一个或更多接口通过用 implements 子句声明,然后提供 API 接口要求。例如: // 一个 person ,包含 greet() 的隐式接口。 class Person { // 在这个接口中,只有库中可见。 final _name; // 不在接口中,因为这是个构造函数。 Person(this._name); // 在这个接口中。 String greet(who) => ‘Hello, $who. I am $_name.’; } // Person 接口的一个实现。 class Imposter implements Person { // 我们不得不定义它,但不用它。 final _name = “”; String greet(who) => ‘Hi $who. Do you know who I am?’; } greetBob(Person person) => person.greet(‘bob’); main() { print(greetBob(new Person(‘kathy’))); print(greetBob(new Imposter())); }这里是具体说明一个类实现多个接口的例子: class Point implements Comparable, Location { // … }类-扩展一个类使用 extends 创建一个子类,同时 supper 将指向父类: class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } // … } class SmartTelevision extends Television { void turnOn() { super.turnOn(); _bootNetworkInterface(); _initializeMemory(); upgradeApps(); } // … }子类可以重载实例方法, getters 方法, setters 方法。下面是个关于重写 Object 类的方法 noSuchMethod() 的例子,当代码企图用不存在的方法或实例变量时,这个方法会被调用。 class A { // 如果你不重写 noSuchMethod 方法, 就用一个不存在的成员,会导致NoSuchMethodError 错误。 void noSuchMethod(Invocation mirror) { print(‘You tried to use a non-existent member:’ + ‘${mirror.memberName}’); } }你可以使用 @override 注释来表明你重写了一个成员。 class A { @override void noSuchMethod(Invocation mirror) { // … } }如果你用 noSuchMethod() 实现每一个可能的 getter 方法,setter 方法和类的方法,那么你可以使用 @proxy 标注来避免警告。 @proxy class A { void noSuchMethod(Invocation mirror) { // … } }库和可见性import,part,library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线()开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。库可以分布式使用包。见 Pub Package and Asset Manager 中有关pub(SDK中的一个包管理器)。使用库使用 import 来指定如何从一个库命名空间用于其他库的范围。例如,Dart Web应用一般采用这个库 dart:html,可以这样导入: import ‘dart:html’;唯一需要 import 的参数是一个指向库的 URI。对于内置库,URI中具有特殊dart:scheme。对于其他库,你可以使用文件系统路径或package:scheme。包 package:scheme specifies libraries ,如pub工具提供的软件包管理器库。例如: import ‘dart:io’; import ‘package:mylib/mylib.dart’; import ‘package:utils/utils.dart’;指定库前缀如果导入两个库是有冲突的标识符,那么你可以指定一个或两个库的前缀。例如,如果 library1 和 library2 都有一个元素类,那么你可能有这样的代码: import ‘package:lib1/lib1.dart’; import ‘package:lib2/lib2.dart’ as lib2; // … var element1 = new Element(); // 使用lib1里的元素 var element2 = new lib2.Element(); // 使用lib2里的元素导入部分库如果想使用的库一部分,你可以选择性导入库。例如: // 只导入foo库 import ‘package:lib1/lib1.dart’ show foo; //导入所有除了foo import ‘package:lib2/lib2.dart’ hide foo;延迟加载库延迟(deferred)加载(也称为延迟(lazy)加载)允许应用程序按需加载库。下面是当你可能会使用延迟加载某些情况:为了减少应用程序的初始启动时间;执行A / B测试-尝试的算法的替代实施方式中;加载很少使用的功能,例如可选的屏幕和对话框。为了延迟加载一个库,你必须使用 deferred as 先导入它。import ‘package:deferred/hello.dart’ deferred as hello; 当需要库时,使用该库的调用标识符调用 LoadLibrary()。greet() async { await hello.loadLibrary(); hello.printGreeting(); }在前面的代码,在库加载好之前,await关键字都是暂停执行的。有关 async 和 await 见 asynchrony support 的更多信息。您可以在一个库调用 LoadLibrary() 多次都没有问题。该库也只被加载一次。当您使用延迟加载,请记住以下内容:延迟库的常量在其作为导入文件时不是常量。记住,这些常量不存在,直到迟库被加载完成。你不能在导入文件中使用延迟库常量的类型。相反,考虑将接口类型移到同时由延迟库和导入文件导入的库。Dart隐含调用LoadLibrary()插入到定义deferred as namespace。在调用LoadLibrary()函数返回一个Future。库的实现用 library 来来命名库,用part来指定库中的其他文件。 注意:不必在应用程序中(具有顶级main()函数的文件)使用library,但这样做可以让你在多个文件中执行应用程序。声明库利用library identifier(库标识符)指定当前库的名称:// 声明库,名ballgame library ballgame; // 导入html库 import ‘dart:html’; // …代码从这里开始… 关联文件与库添加实现文件,把part fileUri放在有库的文件,其中fileURI是实现文件的路径。然后在实现文件中,添加部分标识符(part of identifier),其中标识符是库的名称。下面的示例使用的一部分,在三个文件来实现部分库。第一个文件,ballgame.dart,声明球赛库,导入其他需要的库,并指定ball.dart和util.dart是此库的部分: library ballgame; import ‘dart:html’; // …其他导入在这里… part ‘ball.dart’; part ‘util.dart’; // …代码从这里开始…第二个文件ball.dart,实现了球赛库的一部分:part of ballgame; // …代码从这里开始…第三个文件,util.dart,实现了球赛库的其余部分:part of ballgame; // …Code goes here…重新导出库(Re-exporting libraries)可以通过重新导出部分库或者全部库来组合或重新打包库。例如,你可能有实现为一组较小的库集成为一个较大库。或者你可以创建一个库,提供了从另一个库方法的子集。 // In french.dart: library french; hello() => print(‘Bonjour!’); goodbye() => print(‘Au Revoir!’); // In togo.dart: library togo; import ‘french.dart’; export ‘french.dart’ show hello; // In another .dart file: import ’togo.dart’; void main() { hello(); //print bonjour goodbye(); //FAIL }异步的支持Dart 添加了一些新的语言特性用于支持异步编程。最通常使用的特性是 async 方法和 await 表达式。Dart 库大多方法返回 Future 和 Stream 对象。这些方法是异步的:它们在设置一个可能的耗时操作(比如 I/O 操作)之后返回,而无需等待操作完成当你需要使用 Future 来表示一个值时,你有两个选择。使用 async 和 await使用 Future API同样的,当你需要从 Stream 获取值的时候,你有两个选择。使用 async 和一个异步的 for 循环 (await for)使用 Stream API使用 async 和 await 的代码是异步的,不过它看起来很像同步的代码。比如这里有一段使用 await 等待一个异步函数结果的代码:await lookUpVersion()要使用 await,代码必须用 await 标记 checkVersion() async { var version = await lookUpVersion(); if (version == expectedVersion) { // Do something. } else { // Do something else. } }你可以使用 try, catch, 和 finally 来处理错误并精简使用了 await 的代码。 try { server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044); } catch (e) { // React to inability to bind to the port… }声明异步函数一个异步函数是一个由 async 修饰符标记的函数。虽然一个异步函数可能在操作上比较耗时,但是它可以立即返回-在任何方法体执行之前。 checkVersion() async { // … } lookUpVersion() async => / … */;在函数中添加关键字 async 使得它返回一个 Future,比如,考虑一下这个同步函数,它将返回一个字符串。String lookUpVersionSync() => ‘1.0.0’;如果你想更改它成为异步方法-因为在以后的实现中将会非常耗时-它的返回值是一个 Future 。Future<String> lookUpVersion() async => ‘1.0.0’;请注意函数体不需要使用 Future API,如果必要的话 Dart 将会自己创建 Future 对象使用带 future 的 await 表达式一个 await表达式具有以下形式await expression在异步方法中你可以使用 await 多次。比如,下列代码为了得到函数的结果一共等待了三次。 var entrypoint = await findEntrypoint(); var exitCode = await runExecutable(entrypoint, args); await flushThenExit(exitCode);在 await 表达式中, 表达式 的值通常是一个 Future 对象;如果不是,那么这个值会自动转为 Future。这个 Future 对象表明了表达式应该返回一个对象。await 表达式 的值就是返回的一个对象。在对象可用之前,await 表达式将会一直处于暂停状态。如果 await 没有起作用,请确认它是一个异步方法。比如,在你的 main() 函数里面使用await,main() 的函数体必须被 async 标记: main() async { checkVersion(); print(‘In main: version is ${await lookUpVersion()}’); }结合 streams 使用异步循环一个异步循环具有以下形式: await for (variable declaration in expression) { // Executes each time the stream emits a value. }表达式 的值必须有Stream 类型(流类型)。执行过程如下:在 stream 发出一个值之前等待执行 for 循环的主体,把变量设置为发出的值。重复 1 和 2,直到 Stream 关闭如果要停止监听 stream ,你可以使用 break 或者 return 语句,跳出循环并取消来自 stream 的订阅 。如果一个异步 for 循环没有正常运行,请确认它是一个异步方法。 比如,在应用的 main() 方法中使用异步的 for 循环时,main() 的方法体必须被 async 标记。 main() async { await for (var request in requestServer) { handleRequest(request); } } ...

December 17, 2018 · 14 min · jiezi