mac-打开ios模拟器

命令行输入open -a Simulatorsimulator:模拟器 关于open命令:open,使用关联的程序打开文件,例:open a.txt会用文本编辑打开a.txt,open b.jpg会使用预览打开b.jpgopen -e,强制使用文本编辑程序打开文件open -a,自行选择程序打开文件,例:open -a Preview b.jpg会使用预览打开b.jpg,另外使用此命令输入已安装的程序名可直接打开,而open则需要知道程序存放的路径才行,例:open -a Preview等同于open /Applications/Preview.app

November 4, 2019 · 1 min · jiezi

fluuter制作微信简单介绍

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

October 18, 2019 · 1 min · jiezi

Flutter-App-软件调试指南

前言推荐:Android学习PDF+架构视频+面试文档+源码笔记 在实际开发中,测试和调试所占的时间比例,在总开发时间中还是比较高的。在修复产品缺陷时,我们通常需要实时观察某个对象的值。虽然可以通过Log的形式进行输出,但在某些情形下,使用更好的调试工具可以使观察这些值变得更加方便。想象一下,如果需要观察一个集合,或者一个对象中所有变量的值,单纯地使用Log需要怎么做?可能会想到用循环,也可能会在输出Log的代码中多次运用“.”运算符对对象内的变量取值。这使得编写Log输出语句本身变得复杂,再加上可能还会冒着空指针的风险。 本文涵盖了 Flutter App 代码的所有调试方式,通过本场 Chat 的学习,您将会得到以下知识: 认识 Dart 语言检查器;如何在 IDE 中进行单步调试;打印 Log 的技巧;利用 Dart 语言中的“断言”;如何查看界面 Widget 树形层级;怎样获取语义树。下面我们来逐一进行学习。 认识 Dart 语言检查器在运行应用程序前,使用Dart语言检查器,通过分析代码,可以帮助开发者排除一些代码隐患。当然,如果读者使用的是Android Studio,Dart检查器在默认情况下会自动启用。若要手动测试代码,可以在工程根目录下执行: flutter analyze命令,检查结果会稍后显示在命令行对话框中。 比如,在默认新建的计数器应用中,去掉一个语句结尾的分号: void _incrementCounter() { setState(() { _counter++ });}看到_counter++后面少了一个分号了吗?此时,运行Dart检查器,命令行输出: error - Expected to find ';' - lib\main.dart:32:15 - expected_token1 issue found. (ran in 8.9s)如何在 IDE 中进行单步调试在某些时候,我们需要进行单步调试。单步调试可以让程序逐条语句地进行,并可以看到当前运行的位置。另外,在单步调试过程中,还能实时关注相应范围内所有变量值的详细变化过程。 Android Studio中提供了单步调试功能。这和开发原生Android平台App时的单步调试方法一样,其具体步骤可以分为三步进行,第一步是标记断点,第二步是运行程序到断点处,第三步则是使用Debug工具进行调试。下面以默认的计数器应用为例,观察代码中_counter值的变化,体会单步调试的全过程。 第一步是标记断点,既然要观察_counter值的变化,则在每次_counter值发生变化后添加断点,观察数值变化是最理想的,因此在行号稍右侧点击鼠标,把断点加载下图所示的位置。 添加断点后,相应的行号右侧将会出现圆形的断点标记,并且整行将会高亮显示。 到此,断点就添加好了,当然,还可以同时添加多个断点,以便实现多个位置的调试。 接下来则是运行程序。和之前的运行方式不同,这一次需要以调试模式启动App。方法是点击Android Studio上方工具栏的小虫子图标,如下图所示: 稍等片刻,程序就启动了。由于我们添加断点的位置在程序启动后会被立即运行到,因此,无需其他操作,即可进入调试视图。如果断点位置并不是在程序一启动就执行,则需要手动让程序运行到断点位置。下图展示了代码运行到断点位置时的IDE视图,它自动进入了Debug视图模式: 这里介绍两种方法来获取_counter的值,一种是在代码处,通过执行表达式的方式,如下图所示: 在相应的变量上点右键,接着在弹出的菜单中选择计算表达式(Evaluate Expression),最后在弹出的对话框中点击Evaluate按钮,得到运算结果如下图所示: ...

October 17, 2019 · 9 min · jiezi

干货Flutter-原理与闲鱼深度实践

王康(正物)—— Flutter 官方成员 阿里巴巴技术专家,之前主要负责 Flutter 在闲鱼中的混合开发体系,目前重点关注 Flutter 深入度以及生态相关的工作。本文将分享三方面内容, Flutter 的原理、 Flutter 在闲鱼中的应用,最后介绍我们在深度方面的一些探索。01、Flutter 原理 当我们谈到跨平台框架时,可能会想到很多备选方案。包括早期的 HTML 和 Cordova , 后来的 React Native , Weex ,以及这两年很是流行的 Flutter ,它们都在不同阶段不同程度上解决了我们对跨平台的诉求。如果我们从一些关键指标包括动态性、性能来观察,他们的区别还比较明显。 HTML 和 Cordova 具有最好的动态性,但他们的性能却是最差的,RN / Weex 具有良好的动态性。Flutter 则是一个纯原生的设计,其设计使它天生具有很好的性能与跨端一致性。 Flutter 是如何实现优秀的性能和跨端一致性的呢?从设计上可以看出 Flutter 在操作系统之上包含了三个层次。最下面是平台相关的嵌入层,其向上提供一个 Surface 用以绘制,建立了相关的线程模型和事件循环机制。在此之上则是一个平台无关的引擎,包括用于绘制的 Skia ;Dart 的运行时,开发模式下包括一个解释器;还有一部分是文本绘制相关内容。最上面就是用 Dart 语言编写的 Flutter 框架,也是我们最常接触到的内容。Flutter 框架包含一个完整分层的 UI 框架,从基础的 Foundation 库,到动画手势,再到渲染,之上又提供了各种丰富的 Widget 库。为了方便开发者使用, Flutter 还提供了两套不同风格的组件库,针对安卓的 Material Design 的组件库和针对 iOS 的 Cupertino 风格的组件库。 从这个设计可以看出,Flutter 和平台相关的内容,其实只提供 Surface 和线程/事件循环模型的嵌入层部分。这种类似用游戏引擎的方式来开发应用的设计很好解释了为什么它具有优秀的跨端一致性。 ...

October 16, 2019 · 3 min · jiezi

干货-把Flutter扩展到微信小程序端的探索

Google Flutter是一个非常优秀的跨端框架,不仅可以运行在Android、 iOS平台,而且可以支持Web和桌面应用。在国内小程序是非常重要的技术平台,我们也一直思考能否把Flutter扩展到小程序端?我们团队之前已经开源了Alita项目(https://github.com/areslabs/a...),Alita可以把React Native的代码转换并运行在微信小程序平台。受此启发,我们认为同样是声明式UI框架的Flutter同样可以运行在小程序平台。 所以,我们发起了flutter_mp(https://github.com/areslabs/f...)开源项目。以微信小程序为例,不过现阶段,flutter_mp项目还处于早期的实验阶段,很多功能还在探索规划中,欢迎大家在Github上随时关注我们的最新进展,或者参与项目共同探索。 原理简介 虽然还有诸多功能未完成,我们先来谈谈整个flutter_mp的实现原理。篇幅原因,下面我们将只对flutter_mp几个重要的部分进行简单说明。 先看下flutter_mp的实际效果: Flutter版官方layout样例: 通过flutter_mp转换并运行在小程序端效果 声明式UI的处理 Flutter是声明式UI框架,声明式UI只需要向框架描述UI长什么样子而不用关心框架具体的实现细节,具体到Flutter,上层的UI描述使用底层的skia图形引擎处理就是原生Flutter,而把底层处理换成html/css/canvas就是flutter_web,flutter_mp则是探索在类小程序上对这些UI描述的处理。 我们看一个最简单例子 var x = 'Hello World'Center( child: Text(x));对于上面的UI结构,我们只需要在小程序的wxml文件里,用如下的结构对应就OK了。 // wxml部分<Center> <Text>{{x}}</Text></Center>// js 部分Component({ data: { x: 'Hello World' }}) 虽然实际的结构要比上面的情况复杂的多,不过通过上面简单的例子,我们知道起码要做两个事情: 我们需要根据Flutter代码生成相关小程序wxml模版文件 收集wxml渲染需要的数据,放置到小程序组件的data字段。 wxml结构生成 我们知道小程序是无法动态操作节点的,wxml结构需要预先生成,所以Flutter运行在小程序之前,会存在一个编译打包阶段,这个阶段会遍历Dart代码, 根据一定规则生成wxml文件(编译阶段还会做下文将要提到的另外一个重要事情 --- 把Dart编译为js)。 具体的,我们首先会将Dart源码处理为可分析的AST结构,AST是源代码的树型表示结构。然后我们深度遍历这份AST语法树结构,生成目标wxml,整个过程如下: 构建wxml结构的难点在于:Flutter不仅是声明式UI还是“值UI”,什么叫“值UI”?简单来说,Flutter把UI看成是一个普通的值,类似于字符串,数字一样的值,既然是一个普通的值,就可以参与所有的控制流程,可以是函数的返回值也可以是函数参数等等。而小程序的wxml虽然也是声明式UI,却不是“值UI”,wxml更加像模版,更加的静态。怎么用静态的wxml表达动态的“值UI”是构建wxml结构的关键所在。 看个例子 Widget getX() { if (condition1) { return Text('Hello'); } else if (condition2) { return Container( child: ... ); } else if (condition3) { return Center( child: ... ); } ...}Widget x = getX();Center( child: x // < --- 如何处理这里的 x??);这里的child: x x是一个动态值,它的具体值需要在运行阶段才能确定,它可能是任意的Widget,如何在静态的wxml上处理这里动态的x?受Alita框架的启发,这里主要是借助于小程序template的动态性(template的is属性可以接受变量值)。有如下几步: ...

October 16, 2019 · 1 min · jiezi

各种各样的镜像加速

各种各样的镜像加速mirrors-for-coder这里做一个集中,尽管以前都是遇到时立即搜索,但是集中一下之后,看起来也很壮观的。 当然,欢迎完善它。 https://github.com/hedzr/mirr...China MirrorsGitHub Clone通过HTTPS协议Clone仓库的话,可能会遇到速度很慢的情况。 根据经验,在慢的时候中断Clone捎带片刻重复命令的话,你可能会得到正常速度,这种偷鸡的策略适合于小小仓库。 对于大型仓库,改走SSH协议进行clone的话,走到正常速度的几率较大,但此时的速度相较于HTTPS而言通常会有所损耗。 但下面还有一种较为费事的方法,通过修改 hosts 文件来完成提速,无需科学也无需代理加速也无需镜像加速(GitHub是不太可能有镜像的)。具体来说请接下去阅读: 首先在 https://www.ipaddress.com/ 查询这三个域名的地址: github.comassets-cdn.github.comgithub.global.ssl.fastly.net然后按照查询的结果填写到 /etc/hosts 中,windows用户请查找 %WINDIR%/system32/drivers/etc/hosts 文件。请注意修改 hosts 文件通常需要 sudo 权限 或者管理员权限。修改内容如同下面: 140.82.118.3 github.com185.199.109.153 assets-cdn.github.com185.199.111.153 assets-cdn.github.com185.199.108.153 assets-cdn.github.com185.199.110.153 assets-cdn.github.com151.101.113.194 github.global.ssl.fastly.net如果你有国外的服务器,也可以通过dig指令来查找: $ dig github.com +short140.82.118.3Docker CEDocker CE 的具体加速办法有很多种,然而各种版本的本质都是一样的,一般来说你需要找到 docker daemon 的配置文件 /etc/docker/daemon.json,然后修改它像这样: { "insecure-registries" : [ "registry.mirrors.aliyuncs.com" ], "debug" : true, "experimental" : false, "registry-mirrors" : [ "https://docker.mirrors.ustc.edu.cn", "https://dockerhub.azk8s.cn", "https://reg-mirror.qiniu.com", "https://registry.docker-cn.com" ]}如果你在这个文件中自定义了其他项目,或者这个文件中已经存在其他定义,请注意保持。 参考:https://docs.docker.com/engin... Ubuntu Apt Source如果你使用桌面版本,则 Ubuntu 的软件源设置中,你可以选取最近的地区,例如中国大陆,从而加速软件包下载速度。 ...

October 15, 2019 · 4 min · jiezi

Flutter-命名路由注册-跳转传参接收数据返回数据

注册: MaterialApp( routes: <String, WidgetBuilder> { 'myrouter': (BuildContext context) => new MyRouter(), }, );带参数跳转 Navigator.of(context).pushNamed('myrouter',arguments: "这是传过去的参数");跳转界面接收参数 @override Widget build(BuildContext context) { //获取路由参数 var args=ModalRoute.of(context).settings.arguments }返回时候,传参数 Navigator.of(context).pop('这个是要返回给上一个页面的数据');返回到的上一页接收参数(修改上面的跳转方法) Navigator.of(context).pushNamed('myrouter',arguments: "这是传过去的参数").then((value){ print("value===="+value.toString()); });

October 15, 2019 · 1 min · jiezi

Swift-UI对Flutter的意义JSConf-2019归来记未来属于声明式编程丨体验科技精选第-4-期

这里是蚂蚁金服体验科技精选 第 4 期,本期内容包括原创精选、蚂蚁前端动态和行业动态,希望你喜欢! 蚂蚁前端动态Ant Design https://github.com/ant-design... 九月上旬,Ant Design 又招募到三位优秀的社区协作者@yoyo837 @shaodahong @orzyyyy ,相信其社区生态将会更加繁荣! TechUI https://www.yuque.com/yuque/h... TechUI 9 月上旬新增高级成员搜索组件以及标签设置模板。Kitchen 发布 2.16.0 新增设计资产功能模块,TechUI 设计资产即拖即用 原创精选从链式调用到管道组合 https://zhuanlan.zhihu.com/p/... 如果让你在不用 this 和原型链,不用 ES6 Generator/Iterator,不用箭头函数的前提下,实现「惰性求值」,你是不是想到使用链式调用实现?但初具规模之后,链式调用带来的问题该如何解决? 未来属于声明式编程 http://djyde.github.io/blog/d... 提升开发效率,我们应该去想如何尽量让开发者声明式地编写代码,而不是只去想我们在 Serverless 上能做什么。 行业动态JSConf 2019 归来记 https://www.yuque.com/zhenzis... 作者参加了 JSConf 2019, 归来后写下此文。本文将着重分享:TDCD、Make it boring、Web norms of the world、Tour de bikeshare 这四个 Talk,希望给大家带来一些信息和启发。 尤雨溪:在框架设计中寻求平衡 https://zhuanlan.zhihu.com/p/... 当你试图去设计一个框架时,最佳平衡点在哪?或许说是否存在一个完美的平衡点?它又是否是一个单一、完美的平衡点,甚至是以 JS 开发人员作为一个整体的最佳平衡点? MVVM框架的数据状态管理的发展与探索 https://github.com/farzer/blo... 前端应用日渐复杂,页面状态的可控制及可跟踪已成为开发和调试的重要手段,显然我们有必要了解状态管理方案可以解决什么问题,解决问题的核心方式是什么。 抖音研发实践 https://mp.weixin.qq.com/s/Dr... 基于二进制文件重排的解决方案,APP 启动速度提升超 15%。启动是 App 给用户的第一印象,对用户体验至关重要。为了保证抖音在业务迅速迭代情况下的高效启动,抖音 iOS 客户端团队做了大量优化工作及开拓性探索,将应用启动速度提高了约 15%。 ...

October 14, 2019 · 1 min · jiezi

Flutter-Text文本组件详解

示例 APIText,很常用的一个Widget;用于显示简单样式文本,它包含一些控制文本显示样式的一些属性 text构造方法源码: /// If the [style] argument is null, the text will use the style from the /// closest enclosing [DefaultTextStyle]. /// /// The [data] parameter must not be null. const Text( this.data, { Key key, this.style, this.strutStyle, this.textAlign, this.textDirection, this.locale, this.softWrap, this.overflow, this.textScaleFactor, this.maxLines, this.semanticsLabel, this.textWidthBasis, }) : assert( data != null, 'A non-null String must be provided to a Text widget.', ), textSpan = null, super(key: key); /// Creates a text widget with a [InlineSpan]. /// /// The following subclasses of [InlineSpan] may be used to build rich text: /// /// * [TextSpan]s define text and children [InlineSpan]s. /// * [WidgetSpan]s define embedded inline widgets. /// /// The [textSpan] parameter must not be null. /// /// See [RichText] which provides a lower-level way to draw text. const Text.rich( this.textSpan, { Key key, this.style, this.strutStyle, this.textAlign, this.textDirection, this.locale, this.softWrap, this.overflow, this.textScaleFactor, this.maxLines, this.semanticsLabel, this.textWidthBasis, }) : assert( textSpan != null, 'A non-null TextSpan must be provided to a Text.rich widget.', ), data = null, super(key: key);参数详解: ...

October 9, 2019 · 3 min · jiezi

Flutter-常用库及时使用入门级方法一些经常用到的

UI相关flutter_screenutil flutter_screenutil: ^0.6.0 https://pub.flutter-io.cn/pac... flutter屏幕适应方案,让你的UI在不同尺寸的屏幕上能够显示合理的布局! 使用方法(入门): https://pub.flutter-io.cn/pac... flutter_swiperflutter_swiper: ^1.1.6 https://pub.flutter-io.cn/pac... flutter最强大的siwiper, 多种布局方式,无限轮播,Android和IOS双端适配! 使用方法(入门): https://pub.flutter-io.cn/pac... fluttertoast fluttertoast: ^3.1.3 https://pub.flutter-io.cn/pac... 用于Android和ios的toast库! 使用方法(入门): https://pub.flutter-io.cn/pac... azlistview azlistview: ^0.1.2 https://pub.flutter-io.cn/pac... 城市选择器! 使用方法(入门): https://pub.flutter-io.cn/pac... flutter_datetime_picker flutter_datetime_picker: ^1.2.6 https://pub.flutter-io.cn/pac... 日期选着三级联动! 使用方法(入门): https://pub.flutter-io.cn/pac... pin_input_text_field pin_input_text_field: ^2.0.1 https://pub.flutter-io.cn/pac... Flutter平台上用来展示不同样式的验证码UI! 使用方法(入门): https://pub.flutter-io.cn/pac...

October 8, 2019 · 1 min · jiezi

跨平台APP开发框架flutter

flutter实践数加产品线完整的闭环有一个面向用户的APP端,由于公司没有native app开发,所以选用了跨平台技术开发,任务也自然交给了前端。技术选型由于大家都没有APP开发的经验,技术选型显得尤为重要。本着用新不用旧的原则,技术团队对flutter这个极其新的东西产生了浓厚的兴趣。经过一系列调研,对比react native,以下便是差异之处。 基础特性RNflutter语言JSDart渲染机制通过JS代码调用原生UI渲染界面,效率要慢一些,UI统一性也并不能做到完全统一,只能牺牲部分设计框架自带UI体系,直接与更底层的skia交互,理论上效率要更高,并且能保证在不同平台上的一致表现生态有大量的开源组件以及成熟的解决办法刚开始兴起,有一些高质量的开源组件,但数量不多上手难度简单对于前端工程师需要另学一门语言,布局方式也和CSS截然不同,上手偏难通过上述分析可以得知flutter作为一个新兴框架,其底层机制比RN更为优秀,但是由于处于起步阶段,生态上来说是比不上RN的,再次本着程序员对未知事物充满好奇的心,大家一致选择flutter。 项目搭建有对新事物的热情就必然要有采坑的准备,搭建项目时问题就层出不穷,iOS端cocoapods下了一天都没下下来,最终解决办法竟然是多试几次...接下来使用安卓模拟器控制台报错不断,原因是不能使用最新的安卓Q版本,原来flutter在GitHub上几千个issue是有道理的。虽然困难重重,但是花了一周时间终于把项目建好了,谷歌还是给了开发者一条活路。 编码体验在通读了一遍Dart文档加上flutter教程的基础上,本以为按照知乎大神们俩小时上手开发的速度应该可以马上高效的进行编码了,然而对着UI设计图,脑子空空如也,完全不知道该如何下手,无奈的又边翻阅文档边看技术博客一点一点把页面磊出来了,开始的开发速度简直不忍直视。 首先的难点在于flutter整个的布局规则已经大改了,需要理解很多新的概念,溢出,堆叠,边界约束全部都和CSS不一样。就拿溢出举例,CSS溢出是自然溢出,父元素样式决定子元素溢出表现,到flutter那是直接报错!是的,你没看错,完全不允许溢出,要先考虑内容是否过长来使用不一样的盒子。更别说还有一堆的概念比如长宽自动伸缩方式,对齐方式,堆叠情况下占满宽度的盒子等等,现在想起来都是一头的包。还有一个比较典型的就是flutter也是组件化理念,状态控制视图,但是组件之间通过类来进行蹩脚的通信实在是感觉麻烦,远没有react或者Vue简单直观。各种基础widget的设计纷繁杂乱,有的使用难度极高,到现在我还不明白ListView的builder模式是怎么工作的。打包部署本以为在构建项目时遇到的问题在部署时应该会重现,但实际打包过程出奇的流畅,只有在打IOS包的时候由于其平台的安全性而显得有点步骤冗长之外,基本没有别的问题,成功的能在双端真机上运行release包,项目开发到最后也是终于松了口气。 flutter的优点虽然对于前端程序员来说编码体验很糟糕,但是不得不说这个糟糕体验是在快速上手的前提下,深入理解加上大量的代码经验我相信同样能让flutter工程师高效的进行开发。flutter也有很多不可否认的闪光点的,在release环境下,速度和流畅度都有着非常优良的体验,并且整个项目做下来,几乎没有遇到过双端UI表现不一致的情况,谷歌这个承诺是达到了,不用为平台做特殊兼容对于之前受IE荼毒的前端工程师来说无疑是幸福愉悦的。 以上就是flutter开发的实践体验,总得来说flutter是一个优秀的框架,性能以及UI表现值得肯定,但是还是希望谷歌能够多照顾照顾开发者,大家时间都很宝贵,对于一个框架来说易用性也是一个很重要的因素。

October 7, 2019 · 1 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上实现高性能的动态模板渲染

背景最近小组在尝试使用一套阿里dinamicX的DSL,通过动态模板下发,实现Flutter端的动态化模板渲染;本来以为只是DSL到Widget的简单映射和数据绑定,但实际跑起来的效果出乎意料的差,列表卡顿严重,帧率丢失严重。这就让我们不得不深入Flutter的Framework层,去了解Widget的创建、布局以及渲染的过程。 为什么Native可行的方案在Flutter效果这么差在iOS和Android开发中,DSL到Native的方案其实并不陌生;Android中,我们就是通过编写XML文件来描述页面布局。Native的这种映射的方案,为什么在Flutter上,效果变得如此糟糕呢? 先通过一个简单的示例来看一下dinamicX DSL的定义: 可以看到DSL的设计与Android中的XML很相似,在我们的DSL中,每个节点的width和height属性,可以赋值两种特殊意义的值:match_parent和match_content。 match_parent:当前节点大小,尽量撑开到父节点大小; match_content:当前节点大小,尽量缩小到容纳子节点大小; 在Flutter中,并没有match_parent和match_content的概念。最初我们的想法很简单,在Widget的build方法中,如果属性是match_parent,就不断向上遍历,直到找到一个父节点有确定的宽高值为止;如果是match_content,遍历所有的子节点,获取子节点大小;一旦子节点存在match_content属性,会递归调用下去。 表面上看,做好每个节点的宽高计算的缓存,虽然达不到一次性线性布局,这样的开销也并不是很大。但我们忽略掉了一个很重要的问题:Widget是immutable的,只是包含了视图的配置信息,是非常轻量级的。在Flutter中,Widget会被不断的创建销毁,这会导致布局计算非常的频繁。 要解决这些问题,单单处理Widget是不够的,需要Element以及RenderObject上做更多的处理,这也就是我们为什么要考虑自定义Widget的原因。 接下来通过源码来了解Flutter中Widget的build、layout以及paint相关的逻辑。 认识三棵树我们通过一个简单的Widget——Opacity来了解一下Widget、Element、RenderObject。 Widget在Flutter中,万物皆是Widget,Widget是immutable的,只是包含了视图的配置信息的描述,是非常轻量级的,创建和销毁的开销比较小。 Opacity继承自RenderObjectWidget,其定义了两个比较关键的函数: RenderObjectElement createElement();RenderObject createRenderObject(BuildContext context);这正是我们要找的Element和RenderObject!这里只是定义了创建的逻辑,具体调用的时机我们继续往下看。 Element在SingleChildRenderObjectWidget可以看到创建了SingleChildRenderObjectElement对象。 Element是Widget的抽象,在Widget初始化的时候,调用Widget.createElement创建,Element持有Widget和RenderObject;BuildOwner通过遍历Element Tree,根据是否标记为dirty,构建RenderObject Tree;在整个视图构建过程中,起到了串联Widget和RenderObject的作用。 RenderObjectOpacity的createRenderObject函数创建了RenderOpacity对象,RenderObject真正提供给Engine层渲染所需要的数据,RenderOpacity的Paint方法中找到了真正绘制的地方: void paint(PaintingContext context, Offset offset) { if (child != null) { ... context.pushOpacity(offset, _alpha, super.paint); } } 通过RenderObject,我们可以处理layout、painting以及hit testing。这是我们在自定义Widget处理最多的事情。RenderObject只是定义了布局的接口,并未实现布局模型,RenderBox为我们提供了2D笛卡尔坐标系下的Box模型协议定义,大部分情况下,都可以继承于RenderBox,通过重载实现一个新的layout实现,paint实现,以及点击事件处理等; Flutter在Layout过程中的优化Flutter采用一次布局的方式,O(N)的线性时间来做布局和绘制。 如上图所示,在一次遍历中,父节点调用每个子节点的布局方法,将约束向下传递,子节点根据约束,计算自己的布局,并将结果传回给父节点; RelayoutBoundary优化当一个节点满足如下条件之一,该节点会被标记为RelayoutBoundary,子节点的大小变化不会影响到父节点的布局: parentUsesSize = false:父节点的布局不依赖当前节点的大小sizedByParent = true:当前节点大小由父节点决定constraints.isTight:大小为确定的值,即宽高的最大值等于最小值parent is not RenderObject:如果父节点不是RenderObject,子节点layout变化不需要通知父节点更新RelayoutBoundary的标记,子节点大小变化,不会通知父节点重新layout,重新paint,从而提高效率。 Element更新优化为什么Widget频繁创建销毁不会影响渲染性能呢? Element定义了updateChild的方法,最早在Element被创建,Framework调用mount的时候,以及RenderObject被标记为needsLayout执行RenderObject.performLayout等场景,会调用Element的updateChild方法; Element updateChild(Element child, Widget newWidget, dynamic newSlot) { ... if (child != null) { ... if (Widget.canUpdate(child.widget, newWidget)) { ... child.update(newWidget); ... } }}对于child和newWidget都不为空的情况,通过Widget.canUpdate来判断当前child Element是否可以更新而非重现创建的方式update。 ...

September 20, 2019 · 1 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

Dio-30发布支持Flutter-Web-和-Http20

Flutter 一周前发布了1.9 版本,其中最大特性是将Flutter Web合入了主分支。而今天,Dio 也正式发布了3.0.0,该版本最大的特性是支持Flutter Web 和 Http/2.0。 升级提示:由于Dio 3.0需要支持Flutter Web,需要对代码进行较大的重构,所以并不能完全向下兼容,2.1版本用户可参考 3.0升级指南 。支持Flutter Web开发者只需要将Flutter升级到1.9或更新的dev版本,然后将Dio升级到最新的3.x即可支持Flutter Web即可。 Http/2.0支持Http/2.0 有链接复用、头部压缩、二进制传输、服务端推送等重多特性。Dio 3.0版本后,官方提供了一个 dio_http2_adapter 插件(HttpClientAdapter)来支持Http/2.0。下面是官方示例: import 'package:dio/dio.dart';import 'package:dio_http2_adapter/dio_http2_adapter.dart';main() async { var dio = Dio() ..options.baseUrl = "https://google.com" ..interceptors.add(LogInterceptor()) ..httpClientAdapter = Http2Adapter( ConnectionManager(idleTimeout: 10000), ); Response<String> response; response = await dio.get("/?xx=6"); print(response.data);}可以看到只需要配置一下Http2Adapter即可。值得注意的是Http2Adapter需要一个ConnectionManager参数。ConnectionManager 主要职责是管理链接,是Http/2.0中链接复用策略的实现载体。官方提供了一个默认的ConnectionManager ,它的策略是同一个域名下的请求共享一个Socket链接,当请求完成时,连接默认继续保持15秒,开发者可以通过idleTimeout来自定义保持时间。开发者提供自己的ConnectionManager来自定义链接复用策略。 另外,Http2Adapter内部已经默认处理了重定向,我们可以通过以下代码验证: response.redirects.forEach((e){ print("redirect: ${e.statusCode} ${e.location}");});输出: redirect: 301 https://www.google.com/?xx=6redirect: 302 https://www.google.com.hk/url?sa=p&hl=zh-CN&pref=hkredirect&pval=yes&q=https://www.google.com.hk/%3Fxx%3D6&ust=1568810110125304&usg=AOvVaw0YbFhKFoslI0LPOPFcekGyredirect: 302 https://www.google.com.hk/?xx=6可以看到我们在大陆请求google时进行了三次重定向! 其它更新FormData 支持嵌套。删除了UploadFileInfo类,引入了MultipartFile 类;MultipartFile类不仅支持通过文件来构造上传头块,也支持通过Stream、Byte数组、字符串来构造。将CookieManager抽离成了单独的包;这是因为在Flutter web中不需要手动管理Cookie(浏览器会自动管理),因此将其抽为单独的插件按需引入会更合理。请求取消后,取消Error可以入队拦截器队列(2.1中取消的异常是直接抛给用户处理)。代码优化:API标准化、语义化;对核心代码进行了全部重构。详情请参考:https://github.com/flutterchi... 。

September 19, 2019 · 1 min · jiezi

Flutter设置背景图片

效果展示 前言在我们平时的开发中会经常使用到背景图片,下面我这介绍的是Container通过BoxDecoration来设置的 布局1、创建路由子页面Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("优选访客订阅功能"), ), body: buildBody() ); }Scaffold路由页面的骨架,我们在里面可以拼装出一个完整的路由页面 appBar创建导航栏 body构建页面主体结构 2、页面布局Widget buildBody() { return new Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ buildHeader(), // Row(children: <Widget>[ // Text( // '批量管理功能', // style:TextStyle( // fontSize: 16.0, // color: Color.fromRGBO(234,200,134,1) // ) // ) // ],) ], ); }为了后续方便维护,在页面布局时最好都拆分成不同的小模块来分开写,不然后期太难维护 Column即指沿水平或垂直方向排布子组件。Flutter中通过Row和Column来实现线性布局; 首先布局思路就是使用Column可以在垂直方向排列其子组件。 mainAxisAlignment然后在把里面的子元素都按主轴方向对齐 3、背景设置Widget buildHeader() { return new Container( height: 160.0, width: MediaQuery.of(context).size.width, decoration: BoxDecoration( image: DecorationImage( image: AssetImage("images/header.png"), fit: BoxFit.cover ) ), child: Column( mainAxisAlignment: MainAxisAlignment.center, // alignment: WrapAlignment.center, // crossAxisAlignment: WrapCrossAlignment.center, // runSpacing: 9.0, children: <Widget>[ Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( '批量管理功能', style:TextStyle( fontSize: 16.0, color: Color.fromRGBO(234,200,134,1) ) ) ], ), Wrap( runSpacing: 9.0, alignment: WrapAlignment.center, children: <Widget>[ Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( '${pageData['title']}', style:TextStyle( fontSize: 38.0, color: Color.fromRGBO(234,200,134,1) ) ) ], ), //自定义圆角 ClipRRect( borderRadius: BorderRadius.circular(12.5), child: Container( height: 25.0, width: 190.0, color: Color.fromRGBO(234,200,134,1), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( '${pageData['subTitle']}', textAlign: TextAlign.center, style: TextStyle(color: Color.fromRGBO(113,80,24,1)), ) ] ) ) ) ], ) ], ), ); }首先是把头部banner部分放在一个方法里面使用Container容器组件来包裹 ...

September 19, 2019 · 3 min · jiezi

Flutter-中文文档网站-fluttercn-正式发布

在通常的对 Flutter 介绍中,最耳熟能详的是下面四个特点: 精美 (Beautiful):充分的赋予和发挥设计师的创造力和想象力,让你真正掌控屏幕上的每一个像素。 极速 (Fast):基于 Skia 的硬件加速图形引擎,帮助你媲美原生应用的速度。高效 (Productive):Flutter 的 Stateful Hot Reload (热重载) 特性帮助你实时看到应用修改的结果。开放 (Open):不管是 Flutter 引擎还是 Dart 开发语言,甚至是工程团队的工作空间,Flutter 都在 GitHub 开源协作、与社区透明分享。Flutter 由 Google 主导,与全球社区的开发者共同协作开发。中国使用 Flutter 的开发者数量位居全球第一,同时也为 Flutter 的生态提供了很大的帮助和贡献。本文从 Open 的角度,为大家介绍在中国的 Flutter 社区里有哪些 最新的资源和更新。 Flutter 和 Dart 中文文档发布文档对任何技术项目的本地化都至关重要,维护一份高质量的 Flutter / Dart 文档,是过去几个月来我们一直在努力的工作。我们非常高兴的宣布:Flutter 社区中文资源网站 (flutter.cn)和 Flutter 中文文档 正式发布,欢迎大家的访问!我们同样为 Flutter 的 codelabs 制作了一个单独的二级页面在 codelabs.flutter-io.cn,欢迎大家访问。 同样在本地化的是 Dart 的中文文档网站 dart.cn,我们也欢迎大家贡献自己的时间,影响全球的中国开发者! 企业应用和实践Flutter 在 2018 年的成功并非偶然,原因不仅是 Flutter 产品本身的优秀,最重要的还是来自国内一线互联网公司以及广大开源技术爱好者对 Flutter 的支持,Flutter 团队也非常重视中国开发者市场,2018 年里两次重大的版本发布都是在中国的活动上宣布,1.0 正式版的发布,也在谷歌北京办公室举办了一场正式的活动。 ...

September 19, 2019 · 3 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开发之JSON解析

对于JSON格式的数据交互,想必大家不会陌生。JSON(全称JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式,JSON因为具有易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率等特性,通常被用在客户端与服务端的数据交互中。 对于JSON的基本知识,本文不做详细介绍,读者可以自行搜索资料进行学习。 手动解析手动解析通常应用在一些基本简单的场合,即数据结构不是很复杂的场景,手动解析JSON是指使用Flutter提供的dart:convert中内置的JSON解码器。它能够将原始JSON字符串传递给json.decode() 方法,然后在返回的Map<String, dynamic>中查找所需的值。 它不需要依赖任何第三方库,对于小项目来说很方便。 例如,有下面一个接口:https://jsonplaceholder.typic...,它的数据格式如下: { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}由于上面的数据格式比较简单,因此我们可以使用手动解析的方式来解析它。 final responseJson = json.decode(response.body);Map<String, dynamic> newTitle = responseJson ;print(newTitle['title']);//打印title的值当然,我们也可以新建一个实体类,然后将它解析到实体类中。 final responseJson = json.decode(response.body);print(responseJson.toString());Post postBean = Post.fromJson(responseJson); //Post为实体类对于数据结构不是很复杂的时候,使用fromJson来解析字段还好,但是如果数据结构比较复杂的话,手写fromJson、toJson就不太友好,并且容易出错。 借助工具解析在Android原生开发中,我们可以使用诸如Gson、FastJson等第三方库来帮助我们将JSON数据转成实体类。同样,在Flutter开发中,我们也可以使用插件或工具来一键生成实体类。 ...

August 21, 2019 · 1 min · jiezi

使用-Flutter-for-web-构建了-100-Widget-实时预览示例

使用 Flutter 开发已经接近一年时间了,但是在学习 Flutter 一些 Widget 的时候不能像前端那样有能直接在网站上实时看到效果,每次接触到一个新的 Widget 时都需要花费不少时间才能看到效果,所以我基于 Flutter for web 做了这个网站,目前已经包含 100+ Widgets 基本示例,后续计划增加更多的高级示例,希望可以对您有帮助。 简介Flutter Widget Livebook 是一个使用 Flutter for web 构建的可在线实时预览 Widget 示例的网站。 如果您有任何问题或者希望网站上有其他 Widget 示例,可以通过在 GitHub Repo 上打开一个 issue 来提问或者反馈,当然你也可以给我们提 PR。 立即访问 Flutter Widget Livebook:https://flutter-widget-livebook.blankapp.org GitHub:https://github.com/blankapp/flutter-widget-livebook 如果你觉得 Flutter Widget Livebook 对你有帮助,麻烦给个 Star 哦。效果展示 由于文章篇幅限制,无法把所有示例在此展示全部给你,你可以到我们的网站上查看全部示例。已知问题由于 Flutter for web 目前还是技术预览版,因此有一部分 Widgets(如大部分 Cupertino 风格的 Widgets) 并未在 flutter for web 上实现,所以目前整理的示例中暂未包含此部分。探讨如果您对此项目有任何建议或疑问,可以通过 Telegram 或微信与我进行讨论。 ...

August 18, 2019 · 1 min · jiezi

Flutter-页面间数据传递共享的几种常用方式

前言    在Android中,我们常遇到的场景就是在页面跳转(Frament,Activity)时候,要将当前的部分数据携带到另外一个页面中,供另外页面使用。这时候我们常用的就是使用Intent, Bundle等携带数据。    那么在Flutter的开发过程中,页面之间的数据传递也是必不可少的,又是怎么把一个页面的数据传递(共享)给另外一个页面,或者关闭当前页面并把当前页面的数据带给前一个页面。     本篇文章将会介绍Flutter中,页面面之间的数据传递(共享)的几种常见方式及场景。 在开始数据传递之前我们先创建一个传递数据的类 在Android中传递对象我们需要序列化实现Serializable或者Parcelable接口才能被传递,在Flutter中数据传递没有序列化的方法,直接就可以传递对象。定义一个简单的类如下:///用来传递数据的实体class TransferDataEntity { String name; String id; int age; TransferDataEntity(this.name, this.id, this.age);} 我们具体看看数据传递的方式 通过构造器(constructor)传递数据    通过构造器传递数据是一种最简单的方式,也是最常用的方式,在第一个页面,我们模拟创建一个我们需要传递数据的对象。当点击跳转的时候,我们把数据传递给DataTransferByConstructorPage页面,并把携带过来的数据展示到页面上。 创建一个传递数据对象 final data = TransferDataEntity("001", "张三丰", 18);定义一个跳转到DataTransferByConstructorPage页面的方法 _transferDataByConstructor(BuildContext context, TransferDataEntity data) { Navigator.push( context, MaterialPageRoute( builder: (context) => DataTransferByConstructorPage(data: data))); }在DataTransferByConstructorPage页面接收到数据并展示出来,代码如下 我们只需要做两件事:1.提供一个final变量 final TransferDataEntity data 2.提供一个构造器接收参数 DataTransferByConstructorPage({this.data}); ///通过构造器的方式传递参数class DataTransferByConstructorPage extends StatelessWidget { final TransferDataEntity data; DataTransferByConstructorPage({this.data}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("构造器方式"), ), body: Column( children: <Widget>[ Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text(data.id), ), Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text(data.name), ), Container( width: double.infinity, height: 40.0, alignment: Alignment.center, child: Text("${data.age}"), ) ], ), ); }} ...

August 8, 2019 · 4 min · jiezi

flutter混合开发小记之原生项目集成flutter模块

原生项目集成flutter1、新建项目根目录创建整个项目得根目录,然后再根目录里创建android或ios原生项目......这里默认原生项目创建完毕,期望得目录结构如下: 项目根目录/|── 原生项目文件夹/|── └── app/|── └── gradle/|── └── ...|── Flutter Module文件夹/|── └── ...接下来单独创建flutter模块得项目。 2、单独创建flutter module project(这里以Android Studio为例)File > New Flutter Project 选择 Flutter Module,然后按提示勾选填写完成创建(这里如果没有安装Dart SDK 和 flutter SDK需要另行安装) 完成后项目目录 与创建Flutter Application大致一致,区别在于pubspec.yaml文件中 Flutter Module Flutter Application 3、修改pubspec.yaml文件Flutter Module项目需要在pubspec.yaml文件中添加如下代码: module: androidPackage: com.XXX.flutter_module iosBundleIdentifier: com.XXX.flutterModule未完待续。。。

August 8, 2019 · 1 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微介绍

Flutter是什么?Flutter是谷歌的移动UI框架。使用Dart语言开发。 Flutter的作用:可以在iOS和Android上构建高质量的原生用户界面。 Flutter的历史:在2015年Dart开发者峰会上首次亮相;2018年12月5日,发布了1.0版本; 框架特性:1.热重载2.绚丽UI3.响应式 框架结构:1.Flutter engine2.Foundation library3.Design-specific widgets 扩展:Dart语言:由谷歌开发的网络编程语言,于2011年10月10日发布。

July 26, 2019 · 1 min · jiezi

Mac搭建Flutter开发环境

Flutter简介Flutter 是Google开发的一个移动跨平台(Android 和 iOS)的开发框架,使用的是 Dart 语言。和 React Native 不同的是,Flutter 框架并不是一个严格意义上的原生应用开发框架。Flutter 的目标是用来创建高性能、高稳定性、高帧率、低延迟的 Android 和 iOS 应用。并且开发出来的应用在不同的平台用起来跟原生应用具有一样的体验。 Flutter 包含了一个函数响应式框架( functional-reactive framework)、一个 2D 渲染引擎、直接可用的 Widget 库、和各种开发工具。这些组件在一起配合使用,可以帮助开发者完成设计、开发、测试和调试应用的工作。 WidgetWidget 是每个 Flutter 应用的基础组成部分,每个 Widget 是用户界面最基本的元素。和其他框架把 View、controller、 Layout 和其他资源分开定义不一样,Flutter 具有一致的、唯一的对象模型: Widget。 一个 Widget 具有如下的一些作用: 一个结构性的元素(比如 按钮或者菜单)一个元素的风格(比如 字体或者颜色)指定布局属性(比如 padding)也可以包含一些业务逻辑以及其他等等Widget 通过组合来组成特有的页面层级结构,每个 Widget 都内嵌在父 Widget 中,并继承父 Widget 的属性。 并且,Widget没有单独的 “application” 对象,根 Widget 就相当于application。 布局/样式首先从宏观上来说,Flutter 中的布局、样式中绝大多数的概念其实还是沿用了 CSS 中的概念。例如在布局方面与 CSS 中 flex 布局对应的有 Row、Column 两个 Widget,分别提供了水平和垂直两个方向的布局方式。再比如 Stack Widget 提供了一种 Widget 之间相互堆叠的机制,这又和 CSS 中的 position:absolute; 很像。 ...

July 15, 2019 · 3 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-17正式版发布

此次发布的版本是继上次 I/O大会众多重要功能发布以来的一次小更新。Flutter 1.7 包含了对 AndroidX 的支持,满足了 Play 商店近期对应用提出的要求,包含了一些新的和增强过的组件,修复了开发者们提出的 bug 等。 如果你已经安装并使用默认稳定构建渠道 (stable channel) 的 Flutter,只需要运行 flutter upgrade 命令即可升级到 1.7 版本。同时,如果你还没有安装Flutter,你还可以在 这个文档里 查看如何新安装 Flutter。 本次更新的内容包括: 支持 AndroidX支持 Android App Bundles 和 64 位的 Android 应用新的 widget 和框架的功能增强专注解决开发者反馈的实际问题不断状大的Flutter 社区支持 AndroidXAndroidX 是 Android 团队用于在 Jetpack 中开发、测试、打包和发布库以及对其进行版本控制的开源项目,帮助 Android 应用通过最新的组件保持更新而无需牺牲向后兼容性。目前 AndroidX 已经稳定,很多 Flutter packages 已经更新和支持它,Flutter 现在可以支持 创建一个 AndroidX 项目 (new Flutter project with AndroidX) 了,这也减少了与 Android 生态系统集成所你需要做的工作。当创建 Flutter 项目的时候,你可以通过添加 --androidx 来确保生成的项目文件支持 AndroidX,更多关于将项目迁移到 AndroidX 的相关信息,请访问 官方文档 上的说明。我们也在积极努力为使用了 AndroidX 和 Android 混合库的应用带去 AndroidX 或 Jetifier 的支持,也会将其作为 add-to-app 的中的一项来支持,接下来的文章中会为大家带来更多相关的内容。 ...

July 12, 2019 · 2 min · jiezi

运行起来flutter桌面应用

刚在前一篇文章里说flutter的引擎是针对平台的引擎而言,还在想那桌面环境作为一种平台也应该让flutter应用运行起来,因为引擎应当屏蔽平台而运行dart虚拟机,结果真的发现早就有人在做了!。 这意味着什么呢?! 这意味着我们只需关注视图展示与业务逻辑,写完不用运行设备就可以立即验证,所见即所得!桌面应用可以拉伸窗口成任意大小,可以马上验证屏幕适配的所有效果!这意味桌面应用也可以用flutter来实现,而且是真正的跨平台!MFC, GTK, electron都可以说再见了!这意味着设计湿也可以在自己的机器上运行最终效果,如果能建立方便的工具链那么视觉效果都可以直接交给设计湿来搞了!这将彻底解放码畜的生产力~这些都将大大提升开发和运行效率, 那真是爽了一啤呀!马上搞起来flutter SDK v1.8.0@stable (最新的v1.7.8+hotfix.3运行不起来)flutter-desktop 03d6f06d6@master git clone https://github.com/google/flutter-desktop-embedding.git desktopgit log --oneline -1> 03d6f06 (HEAD -> master, origin/master, origin/HEAD) Remove launch call on FLEViewController (#454)flutter --version> Flutter 1.8.0 • channel unknown • unknown source> Framework • revision 2fefa8c731 (11 days ago) • 2019-07-01 11:33:22 -0700> Engine • revision 45b66b722e> Tools • Dart 2.4.0cd desktop/exampleflutter runLinux上运行结果: 编译过程发现居然下载了WebSDK, 怀疑dart转成了js, 然后运行的是浏览器应用, 这让我的热情被浇了一盆冷水......虽然不那么native, 但是还是能带来不少便利,希望将来能更“原生”吧,像android那种程度。

July 12, 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-17-正式版发布

今天,我们非常高兴地向大家宣布又一个正式版本的发布 —— Flutter 1.7,这是继上次 I/O 时众多重要功能发布以来的一次小更新。Flutter 1.7 包含了对 AndroidX 的支持,满足了 Play 商店近期对应用提出的要求,包含了一些新的和增强过的组件,修复了开发者们提出的 bug 等。 如果你已经安装,并使用默认稳定构建渠道 (stable channel) 的 Flutter,要升级到 1.7 版本,只需要运行 flutter upgrade 即可。同时,你可以在 这个文档里 查看如何新安装 Flutter。 支持 AndroidXAndroidX 是 Android 团队用于在 Jetpack 中开发、测试、打包和发布库以及对其进行版本控制的开源项目,帮助 Android 应用通过最新的组件保持更新而无需牺牲向后兼容性。目前 AndroidX 已经稳定,很多 Flutter packages 已经更新和支持它,Flutter 现在可以支持 创建一个 AndroidX 项目 (new Flutter project with AndroidX) 了,这也减少了与 Android 生态系统集成所你需要做的工作。 当创建 Flutter 项目的时候,你可以通过添加 --androidx 来确保生成的项目文件支持 AndroidX,更多关于将项目迁移到 AndroidX 的相关信息,请访问 官方文档 上的说明。我们也在积极努力为使用了 AndroidX 和 Android 混合库的应用带去 AndroidX 或 Jetifier 的支持,也会将其作为 add-to-app 的中的一项来支持,接下来的文章中会为大家带来更多相关的内容。 ...

July 11, 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个人填坑指南详解第一步安装解压完flutter后,按照flutter的官方教程,首先需要在你的IDE或者编译器(vscode)里安装插件,分别是 flutter 和dart的插件(我使用的是AS,所以下文以AS为例) 第二步---配置环境变量由于在国内访问Flutter有时可能会受到限制,Flutter官方为中国开发者搭建了临时镜像,大家可以将如下环境变量加入到用户环境变量中 第三步进入Flutter的目录中,运行命令行脚本 第一个问题!!!!运行flutter doctor后,你会发现它提示你✗ Android license status unknown. 因此我们应该尝试运行flutter doctor --android-licenses 但是会报错,提示你应该去 sdk目录进行 sdkmanager --update,运行sdkmanager --update时又会出现找不到或无法加载主类的问题 解决方案经过不断的google,在GitHub flutter的i16025 issues中 有人提到 这是jdk版本的问题,原文(OpenJDK 10 was superseeded by OpenJDK 11, which doesn't implement java.se.ee at all. This means that the hack of adding --add-modules java.se.ee doesn't do anything anymore. It also means that OpenJDK 10 will be automatically removed from your system and replaced with OpenJDK 11 the next time you update, if your updates are configured properly. ...

July 10, 2019 · 1 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之-No-connected-devices-found-please-connect-a-device

Flutter工程无法找到Android真机或Android模拟器 一点run就提示 No connected devices found; please connect a device, or see flutter.io/setup for getting 管理员打开AndroidStudio也没有用 需要把在FLutter sdk 目录下运行如下命令 flutter config --android-sdk 自己的android sdk路径

July 10, 2019 · 1 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-自定义组件之贝塞尔曲线画波浪球

百度百科: 贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具,如PhotoShop等。在Flash4中还没有完整的曲线工具,而在Flash5里面已经提供出贝塞尔曲线工具。源码地址设计图效果--设计图地址 开发效果图 贝塞尔曲线画圆 如图当画圆时系数M约等于0.55228475,绘制时调用cubicTo(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y)进行绘制,绘制时以圆心为圆点,x轴、y轴为线划分成4分,进行绘制。 画路径代码void _canvasBesselPath(Path path) { Point p1 = Point(x: radius*2,y: radius); Point p2 = Point(x: radius,y: radius*2); Point p3 = Point(x: 0,y: radius); Point p4 = Point(x: radius,y: 0); if (isToRight) { if (percent <= 0.2) { p1.x = radius*2 + radius*percent/0.2; } else if (percent <= 0.4) { p4.x = p2.x = radius + radius*(percent-0.2)/0.2; p1.x = p2.x + radius*2; } else if (percent <= 0.6) { p4.x = p2.x = radius*2 ; p1.x = radius*4 - radius*(percent - 0.4)/0.2; } else if (percent <= 0.8) { p4.x = p2.x = radius*2 - radius*(percent - 0.6)/0.2; p1.x = p2.x+radius; } else if (percent <= 0.9) { p3.x = radius*(percent - 0.8)/0.3; p4.x = p2.x = radius; p1.x = radius*2; } else if (percent <= 1.0) { p3.x = radius*(1 - percent)/0.3; p4.x = p2.x = radius; p1.x = radius*2; } } else { if (percent <= 0.2) { p3.x = - radius*percent/0.2; } else if (percent <= 0.4) { p3.x = -radius - radius*(percent-0.2)/0.2; p4.x = p2.x = p3.x + 2*radius; } else if (percent <= 0.6) { p3.x = radius*(percent - 0.4)/0.2 - radius*2; p4.x = p2.x = 0; } else if (percent <= 0.8) { p3.x = -radius+radius*(percent - 0.6)/0.2; p4.x = p2.x = p3.x + radius; p1.x = p2.x + radius*2 - radius*(percent - 0.6)/0.2; } else if (percent <= 0.9) { p1.x = radius*2 - radius*(percent - 0.8)/0.3; } else if (percent <= 1.0) { p1.x = radius*2 - radius*(1 - percent)/0.3; } } final p1Radius = p2.y - p1.y; final p24LeftRadius = p2.x - p3.x; final p24RightRadius = p1.x - p2.x; final p3Radius = p2.y - p3.y; path.moveTo(p1.x, p1.y); path.cubicTo( p1.x, p1.y + p1Radius*M, p2.x + p24RightRadius*M, p2.y, p2.x, p2.y ); path.cubicTo( p2.x - p24LeftRadius*M, p2.y, p3.x, p3.y + p3Radius*M, p3.x, p3.y ); path.cubicTo( p3.x, p3.y - p3Radius*M, p4.x - p24LeftRadius*M, p4.y, p4.x, p4.y ); path.cubicTo( p4.x + p24RightRadius*M, p4.y, p1.x , p1.y - p1Radius*M, p1.x, p1.y ); }分享一个贝塞尔曲线在线演示网站源码地址分享一个Flutter项目后续在UI中国上看到了一个不错的设计,里面也涉及贝塞尔曲线全手势操作灯的demo,这里的贝塞尔曲线p2、p4的Y轴向中间做一个伸缩就可以。 ...

July 5, 2019 · 2 min · jiezi

flutter-线程通信与消息循环C

环境: flutter sdk v1.5.4-hotfix.1@stable 对应 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f 这里关注的是flutter在C++层的线程表示, 没有涉及dart层的线程 线程创建flutter底层(C++)的线程(fml::Thread)是和消息循环紧密关联的,即每一个fml::Thead实例都创建了一个消息循环实例,因此如果要创建一个裸线程是不应该用fml::Thread的。fml::Thread内部即是用C++11的std::thread来持有一个线程对象,参看fml::Thread构造函数(thread.cc:25)。 线程运行体做了2件事 创建消息循环实例并关联线程fml::Thread对象获取消息循环的TaskRunner对象实例并赋值给线程fml::Thread,即线程也持有一个TaskRunner实例这个TaskRunner是个干啥的,还得看的fml::MessageLoop实现fml::Thread的实现非常简单,关键还是看它关联的fml::MessageLoop。 线程存储消息循环fml::MessageLoop首先用了线程存储来保存一个回调,这个回调的作用是显式释放一个fml::MessageLoop内存对象,所以先搞清flutter底层是如何进行线程存储的。 线程存储对象即作用域与线程生命周期一致的存储对象,fml::ThreadLocal即为线程存储类,它要保存的值是一个类型为intptr_t的对象;fml::ThreadLocal在不同平台用了不同的实现方式 类linux平台用了pthread的库函数pthread_key_create来生成一个标识线程的key键,key对应的值是一个辅助类Box,它保存了intptr_t对象和传入的回调方法ThreadLocalDestroyCallback。ThreadLocal使用前需要声明的关键字是static对象析构的顺序稍有点绕, 各对象析构调用序列如下: ThreadLocal::~ThreadLocal() ThreadLocal::Box::~Box() pthread_key_delete(_key) ThreadLocal::ThreadLocalDestroy ThreadLocal::Box::DestroyValue ThreadLocalDestroyCallback() => [](intptr_t value) {} MessageLoop::~MessageLoop() ThreadLocal::Box::~Box()这样看似乎thread_local.cc:27处的delete操作是多余的? windows平台ThreadLocal使用前直接用了C++11标准的关键字thread_local。消息循环消息循环即异步处理模型,在没有消息时阻塞当前线程以节省CPU消耗,否则以轮询的方式空转很浪费CPU资源,消息循环在安卓平台上很常见,其实所有的消息循环都大同小异。 关联线程明白了线程存储,那么在创建fml::Thread对象时调用的MessageLoop::EnsureInitializedForCurrentThread就很浅显了(名字虽然有点累赘),当前线程是否创建了消息循环对象,如果没有那么创建并保存。这样消息循环就与线程关联起来了, 通过什么关联的?tls_message_loop这个线程存储类对象。 消息队列MessageLoopImpl ::delayed_tasks_就是实际的消息队列,它被delayed_tasks_mutex_这个互斥变量保证线程安全。看着有点累赘,其实就是用了一个优先级队列按执行时间点来插入,如果时间点相同就按FIFO的规则来插入。 队列元素是一个内部类DelayedTask, 主要包含消息执行体task和执行时间点target_time,order其实是用来排序的。 循环实现MessageLoop对象构造函数创建了2个重要实例,消息循环实现体MessageLoopImpl和fml::TaskRunner, 而fml::TaskRunner内部又引用了MessageLoopImpl。MessageLoopImpl::Create()创建了不同平台对应的消息循环实现体,于是MessageLoop与MessageLoopImpl之间的关系也非常清楚了: MessageLoop是MessageLoopImpl的壳或者MessageLoopImpl是MessageLoop的代理,MessageLoopImpl是不对外暴露的、与平台相关的、真正实现消息读取与处理的对象。 MessageLoopImpl::Run,Terminate,WakeUp是纯虚函数,由平台实现,譬如安卓平台的实现MessageLoopAndroid调用的是AndroidNDK方法ALooper_pollOnce, MessageLoopLinux调用是Linux阻塞函数epoll_wait。 这里涉及的类和方法有点绕,其实想达到目的很简单:读取并处理消息的操作是统一的,但线程唤醒或者阻塞的方式是允许平台差异的 发送消息一个消息循环关联一个TaskRunner,而TaskRunner细看实现发现全都是MessageLoopImpl的方法,再联系之前在AndroidShellHolder构造函数里创建的TaskHost,就可以发现所谓的TaskRunner无非就是给指定消息循环发送消息,而一个消息循环是和一个线程(fml::Thread)关联的,因而也也就是给指定线程发送消息,没错,正是线程间通信!TaskRunner也正是声明成了线程安全对象(fml::RefCountedThreadSafe<TaskRunner>) 这样其实一切都串联起来了: fml::TaskRunner正如android中的android.os.Handler, fml::closure正如android中的Runnable, fml::TaskRunner不断的将各种fml::closure对象添加到消息队列当中,并设定消息循环在指定的时间点唤醒并执行。 线程结束fml::Thread析构函数调用了自身的Join方法, 这个操作初看有点别扭,后来才明白意图:主调线程需要同步的等待被调线程结束,名称不如Exit来的言简意赅。Join方法先异步发送了一个结束消息循环的请求(MessageLoop::GetCurrent().Terminate()),然后阻塞式等待结束。结合以上列出线程退出的调用序列: Thread::~Thread() Thread::Join() TaskRunner::PostTask()...[异步]MessageLoop::Terminate() MessageLoopImpl::DoTerminate() MessageLoopImpl::Terminate() => MessageLoopAndroid::Terminate() ALooper_wake()...[异步,函数开始返回] MessageLoopImpl::Run() => MessageLoopAndroid::Run() MessageLoopImpl::RunExpiredTasksNow() MessageLoopImpl::DoRun()MessageLoop::Run()...[异步]ThreadLocal::~ThreadLocal()[省略,同线程存储对象析构的调用序列]线程体系回看AndroidShellHolder的构造函数,其中涉及flutter::ThreadHost, fml::TaskRunner, flutter::TaskRunners,在创建Shell对象之前还创建了一系列线程:ui_thread, gpu_thread, io_thread,并对TaskRunner有一系列操作,有点杂乱但现在看其实就非常清晰了。 ...

July 4, 2019 · 1 min · jiezi

Flutter-For-Web

原文地址: https://medium.com/flutter/br...原文作者: Kevin Moore 翻译作者: Fedora Flutter For Web用来构建漂亮、定制化应用的跨平台的 UI 框架 Flutter 现在已经支持 Web 开发了。我们很高兴推出了一个预览版的 SDK 可以让开发者直接使用 Flutter UI 和业务逻辑代码构建 web 应用在浏览器中运行起来。 Flutter 在 Web端的雄心自从去年第一个公测版本推出之后,开发者使用 Flutter 构建跨 IOS 和 Android 的应用。但是 Flutter 自始至终被设计成一个跨平台的 UI 框架包括 Windows ,Mac,Fuchsia 甚至是 Raspberry Pi(树莓派)。因为 Flutter 是由 Dart 编写的,里面包含一个生产环境的编译器来构建原生的代码和 JavaScript 代码,所以我们有一个坚实的基础。剩下的挑战就是替换基于 Skia-based 的图形引擎和文本渲染来适配 Web 平台。 要做到这些,我们需要提供: 快速,无抖动的且每秒60帧的页面交互考虑到 Flutter 在其他平台提供的能力和视觉和现有开发模式整合的高效率的开发体验支持所有现代浏览器的核心 Web 功能虽然Flutter for web是一项正在进行中的工作,而且为了实现上述功能还有很多工作要做。我们已经推出一个预览版,所以开发者可以进行尝鲜并给我们反馈。 Flutter Web 架构Flutter 在 Web 端的整体架构和移动端的架构差不多: Flutter 核心层(上图绿色部分)在移动端和 Web 端是一样的。它提供了 Flutter UI 的高度抽象,包括动画,手势,基本的小部件,以及一套大部分应用需要的 Material 风格的部件。如果你已经在客户端开发中使用了 Flutter,那么你就会很快的在 Web 开发中上手。 ...

July 2, 2019 · 1 min · jiezi

flutter-深入通信接收端

环境: flutter sdk v1.5.4-hotfix.1@stable 对应 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f 前言通过PlatformChannel为平台层作为接收端的例子我们已经了解到DartMessenger通过响应接口handleMessageFromDart来把Dart层的消息/操作发送到平台层,而这个方法是PlatformMessageHandler这个接口对象的,持有接口实例的对象正是FlutterJNI。 作为被动调用的一方,平台层等待消息接收,并不知道消息的来源和用途,所以我们只需要按图索骥,找出调用方,就可追踪接收过程的完整流程。 追溯容易看到FlutterJN.handlePlatformMessage调用了handleMessageFromDart,此函数被标记成@SuppressWarnings("unused"),很大可能与C++层有关了,搜索方法名称果然在`中找到"handlePlatformMessage", 函数签名是"(Ljava/lang/String;[BI)V"正是些方法,方法对象被全局变量g_handle_platform_message_method持有,又被FlutterViewHandlePlatformMessage`引用, 至此又进入到C++层。 这里HandlePlatformMessage这个名称实在太让人产生误解,感觉像是C++层在处理平台层发来的消息,然而实际却是传递Dart层的消息到平台,虽然handlePlatformXXX这种风格都表示处理Dart层的消息,并且保持的很好,但还是没有receiveXXX来的简单直观明了。 为便于理解以下是被调用序列 DartMessenger.handleMessageFromDart => PlatformMessageHandler FlutterJNI.handlePlatformMessage => g_handle_platform_message_method FlutterViewHandlePlatformMessage PlatformViewAndroid::HandlePlatformMessage <= PlatformView::HandlePlatformMessage ...Shell::OnEngineHandlePlatformMessage <= PlatformView::Delegate::OnEngineHandlePlatformMessage Engine::HandlePlatformMessage <= RuntimeDelegate::HandlePlatformMessage RuntimeController::HandlePlatformMessage <= WindowClient::HandlePlatformMessage ::SendPlatformMessage ...tonic::DartCallStatic(::_SendPlatformMessage ...Window::RegisterNatives这与发送端的序列层次完全一样,从上到下分别是Shell -> PlatformView -> Engine -> RuntimeController -> Window。 可知FlutterViewHandlePlatformMessage是C++调用的终点,全局变量g_handle_platform_message_method其实是平台java方法,所以需要知道java方法何时与C++方法关联起来的, 即g_handle_platform_message_method何时被设置的:以下是被调用序列 g_handle_platform_message_method = env->GetMethodID(,"handlePlatformMessage",) ::RegisterApi PlatformViewAndroid::Register JNI_OnLoad System.loadLibrary("flutter") (library_loader.cc:23) FlutterMain.startInitialization (FlutterMain.java:161) FlutterApplication.onCreate (FlutterApplication.java:22)JNI_OnLoad被声明在了链接器脚本(android_exports.lst)中,表示被加载时执行的操作。 结语结合前2篇的调用细节分析(精确到函数),及一些关键类的创建时机做一个简明flutter通道数据通信类图如下:左边是java类,右边是C++类

July 2, 2019 · 1 min · jiezi

极客时间Flutter核心技术与实战课程返现-脑图-学习笔记

关注有课学微信公众号,回复暗号 flutter 获取购买《Flutter核心技术与实战》极客时间专栏地址,购买成功后提交购买截图即可获得返现,另外送 《Flutter核心技术与实战》专栏学习笔记,待课程更新完成送统一通过微信公众号发放。 陈航 @美团点评高级技术专家 这门课程帮助你快速上手 Flutter 开发应用,掌握其精髓,并引导你建立起属于自己的终端知识体系。《Flutter核心技术与实战》专栏主要包括以下五大部分内容:Flutter 开发起步模块。介绍 Flutter 的诞生背景、基本原理,并初次体验 Flutter 代码是如何在原生系统上运行的。Dart 基础模块。对比 Dart 与其他编程语言的设计思想出发,了解 Dart 设计的关键思路以及独有特性,并结合一个综合案例去实践。Flutter 基础模块。学完这个模块,你就可以开发出一个简单的 App 了。Flutter 进阶模块。了解 Flutter 开发中的一些疑难问题、高级特性及其背后原理。Flutter 综合应用模块。如何从效率和质量这两个维度出发,构建自己的 Flutter 开发体系。

July 1, 2019 · 1 min · jiezi

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

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

July 1, 2019 · 1 min · jiezi

flutter-深入通信发送端

环境: flutter sdk v1.5.4-hotfix.1@stable对应 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f 站在平台端的视角对通道有一个通观概览的认知之后就需要深入内里对通信机制需要一个深入剖析了,之前已经了解到FlutterJNI.dispatchPlatformMessage是平台层(java)发送数据调用的最后一层,那么继续这个调用序列: FluttereJNI.dispatchPlatformMessage nativeDispatchPlatformMessage(FlutterJNI.java) DispatchPlatformMessage(platform_view_android_jni.cc:56) AndroidShellHolder::GetPlatformView(platform_view_android_jni.cc:421) PlatformViewAndroid::DispatchPlatformMessage(platform_view_android.cc:92) TaskRunners::GetPlatformTaskRunner => PlatformView::task_runners_ new PlatformMessageResponseAndroid() new flutter::PlatformMessage(name, message, response) PlatformView::DispatchPlatformMessage PlatformView::Delegate::OnPlatformViewDispatchPlatformMessage() => Shell::On..() ::GetUITaskRunner TaskRunner::PostTask ...Engine::DispatchPlatformMessage发送消息最终调用到了C++层的PlatformViewAndroid::DispatchPlatformMessage方法, 又调用了PlatformViewAndroid成员delegate_的OnPlatformViewDispatchPlatformMessage方法, 所以我们要确定PlatformView::Delegate抽象类的实现体, 也就是要追踪成员被创建或赋值的地方。 由构造函数可知成员PlatformView::delegate_是创建时外部传入,而PlatformViewAndroid作为子类把它的delegate传入,所以需要了解PlatformViewAndroid被创建时传入的delegate对象, android_shell_holder.cc:63可知创建PlatformViewAndroid时传入的的delegate对象实际为Shell,在其方法中又异步调用了成员engine_的方法,即Engine::DispatchPlatformMessage方法 所以我们需要 明确PlatformViewAndroid被创建的流程明确Engine被赋值或创建的时机创建PlatformViewAndroid流程:AndroidShellHolder::AndroidShellHolder() ThreadHost::ThreadHost platform_thread= fml::MessageLoop::EnsureInitializedForCurrentThread platform_runner=fml::MessageLoop::GetCurrent().GetTaskRunner() Shell::Create() DartVMRef::Create(settings) Shell::Create() TaskRunner::RunNowOrPostTask lamda() => Shell::CreateShellOnPlatformThread() Shell::CreateCallback<PlatformView>(Shell&) => on_create_platform_view new PlatformViewAndroid(Shell,...) 最重要的是Shell::Create这个方法,在调用时传入了一个回调,这个回调调用了Shell::CreateShellOnPlatformThread(), 继续回调了on_create_platform_view,它的实现体在AndroidShellHolder构造函数上下文中。 创建Shell::engine_[Engine]流程:第二个问题刚好承接了对每一个问题的分析: 我们是在创建Shell的时候创建了PlatformViewAndroid对象shell.cc:38可知engine_也是外部传入 Shell::CreateShellOnPlatformThread() new Shell() on_create_platform_view => AndroidShellHolder.on_create_platform_view std::make_unique<PlatformViewAndroid>() TaskRunner::RunNowOrPostTask ...lamda => engine = std::make_unique<Engine>() (shell.cc:131) Shell::Setup engine_ = std::move(engine); (shell.cc:388)在Shell::CreateShellOnPlatformThread 中先创建了Shell实例, 接着创建了PlatformView实例,接着又异步执行了一个lamda,创建了Engine实例 ...

June 30, 2019 · 1 min · jiezi

Flutter-for-Web-详细预研

首先感谢@栖冰 @祖建国 一起对FFW的预研做的投入! 背景Google在最新的Google I/O上推出了Flutter for Web,旨在进一步解决一次代码,多端运行的问题。Flutter for Web还处于早期试验版,官方不建议在生产环境上使用。那么到底它的实际情况怎么样呢? 我们做了一次预研。期望这次预研的结果可以帮你决定是用,还是不用FFW。 Flutter for Web原理 Flutter for Web和Flutter在上层都是Dart环境,两者不同的是,Flutter的Dart代码运行在Dart虚拟机中,界面由Flutter引擎处理,通过Skia绘图引擎经由GPU绘制到屏幕上。而Flutter for Web的Dart代码编译成JavaScript,界面上部分转换成标准的html标签,部分转换成通过Canvas绘制的自定义标签,最终构成一个dom树。这个原理上的差异非常重要,这直接可以让我们通过原理得出下面的结论: Flutter for Web的一致性和体验上存在矛盾如果Flutter for Web追求(和Flutter)完美的一致性,势必需要大量使用Canvas去绘制,而Canvas去绘制组件的性能(尤其在移动端)至少不会比html标签好。如果FFW追求性能极限而使用大量标准的html标签,这就会带来和Weex、RN等一样的一致性问题:对于Flutter所有的控件都是一套代码在绘图引擎上绘制,对Flutter for Web如果要使用大量html标签,那如何保证一致性呢?只能靠大量精细的打磨工作了。所以FFW必须要处理好这个平衡。 为啥使用canvas绘制性能不优于手写html呢,定性的从几个角度分析: FFW在canvas上绘制的组件带有很多MD特色的视觉和动画,比如阴影、Z轴变化等,这部分对性能的消耗要大于普通html标签FFW是通过Dart的DSL转成的dom树结构,转化后的dom树十分复杂,不太可能比手写的dom树更简洁使用canvas的控件,其手势事件的捕获分发都是靠FFW框架自己实现的,emmmm虽然不排除Google大力出奇迹的情况,但是不管怎样,相同素质的开发人员,相同的界面,性能上也不可能优于html+css+js另外一点,如果FFW在原理上涉及大量HTML标签的转化,那就势必会涉及到碎片化的处理中,浏览器的碎片化程度可一点都不比Android系统的碎片化小。像Flutter本身之所以被那么多人看重,就是因为其通过绘图引擎这一层,完美的避开了碎片化,保证一致性。所以最好的平衡就是,只有有限的一部分标准的html标签可以被FFW复用,其必须有几点性质: 标签本身的功能简单又直观最好不要有直接图形化的展示,或者只负责简单的图形化展示(比如画方形)那几个比较典型的标签就是<p>、<div>这种了Flutter官方就是这么做的,所以我的结论是:一致性上大体不会存在问题,性能上,FFW应该不会优于纯手写html标签界面。 官方现状&建议根据官网和Github repo上的说法,我们整理了一下: Flutter for Web和Flutter目前暂时是两个仓库,官方正在进行合并,没有给出结论。这一点在工程上非常重要,它说明了几个问题: 目前官方对FFW的成熟度没有信心,同时FFW的迭代速度也很快。目前FFW和Flutter最多保证API一样,实现原理差异可能非常大,同时不保证所有控件都已经在FFW上实现。官方不建议应用在生产环境目前插件能力十分有限,和系统交互的一些能力缺失,比如拍照等。性能无法保证,运行会慢,可能会有掉帧FFW中针对桌面的UI部分没有完成(跟我无线有什么关系?)开发中只能在Chrome中调试(又有什么关系?),release版是可以运行在任意浏览器中(除了IE,另支持的最低版本存疑)。实践对于这么新的东西,官网上的内容的确不多,而且简单来看这些问题好像也没什么,所以对于到底能不能用,我们还是需要抱着吃螃蟹的心态具体进去预研一下,为了尽快弄清,我计划找一个我们app已经做好的flutter页面,把它迁移成FFW,对整个迁移过程做个评估,再看下页面效果,基本上就能得出结论了。具体的迁移细节就不提了,官网也有迁移文档,大体上就是这么几个步骤 安装Flutter for Web的工具webdev改SDK依赖,新增Web文件夹(和之前存在的android、ios文件夹同级),新增一些其他文件(index.html, main.dart等)。将所有flutter代码中依赖的flutter包,改成flutter_web包去掉所有不兼容的代码,比如多语言、路由、Platform.isAndroid等等编译运行实践的主要目的,有以下几个: 对整体坑的深度和广度有个认知,方便推算出填坑成本对FFW整体的性能和体验有个把握,尤其是我们自己的页面跑在FFW上是什么体验。对FFW和JS相互调用有具体的了解,如果可行,那复用集团已有能力(比如mtop)的坑就会小很多 删了一万行代码跑成功之后,最终在工程、开发体验、用户体验上得到一些结论,以下的结论中,体验部分是我在我的mix2s上的感受: 工程支持debug和release模式,后者比前者性能高(差异很明显)。 debug模式支持代码修改后自动重新编译,和其他的前端框架(我只用过Django)一致支持hotreload,暂时没有尝试支持webdev build命令编译出index.html+js,可以通过nginx做反向代理。非常重要 编译出来的代码,gzip压缩前,最简单的helloworld的main.dart.js大小约为500k左右,sample中的gallery大小约为2M,阉割版的纯展示用的订单列表大小约为1.3M。gzip对文本的压缩率一般是80%,压缩后也要动辄几百k的大小。而且main.dart.js不加载完,界面是不会展示的。开发体验非常重要flutter for web使用flutter_web库,且不支持其他许多插件,这会带来几个问题 工程上无法优雅的解决flutter和flutter_web共存的情况,最多搞个dart2的conditional import(这个特性可不在官方文档中哦)依赖flutter sdk的几个库,尤其是多语言库无法应用在flutter_web上,并且Google肯定不会再单独为flutter_web适配,而是在合并时做支持。这就意味着现阶段所有依赖flutter sdk的库不能被flutter_web使用。调试困难 错误日志可以打印到浏览器的console中,但是try catch部分的堆栈不好拿浏览器中的堆栈很复杂,但是基本上能找到出错的dart代码目前没有发现单步调试的能力Platform.isAndroid全部报错,针对Android和iOS做差异化展示目前还不知道有没有其他方法可以做到。目前没有发现控件的api不一致的地方,不过有些控件的行为十分异常,比如下拉刷新,在Android手机上经常卡死、失效。猜测重交互的一些控件都有可能存在类似的问题,但是测一遍的成本太高。图片控件NetworkImage可以直接用,但是现在来看有些糊,不确定是官方控件的问题,还是我们做的cdn url策略有问题。dart代码可以调用js,开发体验和反射类似,并且需要处理JS类型和Dart类型的转换。Dart和JS的交互速度未知。只能说哪怕FFW有很多浏览器的API不支持,也可以通过JS来扩展能力。Dart和Js语言本身的差异会带来测试和兼容成本的增加,虽然Dart可以编译成Js,但是跑在DartVM上的Dart的表现,和其编译成Js运行在浏览器中的表现并不完全相同。比如对于如下代码 Map<String, String> query = null;val b = query["abc"];在DartVM中该代码可以执行,结果是b=null;但是编译成Js以后运行时因为query为null,会报空指针。 用户体验使用了chrome、uc、小米自带浏览器分别试了一下订单列表和官方的sample-gallery界面,体验如下: 加载速度差不多,因为是局域网环境,感知不到差异。非常重要打出的唯一的main.dart.js和部分资源文件(比如MaterialIcons)没有加载出来的时候,界面不会展示。帧率或者流畅度上,chrome > uc >> 小米自带浏览器,其中chrome算是最流畅的体验,但是sample中的一些动画和页面转场也可以看到明显的卡顿。文字展示上,看flutter for web的界面,chrome对大部分文字处理的很清晰,小米自带浏览器看文字明显模糊。对比下面两张图,点开放大之后查看,FFW页面的文字模糊的很明显。 ...

June 27, 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

GMTC2019闲鱼基于Flutter的架构演进与创新

2012年应届毕业加入阿里巴巴,主导了闲鱼基于Flutter的新混合架构,同时推进了Flutter在闲鱼各业务线的落地。未来将持续关注终端技术的演变及趋势 Flutter的优势与挑战 Flutter是Google开源的跨端便携UI工具包,除了具有非常优秀的跨端渲染一致性,还具备非常高效的研发体验,丰富的开箱即用的UI组件,以及跟Native媲美的性能体验。由于它的众多优势,也使得Flutter成为了近些年来热门的新技术。 通过以上的特点可以看出,Flutter可以极大的加速客户端的研发效率,与此同时得到优秀的性能体验,基于我的思考,Flutter会为以下团队带来较大的收益: 中小型的客户端团队非常适合Flutter开发,不仅一端编写双端产出,还有效的解决了小团队需要双端人员(iOS:Android)占比接近1:1的限制,在项目快速推进过程中,能让整个团队的产能最大化。App在Android市场占比远高于iOS的团队,比如出海东南亚的一些App,Android市场整体占比在90%以上,通过Flutter可以将更多的人力Focus在Android市场上,同时通过在iOS端较小的投入,在结果上达到买一送一的效果。以量产App为主要策略的团队,不论是量产ToB的企业App,还是有针对性的产出不同领域的ToC的App的公司,都可以通过一端开发多端产出的Flutter得到巨大的产能提升。 闲鱼在以上的场景中属于第一种场景,服务3亿用户的闲鱼App的背后,开发资源投入很少,与竞对相比,我们是一只再小不过的团队,在这种场景下,Flutter为闲鱼业务的稳定发展以及提供更多的创新产品给予了很大的帮助。 但与此同时,Flutter在设计上带来的优势同时又会带来新的问题。所有的新技术都是脱胎于老技术的,Flutter也不例外,其身上带有很多Chrome的影子。我们再做一层简化,如果我们认为Flutter是一个使用Dart语言的浏览器容器,请大家思考一下两个问题如何解决。 如果在一个已经存在的App中加入Flutter,如何让Native与Flutter进行无缝的衔接,同时保证相互开发之间的隔离性如果在Flutter的容器中,使用已有的Native UI组件,在Flutter与Native渲染机制不同的情况下,怎么保证两者的无缝衔接以及高性能。闲鱼的架构演进与创新带着上面两个问题,我们来到闲鱼场景下的具体Case以及解决方案的演进过程。 已有App+Flutter容器 在这种情况下,闲鱼需要考虑的是首先要考虑引入Flutter容器后的内存压力,保证不要产生更多的内存溢出。与此同时我们希望能让Flutter和Native之间的页面切换是顺畅的,对不同技术栈之间的同学透明。因此我们有针对性的进行了多次迭代。 在没有任何改造的情况下以iOS为例,你可以通过创建新的FlutterViewController来创建一个新的Flutter容器,这个方案下,当创建多个FlutterViewController时会同时在内存中创建多个Flutter Engine的Runtime(虽然底层Dart VM依然只有一个),这对内存消耗是相当大的,同时多个Flutter Engine的Runtime会造成每个Runtime内的数据无法直接共享,造成数据同步困难。 这种情况下,闲鱼选择了全局共享同一个FlutterViewController的方式保证了内存占用的最小化,同时通过基础框架Flutter Boost提供了Native栈与Flutter栈的通信与管理,保证了当Native打开或关闭一个新的Flutter页面时,Dart侧的Navigator也做到自动的打开或关闭一个新的Widget。目前Google官方的提供的方案上就是参考闲鱼早先的这个版本进行的实现的。 然而在这种情况下,如果出现如闲鱼图中所示多个Tab的场景下,整个堆栈逻辑就会产生混乱,因此闲鱼在这个基础上对Flutter Boost的方案进行了升级并开源,通过在Dart侧提供一个BoostContainerManager的方式,提供了对多个Navigator的管理能力,如果打比方来看这件事,就相当于,针对Flutter的容器提供了一个类似WebView的OpenWindow的能力,每做一次OpenWindow的调用,就会产生一个新的Navigator,这样开发者就可以自由的选择是在Navigator里进行Push和Pop,还是直接通过Flutter Boost新开一个Navigator进行独立管理。 Flutter Boost目前已在github开源,由于闲鱼目前线上版本只支持Flutter 1.2的版本,因此需要支持1.5的同学等稍等,我们会在近期更新支持1.5的Flutter Boost版本。 Flutter页面+Native UI 由于闲鱼是一个闲置交易社区,因此图片和视频相对较多,对图片视频的线上性能以及内存占用有较严格的要求。目前Flutter已提供的几种方案中(Platform View以及Flutter Plugin),不论是对内存的占用还是整个的线上流畅度上还存在一定的问题,这就造成了当大部分同学跟闲鱼一样实现一个复杂的图文Feed推荐场景的时候,非常容易产生内存溢出。而实际上,闲鱼在以上的场景下有针对性的做出了较大的优化。 在整个的Native UI到Flutter渲染引擎桥接的过程中,我们选用了Flutter Plugin中提供的FlutterTextureRegistry的能力,在去年上半年我们优先针对视频的场景进行了优化,优化的思路主要是针对Flutter Engine底层的外接纹理接口进行修改,将原有接口中必须传入一个PixelBuffer的内存对象这一限制做了扩展,增加一个新的接口保证其可以传入一个GPU对象的TextureID。 如图中所示,优化后的整个链路Flutter Engine可以直接通过Native端已经生成好的TextureID进行Flutter侧的渲染,这样就将链路从Native侧生成的TextureID->copy的内存对象PixelBuffer->生成新的TextureID->渲染,转变为Native侧生成的TextureID->渲染。整个链路极大的缩短,保证了整个的渲染效率以及更小的内存消耗。闲鱼在将这套方案上线后,又尝试将该方案应用于图片渲染的场景下,使得图片的缓存,CDN优化,图片裁切等方案与Native归一,在享受已有集团中间件的性能优化的同时,也得到了更小的内存消耗,方案落地后,内存溢出大幅减少。 目前该方案由于需要配合Flutter Engine的修改,因此暂时无法提供完整的方案至开源社区,我们正在跟google积极沟通整个修改方案,相信在这一两个月内会将试验性的Engine Patch开源至社区,供有兴趣的同学参考。 复杂业务场景的架构创新实践 将以上两个问题解决以后,闲鱼开始了Flutter在业务侧的全面落地,然而很快又遇到新的问题,在多人协作过程中: 如何提供一些标准供大家进行参考保证代码的一致性如何将复杂业务进行有效的拆解变成子问题如何保证更多的同学可以快速上手并写出性能和稳定性都不错的代码 在方案的前期,我们使用了社区的Flutter Redux方案,由于最先落地的详情,发布等页面较为复杂,因此我们有针对性的对View进行了组件化的拆分,但由于业务的复杂性,很快这套方案就出现了问题,对于单个页面来说,State的属性以及Reducer的数量都非常多,当产生新需求堆叠的时候,修改困难,容易产生线上问题。 针对以上的情况,我们进行了整个方案的第二个迭代,在原有Page的基础上提供了Component的概念,使得每个Component具备完整的Redux元素,保证了UI,逻辑,数据的完整隔离,每个Component单元下代码相对较少,易于维护和开发,但随之而来的问题是,当页面需要产生数据同步时,整个的复杂性飙升,在Page的维度上失去了统一状态管理的优势。 在这种情况下闲鱼换个角度看端侧的架构设计,我们参考React Redux框架中的Connect的思想,移除掉在Component的Store,随之而来的是新的Connector作为Page和Component的数据联通的桥梁,我们基于此实现了Page State到Component State的转换,以及Component State变化后对Page State的自动同步,从而保证了将复杂业务有效的拆解成子问题,同时享受到统一状态管理的优势。与此同时基于新的框架,在统一了大家的开发标准的情况下,新框架也在底层有针对性的提供了对长列表,多列表拼接等case下的一些性能优化,保证了每一位同学在按照标准开发后,可以得到相对目前市面上其他的Flutter业务框架相比更好的性能。 目前这套方案Fish Redux已经在github开源,目前支持1.5版本,感兴趣的同学可以去github进行了解。 研发智能化在闲鱼的应用闲鱼在去年经历了业务的快速成长,在这个阶段上,我们同时进行了大量的Flutter的技术改造和升级,在尝试新技术的同时,如何能保证线上的稳定,线下的有更多的时间进行新技术的尝试和落地,我们需要一些新的思路和工作方式上的改变。 以我们日常工作为例,Flutter的研发同学,在每次开发完成后,需要在本地进行Flutter产物的编译并上传到远端Repo,以便对Native同学透明,保证日常的研发不受Flutter改造的干扰。在这个过程中,Flutter侧的业务开发同学面临着很多打包上传更新同步等繁琐的工作,一不小心就会出错,后续的排查等让Flutter前期的开发变成了开发5分钟,打包测试2小时。同时Flutter到底有没有解决研发效率快的问题,以及同学们在落地过程中有没有Follow业务架构的标准,这一切都是未知的。 在痛定思痛以后,我们认为数据化+自动化是解决这些问题的一个较好的思路。因此我们首先从源头对代码进行管控,通过commit,将代码与后台的需求以及bug一一关联,对于不符合要求的commit信息,不允许进行代码合并,从而保证了后续数据报表分析的数据源头是健康的。 在完成代码和任务关联后,通过webhook就可以比较轻松的完成后续的工作,将每次的commit有效的关联到我们的持续集成平台的任务上来,通过闲鱼CI工作平台将日常打包自动化测试等流程变为自动化的行为,从而极大的减少了日常的工作。粗略统计下来,在去年自动化体系落地的过程中单就自动打Flutter包上传以及触发最终的App打包这一流程就让每位同学每天节省一个小时以上的工作量,效果非常明显。另外,基于代码关联需求的这套体系,可以相对容易的构建后续的数据报表对整个过程和结果进行细化的分析,用数据驱动过程改进,保证新技术的落地过程的收益有理有据。 总结与展望回顾一下上下文 Flutter的特性非常适合中小型客户端团队/Android市场占比较高的团队/量产App的团队。同时由于Flutter的特性导致其在混合开发的场景下面存在一定劣势。闲鱼团队针对混合开发上的几个典型问题提供了对应的解决方案,使整个方案达到上线要求,该修改会在后续开放给google及社区。为全面推动Flutter在业务场景下的落地,闲鱼团队通过多次迭代演进出Fish Redux框架,保证了每位同学可以快速写出相对优秀的Flutter代码。新技术的落地过程中,在过程中通过数据化和自动化的方案极大的提升了过程中的效率,为Flutter在闲鱼的落地打下了坚实的基础。除了本文提及的各种方案外,闲鱼目前还在多个方向上发力,并对针对Flutter生态的未来进行持续的关注,分享几个现在在做的事情 Flutter整个上层基础设施的标准化演进,混合工程体系是否可以在上层完成类似Spring-boot的完整体系构架,帮助更多的Flutter团队解决上手难,无行业标准的问题。动态性能力的扩展,在符合各应用商店标准的情况下,助力业务链路的运营效率提升,保证业务效果。目前闲鱼已有的动态化方案会后续作为Fish-Redux的扩展能力提供动态化组件能力+工具链体系。Fish-Redux + UI2Code,打通代码生成链路和业务框架,保证在团队标准统一的情况下,将UI工作交由机器生成。Flutter + FaaS,让客户端同学可以成为全栈工程师,通过前后端一体的架构设计,极大的减少协同,提升效率。让工程师去从事更多创造性的工作,是我们一直努力的目标。闲鱼团队也会在新的一年更多的完善Flutter体系的建设,将更多已有的沉淀回馈给社区,帮助Flutter社区一起健康成长。 ...

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

年中杂想

前言有两个月没写总结了,不管原因如何,总要把欠了的补上。这个周末抽出时间,好好整理一下前两个月的工作内容以及感想,内容涉及: GithubFlutter产品思维与用户意识新的技术观新的团队观自我认可 Github具体技术点,已写在文章中:????自动化的Github Workflow自做开源组件以来,文档、预览、构建、发布这几个环节,一直都是很繁琐,手工操作多,易出错,十分不得要领。 于是参考vue/nuxt/element, 一点一点地摸索,反复地尝试,终于找到了适合我们的github wokflow。 于是我们就对所有组件进行升级改造,相关脑图如下: Flutter滴雀APP(意为滴普语雀)下载地址这个星期,我们由Flutter开发的APP终于登录App Store了。这是我们第一款由Flutter开发的应用,我也经历了第一次从0到1完整的上架的过程。 我在语雀官方论坛上发布相关信息,也获得了官方人员的认可。 我感触比较深的是两点: 增长了信心实践才是王道 信心年初的时候,与某团队进行技术交流,听说他们不但做了小程序,还做了APP,甚至他们已经完成了从RN到Flutter的转型。我们当时只涉及H5,并没有过APP方面的积累,因此虽然表面不动声色,其实心里大吃一惊,不免有些没底气。 时至今日,我们开发的APP已被当初那个团队的人使用着,我心里已不虚。 实践如今很多社区、论坛、公众号,都在报道Flutter的相关信息,各种比较Flutter与其他技术的优缺点; 身边很多人也早就跃跃欲试,却一直说没时间而停滞不前,或因为做不出最佳选择而犹豫不决。 而我们已经在实践,并且上生产了。要想真正了解一件事,唯有通过亲身实践。所以,如果真的想尝试,赶紧行动起来吧,不用再犹豫了,也不需要把没时间当借口。 产品思维与用户意识通过实践,我认为做产品有两点很重要: 搞清楚用户最想要解决的是什么问题自己用起来 MVP第1点也就是mvp,但这点理论上说起来简单,做起来却难。 首先,用户的声音不一定能听到,就算听到了,也可能因为沟通表达的问题,理解有偏差。 其次,很多时候产品规划都是很宏大的,功能都是很齐全的,从0到1的过程中,很容易在这种规划中迷失。 同时,优先解决核心问题,先实现最小功能闭环,思路是没错,但这样的产物可能过于简陋,因此有人可能会觉得这不像是产品,而持否定意见。 吃自己的狗粮所以,第2点就很重要。自己做的产品,自己用起来,也即“吃自己的狗粮”。 自己去动手前,想一想,这个东西做出来自己会不会用?如果答案是否定的,那就先别动手,重新再想一想。 自己作为用户,才能真正地培养用户意识。 最后还要注意的是,产品越早投入使用越好,这样可以根据用户反馈,快速调整与完善。 新的技术观对于技术,我新增了以下方面来观察与思考: 全局金钱用户开源 全局全局意味着要有整体思维,从团队、从整条生产链路来思考。 现在市面上很多媒体,过分强调个人或单个环节,导致人们普遍过分关注局部,而忽略了整体。 比如有很多开发技术方面的“练级攻略”,给人一种错觉,似乎掌握了这些技术,就能横扫江湖,走向巅峰。但其实开发只是软件工程的一个环节罢了,如果不能掌握整体,在局部再登峰造极,也会有捉襟见肘的时候。 再比如某些教程,说是“教你从0到1开发一个APP”,但其实也只是聚焦在开发领域,也只是能在自己的机器上构建一个APP,安装在自己手机里而已。真正要上线到生产,让别人也能安装使用,同时保持后续的更新迭代,前前后后还有很多内容要涉及,并不是学了教程,开发好了,就完事了。 使用全局的思维来看待这些事情,这样才能看得更清楚、更透彻。 金钱其实很多技术是可以用金钱来买的。这个交易的本质是:用金钱来买时间。 下面举些例子: 我们没有相关新技术的知识,我们可以自己去预研、去实践,然后自己再写一份教程,也可以直接买付费的教程,让大家快速进入系统的学习我们要想一个功能,目前没有,可以自己去实现,也可以买第三方服务我们缺少具备某些经验的人员,可以内部花时间培养,也可以直接从外部招聘有相关经验的人员上面的例子都是常见的待决策的情况,前者都是自己亲力亲为,花费的是时间; 后者则是做资源整合,付出的是金钱。值得一提的是,时间也是金钱,也是成本,因为员工是要发工资的,这一点千万不要忽略了。 遇到上述情况如何决策,需要具体情况具体分析。 只不过,在筛选简历时,每每遇到一些人描述自己“全栈工程师”,但其实只是泛泛而谈时,我都在想,我为何不直接招一个专职的前端、一个专职的后端?这对于企业而言,一个月增加的成本,微不足道。这种类似的思想,池建强在《Flutter 要全平台制霸?我看悬》一文也提到了。 用户 没有愚蠢的用户产品上线后,就会收到用户的反馈。有可能产品的第一个界面,就让用户困惑了。此时技术人员常见的反应是:这都不懂,真笨! 请尽早把用户愚蠢的想法抛弃掉。用户遇到了任何问题,首先应该想想,是不是产品有需要优化的地方。技术是用来解决问题,提升效率的,不是用来彰显自己高端、与众不同的,做技术的千万不能曲高和寡。 用户第一很多时候技术人员评估的事情的优先级,是按照技术难易程度、有趣程度而排列的,但其实上线后,最重要的,是倾向用户的声音,根据反馈进行开发调整。 也许你觉得做个炫酷的动画是很有挑战性、很有趣的事情,但用户也许更关心产品首页的错别字。这种情况下,请优先改正错别字。 开源总有一些技术团队在讲自己是设计这个系统的,怎么实现的,讲了一大堆,即没有附上代码地址,说什么与内部业务联系太紧密,不方便开源。 我也写过一些文章,所以我清楚,很多时候文字并不能完整地表达意图,更别说一个大型系统的设计。也许文章作者觉得自己写出关键点了,但读者的知识背景参差不齐,难保文章没写出来的,才是读者更需要的。因此,软件设计没有附上相关的可执行产物,我认为做的就是不到位。 很多时候,大家重复造轮子,就是因为资源不共享,前人的成果没法复用。 vue、k8s都开源了,把我们的实践成果开个源,其实也什么大不了的。如果涉及商业机密,把相关内容替换成demo就好了。说到底,分享的意愿不够罢了。 如果我们实践了想分享,一定会开源,不搞虚的。 新的团队观去年,我的团队理念是,基于团队现有成员,把每个人打造出我想象中的样子。 当时,我看人主要还是看技术能力; 对于一些技术能力并不符合预期,但态度好的人,我也愿意招聘进来,因为可以培养。 今年,我更强调的是适者生存,也即,团队是有淘汰的,跟不上步伐的人,只能离开。 我现在招聘更愿意招已经技术能力过关,不太需要进行技术培养的人,也即宁缺勿滥,纵然这样很可能一个月也招不到一个人。 另外,我还会注重对软实力的考察,比如热情、目标规划、自我定位、思维方式等,我希望团队成员是自我驱动的,最终能成为自己想要的样子。 一句话总结:去年我想让团队的所有人变得合适; 现在我只想要合适的人留在团队里。 ...

June 25, 2019 · 1 min · jiezi

React-Native快速入门

准备学习React Native之前,需要了解一下其他知识,帮助你更快的理解RNReact:React中文文档ES6:ES6入门教程 环境搭建本人搭建的是mac+Android环境,具体过程参考:React Native中文网--搭建环境。搭建结束后,运行项目 cd AwesomeProjectreact-native run-android当在手机或模拟器上出现如下页面,则说明配置成功。我在搭建结束后,出现红屏,报错如下: Unable to load script.Make sure you're either running a metro server(run 'react-native start' ) or thatyour bundle 'index.android.bundle' is packaged correctly for release.解决方法我记录在这里:点这里。如果遇到同样的问题可以看下。 语法简介Hello World先看下大概长什么样 import React, { Component } from 'react';import { Text, View } from 'react-native';export default class HelloWorldApp extends Component { state={ text : 'this is state.name' } render() { let pic = { uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg' }; const stateName = this.state.name; return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <Text>Hello, world!</Text> <Image source={pic} style={{width: 193, height: 110}} /> <Text>{stateName}</Text> </View> ); }}基本的用法Props,State等 与React是一样的,只不过基础组件不同。React里使用常规的html标签,div span等,使用了一些RN自己的原生组件Image,Text等。 ...

June 25, 2019 · 4 min · jiezi

Channel-flutter平台层与运行层的双向通信

环境: flutter sdk v1.5.4-hotfix.1@stable 对应 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f 存在这样的情形: flutter应用的视图控件响应用户的输入(比如KeyEvent), 需要将平台的按键数据传递到flutter的dart环境并响应, 同时应用可能因为某个操作需要调用平台的接口让手机震动. 但是flutter的App视图运行dart代码,平台(Android)运行Java代码, 同时dart层无法识别java层定义的对象类, 这就需要将数据在不同的运行环境中传递, flutter框架中的channel机制其实就是实现这个目的的. 一些文章和部分代码可能会让人感到困扰, 为什么已经有send接口了还要添加一个setMessageHander接口, 同时send已经有回调reply了, 怎么MessageHandler除了数据还有带一个reply. 理解的关键其实就是这个channel, 顾名思义, 就是进行数据传送的通道, 在平台层(java)与运行层(dart)进行数据通信. 一旦涉及通信就涉及对象传递, 而在不同运行时(runtime)环境进行对象传递就必然涉及对象序列化了. 所以不用被名称迷惑, 所谓的MessageCodec其实就是专门作对象序列化的实例, 而通道既然能发送数据也必须能够接收数据, 如此的双向通信, 仅此而已. 一个通道关联3个对象: 名称, 操作与序列化, 操作即具体做收发消息的工作, 即Messenger. 而消息按类型又分为普通对象, 操作方法, 数据流, 对应着3种基本通道: BasicMessageChannel<T>, MethodChannel, EventChannel 发送有时机, 接收无定时平台端(android)可以显式的创建一个通道, 通道建立后既可作为发送端又可作为接收端, 作为发送端可以主动的传送相关数据, 是为有时机, 作为接收端, 只能被动等待数据到来, 是为无定时 数据发送调用一个通道的send方法,即为发送数据了, 有时发送完数据需要一个反馈, 于是有另一个回调参数Reply<T>, 这个回复是接收端反馈给发送端后发送端作的响应, 可以叫做发送回复. 数据接收每种通道都设置了一个setMessageHandler的方法, MessageHandler<T>其实就是通道的数据接收器, 更容易理解的名字应该是MessageReceiver, 专门等待发送端发送的数据; 表示通道建立后作为接收方接收数据后进行的处理, 数据处理完之后可能需要再反馈给发送端, 所以MessageHandler<T>.onMessage(T message, Reply<T> reply)中的Reply<T>是接收端反馈给发送端的回复, 可以叫做接收回复 ...

June 25, 2019 · 1 min · jiezi

flutter-彻底解决Wrong-full-snapshot-version问题

环境: flutter-sdk(v1.5.4-hotfix.1@stable) 运行flutter脚本的时候有时会遇见Wrong full snapshot version, expected '…' found '…'的错误, 其实这时候是因为在${FLUTTER_ROOT}/bin/cache里缓存的快照过期或者无效了导致的. 网上有把${FLUTTER_ROOT}/bin/cache目录删除了的, 有用git clean -xfd命令解决的, 其实还是删除了${FLUTTER_ROOT}/bin/cache目录, 这样的做法不太好, fluter脚本会重新下载dartSDK等一系列工具,整个过程会持续很长时间. ${FLUTTER_ROOT}/bin/cache有8个标识用的时间戳bin/cache/android-sdk.stamp bin/cache/flutter_sdk.stamp bin/cache/flutter_version_check.stamp bin/cache/ios-sdk.stampbin/cache/engine-dart-sdk.stamp bin/cache/flutter_tools.stamp bin/cache/gradle_wrapper.stamp bin/cache/material_fonts.stamp其中android-sdk.stamp,flutter_sdk.stamp,os-sdk.stamp,engine-dart-sdk.stamp一般内容一致, 是${FLUTTER_ROOT}/bin/internal/engine.version中的字串, 相对来说sdk中的内容我们一般不会变更, 如果这几个stamp文件内空不一样改成一致即可. 最容易出问题的其实是flutter_tools.stamp, 我们有时在${FLUTTER_ROOT}/packages/flutter_tools/lib 中的文件添加了一些log, 结果运行时内容打印不出来, 这时只要删除flutter_tools.stamp即可, flutter脚本(${FLUTTER_ROOT}/bin/flutter)会自行生成stamp并重建snapshot(${FLUTTER_ROOT}/bin/cache/flutter_tools.snapshot), 而dart最终运行的是snapshot文件. 所以一般情况下删除flutter_tools.stamp即可解决问题 另外如果想让${FLUTTER_ROOT}/packages/flutter_tools/lib里更改的内容实时生效, 将${FLUTTER_ROOT}/bin/flutter中引用的$SNAPSHOT_PATH改成$SCRIPT_PATH就可以实时查看dart脚本怎样同步工程, 怎样诊断环境等等所有事情:这意味着一旦脚本运行出现难懂的错误,我们就可以很容易的定位问题了!

June 22, 2019 · 1 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移动端实战手册

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> https://www.jianshu.com/p/d27c1f5ee3ff iOS接入Flutter在进行iOS和Flutter的混编时,iOS比Android的接入方式略复杂,但也还好。现在市面上有不少接入Flutter的方案,但大多数都是千篇一律相互抄的,没什么意义。 进行Flutter混编之前,有一些必要的文件。 xcode_backend.sh文件,在配置flutter环境的时候由Flutter工具包提供。xcconfig环境变量文件,在Flutter工程中自动生成,每个工程都不一样。xcconfig文件xcconfig是Xcode的配置文件,Flutter在里面配置了一些基本信息和路径,接入Flutter前需要先将xcconfig接入进来,否则一些路径等信息将会出错或找不到。 Flutter的xcconfig包含三个文件,Debug.xcconfig、Release.xcconfig、Generated.xcconfig,需要将这些文件配置在下面的位置,并且按照不同环境配置不同的文件。 Project -> Info -> Development Target -> Configurations 有些比较大的工程中已经在Configurations中设置了xcconfig文件,由于每个Target的一种环境只能配置一个xcconfig文件,所以可以在已有的xcconfig文件中import引入Generated.xcconfig文件,并且不需要区分环境。 脚本文件xcode_backend.sh脚本文件用来构建和导出Flutter产物,这是Flutter开发包为我们默认提供的。需要在工程Target的Build Phases加入一个Run Script文件,并将下面的脚本代码粘贴进去。需要注意的是,不要忘记前面的/bin/sh操作,否则会导致权限错误。 /bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed在xcode_backend.sh中有三个参数类型,build、thin、embed,thin没有太大意义,其他两个则负责构建和导出。 混合开发随后可以对Xcode工程进行编译,这时候肯定会报错的。但是不要慌张,报错后我们在工程主目录下会发现一个名为Flutter的文件夹,其中会包含两个framework,这个文件夹就是Flutter的编译产物,我们将这个文件夹整体拖入项目中即可。 这时候就可以在iOS工程中添加Flutter代码了,下面是详细步骤。 将AppDelegate的集成改为FlutterAppDelegate,并且需要遵循FlutterAppLifeCycleProvider代理。#import <Flutter/Flutter.h>#import <UIKit/UIKit.h>@interface AppDelegate : FlutterAppDelegate <FlutterAppLifeCycleProvider>@end创建一个FlutterPluginAppLifeCycleDelegate的实例对象,这个对象负责管理Flutter的生命周期,并从Platform侧接收AppDelegate的事件。我直接将其声明为一个属性,在AppDelegate中的各个方法中,调用其方法进行中转操作。- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self.lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions]; return YES;}- (void)applicationWillResignActive:(UIApplication *)application { [self.lifeCycleDelegate applicationWillResignActive:application];} - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { [self.lifeCycleDelegate application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; return YES;}随后即可加入Flutter代码,加入的方式也很简单,直接实例化一个FlutterViewController控制器即可,也不需要传其他参数进去(这里先不考虑多实例的问题)。FlutterViewController *flutterViewController = [[FlutterViewController alloc] init];Flutter将其看做是一个画布,实例化一个画布上去之后,任何操作其实都是在当前页面完成的。 ...

June 20, 2019 · 2 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

flutter实用系列六之启动第一屏第一屏显示跳过按钮

先上效果图flutter如果不设置启动的第一屏 根据flutter本来的设定为显示白色,也就是打开App的一瞬间到执行main函数的阶段会有一个白屏显示,这样难免会有点难看,很多app打开会有一个停留3秒或者几秒的图片,并有一个按钮让你点击跳过,那么往下看,这些是怎么实现的 设置第一屏 把图上的那一段代码注释去掉(flutter项目创建完,这段是注释着的)mipmap这个文件夹如果本来没有,就创建这个文件夹,然后把要启动的第一屏图片放进去,这样启动app的时候就会显示这一张图片了 android:gravity="fill"表示图片填充满屏幕大小 停留5秒和跳过按钮直接上代码,注释在代码里面 import 'package:flutter/material.dart';import 'dart:async';import 'package:flutter/rendering.dart';import 'package:flutter/services.dart';void main(){ // debugPaintSizeEnabled = true; runApp(MaterialApp( debugShowCheckedModeBanner: false, title: '签到', theme: ThemeData( primaryColor: Colors.yellow ), home: StartApp() ));}class StartApp extends StatefulWidget{ _StartAppState createState() => _StartAppState();}class _StartAppState extends State<StartApp>with SingleTickerProviderStateMixin{ var loginState; AnimationController _animationController; Animation _animation; void initState(){ super.initState(); //创建动画控制器 _animationController = AnimationController(vsync: this,duration: Duration(milliseconds: 5000)); _animation = Tween(begin: 1.0,end: 1.0).animate(_animationController); _animation.addStatusListener((status){ if(status == AnimationStatus.completed){ // //这边的添加动画的监听,当动画5秒后的状态是completed完成状态,则执行这边的代码,跳转到登录页,或者其他页面 // } }); _animationController.forward(); } @override void dispose(){ _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context){ return FadeTransition( opacity: _animation, child: ConstrainedBox( //让他的child充满整个屏幕 constraints: BoxConstraints.expand(), child: Stack( // children: <Widget>[ Container( height:double.infinity, //这边放一张图用于显示5秒的底图,这张图和上面的图一样,这样就有连贯起来的效果了 child:Image.asset( 'image/first.jpg', scale:1.0, fit:BoxFit.fill ), ), Positioned( top: 50.0, right: 20.0, child: FlatButton( color: Colors.green, highlightColor: Colors.blue, colorBrightness: Brightness.dark, splashColor: Colors.grey, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20.0) ), child: Text("跳过"), onPressed: (){ _animationController.dispose(); // //当点击跳过按钮的时候,则执行这边的代码,跳转到登 //录页,或者其他页面 }, ), ) ], ) ) ); }}

June 20, 2019 · 1 min · jiezi

重磅开源AOP-for-Flutter开发利器AspectD

https://github.com/alibaba-flutter/aspectd 问题背景随着Flutter这一框架的快速发展,有越来越多的业务开始使用Flutter来重构或新建其产品。但在我们的实践过程中发现,一方面Flutter开发效率高,性能优异,跨平台表现好,另一方面Flutter也面临着插件,基础能力,底层框架缺失或者不完善等问题。 举个栗子,我们在实现一个自动化录制回放的过程中发现,需要去修改Flutter框架(Dart层面)的代码才能够满足要求,这就会有了对框架的侵入性。要解决这种侵入性的问题,更好地减少迭代过程中的维护成本,我们考虑的首要方案即面向切面编程。 那么如何解决AOP for Flutter这个问题呢?本文将重点介绍一个闲鱼技术团队开发的针对Dart的AOP编程框架AspectD。 AspectD:面向Dart的AOP框架AOP能力究竟是运行时还是编译时支持依赖于语言本身的特点。举例来说在iOS中,Objective C本身提供了强大的运行时和动态性使得运行期AOP简单易用。在Android下,Java语言的特点不仅可以实现类似AspectJ这样的基于字节码修改的编译期静态代理,也可以实现Spring AOP这样的基于运行时增强的运行期动态代理。那么Dart呢?一来Dart的反射支持很弱,只支持了检查(Introspection),不支持修改(Modification);其次Flutter为了包大小,健壮性等的原因禁止了反射。 因此,我们设计实现了基于编译期修改的AOP方案AspectD。 设计详图 典型的AOP场景下列AspectD代码说明了一个典型的AOP使用场景: aop.dartimport 'package:example/main.dart' as app;import 'aop_impl.dart';void main()=> app.main();aop_impl.dartimport 'package:aspectd/aspectd.dart';@Aspect()@pragma("vm:entry-point")class ExecuteDemo { @pragma("vm:entry-point") ExecuteDemo(); @Execute("package:example/main.dart", "_MyHomePageState", "-_incrementCounter") @pragma("vm:entry-point") void _incrementCounter(PointCut pointcut) { pointcut.proceed(); print('KWLM called!'); }}面向开发者的API设计PointCut的设计 @Call("package:app/calculator.dart","Calculator","-getCurTime")PointCut需要完备表征以怎么样的方式(Call/Execute等),向哪个Library,哪个类(Library Method的时候此项为空),哪个方法来添加AOP逻辑。PointCut的数据结构: @pragma('vm:entry-point')class PointCut { final Map<dynamic, dynamic> sourceInfos; final Object target; final String function; final String stubId; final List<dynamic> positionalParams; final Map<dynamic, dynamic> namedParams; @pragma('vm:entry-point') PointCut(this.sourceInfos, this.target, this.function, this.stubId,this.positionalParams, this.namedParams); @pragma('vm:entry-point') Object proceed(){ return null; }}其中包含了源代码信息(如库名,文件名,行号等),方法调用对象,函数名,参数信息等。请注意这里的@pragma('vm:entry-point')注解,其核心逻辑在于Tree-Shaking。在AOT(ahead of time)编译下,如果不能被应用主入口(main)最终可能调到,那么将被视为无用代码而丢弃。AOP代码因为其注入逻辑的无侵入性,显然是不会被main调到的,因此需要此注解告诉编译器不要丢弃这段逻辑。此处的proceed方法,类似AspectJ中的ProceedingJoinPoint.proceed()方法,调用pointcut.proceed()方法即可实现对原始逻辑的调用。原始定义中的proceed方法体只是个空壳,其内容将会被在运行时动态生成。 ...

June 19, 2019 · 2 min · jiezi

flutter之网络请求dio请求拦截器简单示例

flutter一直很火的网络请求插件dio直接上代码,写成一个类,可以直接使用包含请求的封装,拦截器的封装 import 'package:dio/dio.dart';import 'dart:async';import 'dart:io';import './apidomain.dart';import './httpHeaders.dart';import 'package:shared_preferences/shared_preferences.dart';class DioUtil{ static Dio dio = new Dio(); //请求部分 static Future request(url,{formData})async{ try{ Response response; dio.options.headers = httpHeaders; dio.options.contentType = ContentType.parse("application/json;charset=UTF-8"); if(formData == null){ response = await dio.post(serviceUrl+url); }else{ response = await dio.post(serviceUrl+url,data:formData); } if(response.statusCode == 200){ return response; }else{ throw Exception("接口异常R"); } }catch(e){ print("网络出现错误${e}"); } } //拦截器部分 static tokenInter(){ dio.interceptors.add(InterceptorsWrapper( onRequest:(RequestOptions options){ // 在发送请求之前做一些预处理 //我这边是在发送前到SharedPreferences(本地存储)中取出token的值,然后添加到请求头中 //dio.lock()是先锁定请求不发送出去,当整个取值添加到请求头后再dio.unlock()解锁发送出去 dio.lock(); Future<dynamic> future = Future(()async{ SharedPreferences prefs =await SharedPreferences.getInstance(); return prefs.getString("loginToken"); }); return future.then((value) { options.headers["Authorization"] = value; return options; }).whenComplete(() => dio.unlock()); // unlock the dio }, onResponse:(Response response) { // 在返回响应数据之前做一些预处理 return response; // continue }, onError: (DioError e) { // 当请求失败时做一些预处理 return e;//continue } )); }}httpHeaders文件则是放一些请求头信息如下 ...

June 19, 2019 · 1 min · jiezi

Flutter中showDatePickershowTimePicker国际化更改中文

showDatePicker&showTimePicker国际化Flutter 中避免不了使用日期、时间选择器,这里我们使用官方showDatePicker与showTimePicker。 要想使得这两个组件为中文的先决条件为项目加入国际化(fluter_localizations)。 添加国际化在/pubspec.yaml中: ...dependencies: flutter: sdk: fultter flutter_localizations: # 添加 sdk: flutter # 添加.../lib/main.dart中: import 'package:flutter_localizations/flutter_localizations.dart'; // 添加// MaterialApp中加入...localizationsDelegates: [ GlobalMeterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate,],supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US'),],locale: const locale('zh'),...showDatePicker默认使用MaterialApp设置中的。 showDatePicker提供了locale参数,如果有需要可以继续更改。 showTimePicker默认使用MaterialApp设置中的。 showTimePicker没有提供locale参数,如果想改变语言可以使用: await showTimePicker( context: context, initialTime: TimeOfDay.now(), builder: (BuildContext context, Widget child) { return Localizations( locale: const Locale('zh'), child: child, delegates: <LocalizationsDelegate>[ GlobalMeterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ] ) })

June 14, 2019 · 1 min · jiezi

揭秘一个高准确率的Flutter埋点框架如何设计

背景用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失。闲鱼将业务代码从Native迁移到Flutter上过程中,发现原先Native体系上的埋点方案无法应用在Flutter体系之上。而如果我们只把业务功能迁移过来就上线,对业务是极其不负责任的。因此,经过不断探索,我们沉淀了一套Flutter上的高准确率的用户行为埋点方案。 用户行为埋点定义先来讲讲在我们这里是如何定义用户行为埋点的。在如下用户时间轴上,用户进入A页面后,看到了按钮X,然后点击了这个按钮,随即打开了新的页面B。 这个时间轴上有如下5个埋点事件发生: 进入A页面。A页面首帧渲染完毕,并获得了焦点。曝光坑位X。按钮X处于手机屏幕内,且停留一段时间,让用户可见可触摸。点击坑位X。用户对按钮X的内容很感兴趣,于是点击了它。按钮X响应点击,然后需要打开一个新页面。离开A页面。A页面失去焦点。进入B页面。B页面首帧渲染完毕,并获得焦点。在这里,打埋点最重要的是时机,即在什么时机下的事件中触发什么埋点,下面来看看闲鱼在Flutter上的实现方案。 实现方案进入/离开页面 在Native原生开发中,Android端是监听Activity的onResume和onPause事件来做为页面的进入和离开事件,同理iOS端是监听UIViewController的viewWillAppear和viewDidDisappear事件来做为页面的进入和离开事件。同时整个页面栈是由Android和iOS操作系统来维护。 在Flutter中,Android和iOS端分别是用FlutterActivity和FlutterViewController来做为容器承载Flutter的页面,通过这个容器可以在一个Native的页面内(FlutterActivity/FlutterViewController)来进行Flutter原生页面的切换。即在Flutter自己维护了一个Flutter页面的页面栈。这样,原来我们最熟悉的那套在Native原生上方案在Flutter上无法直接运作起来。 针对这个问题,可能很多人会想到去注册监听Flutter的NavigatorObserver,这样就知道Flutter页面的进栈(push)和出栈(pop)事件。但是这会有两个问题: 假设A、B两个页面先后进栈(A enter -> A leave -> B enter)。然后B页面返回退出(B leave),此时A页面重新可见,但是此时是收不到A页面push(A enter)的事件。假设在A页面弹出一个Dialog或者BottomSheet,而这两类也会走push操作,但实际上A页面并未离开。好在Flutter的页面栈不像Android Native的页面栈那么复杂,所以针对第一个问题,我们可以来维护一个和页面栈匹配的索引列表。当收到A页面的push事件时,往队列里塞一个A的索引。当收到B页面的push事件时,检测列表内是否有页面,如有,则对列表最后一个页面执行离开页面事件记录,然后再对B页面执行进入页面事件记录,接着往队列里塞一个B的索引。当收到B页面的pop事件时,先对B页面执行离开页面事件记录,然后对队列里存在的最后一个索引对应的页面(假设为A)进行判断是否在栈顶(ModalRoute.of(context).isCurrent),如果是,则对A页面执行进入页面事件记录。 针对第二个问题,Route类内有个成员变量overlayEntries,可以获取当前Route对应的所有图层OverlayEntry,在OverlayEntry对象中有个成员变量opaque可以判断当前这个图层是否全屏覆盖,从而可以排除Dialog和BottomSheet这种类型。再结合问题1,还需要在上述方案中加上对push进来的新页面来做判断是否为一个有效页面。如果是有效页面,才对索引列表中前一个页面做离开页面事件,且将有效页面加到索引列表中。如果不是有效页面,则不操作索引列表。 以上并不是闲鱼的方案,只是笔者给出的一个建议。因为闲鱼APP在一开始落地Flutter框架时,就没有使用Flutter原生的页面栈管理方案,而是采用了Native+Flutter混合开发的方案。具体可参考前面的一篇文章《已开源|码上用它开始Flutter混合开发——FlutterBoost》。因此接下来也是基于此来阐述闲鱼的方案。 闲鱼的方案如下(以Android为例,iOS同理): 注:首次打开指的是基于混合栈新打开一个页面,非首次打开指的是通过回退页面的方式,在后台的页面再次到前台可见。 看似我们将何时去触发进入/离开页面事件的判断交给Flutter侧,实际上依然跟Native侧的页面栈管理保持了一致,将原先在Native侧做打埋点的时机告知Flutter侧,然后Flutter侧再立刻通过channel来调用Native侧的打埋点方法。那么可能会有人问,为什么这么绕,不全部交给Native侧去直接管理呢?交给Native侧去直接管理这样做针对非首次打开这个场景是合适的,但是对首次打开这个场景却是不合适的。因为在首次打开这个场景下,onResume时Flutter页面尚未初始化,此时还不知道页面信息,因此也就不知道进入了什么页面,所以需要在Flutter页面初始化(init)时再回过来调Native侧的进入页面埋点接口。为了避免开发人员去关注是否为首次打开Flutter页面,因此我们统一在Flutter侧来直接触发进入/离开页面事件。 曝光坑位先讲下曝光坑位在我们这里的定义,我们认为图片和文本是有曝光意义的,其他用户看不见的是没有曝光意义的,在此之上,当一个坑位同时满足以下两点时才会被认为是一次有效曝光: 坑位在屏幕可见区域中的面积大于等于坑位整体面积的一半。坑位在屏幕可见区域中停留超过500ms。基于此定义,我们可以很快得出如下图所示的场景,在一个可以滚动的页面上有A、B、C、D共4个坑位。其中: 坑位A已经滑出了屏幕可见区域,即invisible;坑位B即将向上从屏幕中可见区域滑出,即visible->invisible;坑位C还在屏幕中央可视区域内,即visible;坑位D即将滑入屏幕中可见区域,invisible->visible; 那么我们的问题就是如何算出坑位在屏幕内曝光面积的比例。要算出这个值,需要知道以下几个数值: 容器相对屏幕的偏移量坑位相对容器的偏移量坑位的位置和宽高容器的位置和宽高其中坑位和容器的宽和高很容易获取和计算,这里就不再累述。 获取容器相对屏幕的偏移量 //监听容器滚动,得到容器的偏移量double _scrollContainerOffset = scrollNotification.metrics.pixels;获取坑位相对容器的偏移量 //曝光坑位Widget的contextfinal RenderObject childRenderObject = context.findRenderObject();final RenderAbstractViewport viewport = RenderAbstractViewport.of(childRenderObject);if (viewport == null) { return;}if (!childRenderObject.attached) { return;}//曝光坑位在容器内的偏移量final RevealedOffset offsetToRevealTop = viewport.getOffsetToReveal(childRenderObject, 0.0);逻辑判断 if (当前坑位是invisible && 曝光比例 >= 0.5) { 记录当前坑位是visible状态 记录出现时间} else if (当前坑位是visible && 曝光比例 < 0.5) { 记录当前坑位是invisible状态 if (当前时间-出现时间 > 500ms) { 调用曝光埋点接口 }}点击坑位 ...

June 13, 2019 · 1 min · jiezi

flutter之chartsflutter图表插件超简单实践

先上图 引入插件在pubspec.yaml中引入charts_flutter插件 使用的时候版本到0.6.0,插件地址:https://github.com/google/charts 使用插件import 'package:flutter/material.dart';引入插件import 'package:charts_flutter/flutter.dart' as charts;class Test extends StatefulWidget{ _TestState createState() => _TestState();}class _TestState extends State<Test> { String _year; int _sales; //点击柱状图触发的函数 _onSelectionChanged(charts.SelectionModel model) { final selectedDatum = model.selectedDatum; print(selectedDatum.first.datum.year); print(selectedDatum.first.datum.sales); print(selectedDatum.first.series.displayName); setState(() { //改变两个显示的数值 _year = selectedDatum.first.datum.year; _sales = selectedDatum.first.datum.sales; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar( title: Text("图表"), centerTitle: true, ), body:Container( child: Column( children: <Widget>[ Row( children: <Widget>[ Expanded( child: Container( alignment: Alignment.center, child: Text("年份:${_year}"), ), ), Expanded( child: Container( alignment: Alignment.center, child: Text("数值:${_sales}"), ), ) ], ), Container( width: double.infinity, height: 200.0, child: charts.BarChart( //通过下面获取数据传入 ChartFlutterBean.createSampleData(), //配置项,以及设置触发的函数 selectionModels: [ charts.SelectionModelConfig( type: charts.SelectionModelType.info, changedListener: _onSelectionChanged, ) ], ), ), ], ), ) ); }}//一下为组合柱状图数据部分class OrdinalSales { final String year; final int sales; OrdinalSales(this.year, this.sales);}class ChartFlutterBean { static List<charts.Series<OrdinalSales, String>> createSampleData() { final data = [ new OrdinalSales('2014', 5), new OrdinalSales('2015', 25), new OrdinalSales('2016', 100), new OrdinalSales('2017', 75), ]; return [ new charts.Series<OrdinalSales, String>( id: 'Sales', colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, domainFn: (OrdinalSales sales, _) => sales.year, measureFn: (OrdinalSales sales, _) => sales.sales, data: data, ) ]; }}

June 13, 2019 · 1 min · jiezi

2019年Flutter-和-React-Native-谁主沉浮

Flutter 与 React Native混淆了吗?本文是帮助你了解这两个应用程序开发框架区别指南。咱们知道,几年前开发和维护iOS和Android的应用程序曾经是一项艰巨的任务(独立的代码库|独立的开发团队|开发成本也忒高)。 一堆狗屎。 移动行业渴望进行一场革命,以遏制移动应用程序开发过程中出现的问题。 因此,跨平台开发的形式就此出现了。现在,维护代码和开发应用程序对于开发人员来说变得简单且耗时也少了。 跨平台应用程序开发什么时候出现的?不仅开发商,企业和初创公司也通过为跨平台应用的方式来开发他们的业务。不出所料,他们喜欢它。 为了提高应用开发的效率,越来越多的跨平台应用开发框架应运而生。 脸书在2015年又跳了回来,推出了React native。 毫无疑问,它得到很好的回应。如今,React native 是 Facebook、沃尔玛(Walmart)、优步(UberEats)、Instagram 和特斯拉(Tesla)等应用程序的幕后支持者。 后来,谷歌也加入了进来,并推出了广受好评的跨平台框架 Flutter。并保证了所有应用程序都具有原生性能。 从那时起,新创公司和企业就面临着如何选择应用程序开发的两难境地。这使得 Flutter 与 React native 的争论更加激烈。 在本文中,我们将讨论React Native 和 Google 的 Flutter 之间备受争议的论点。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 什么是 FlutterFlutter 是谷歌的移动UI框架,可以快速在 iOS 和 Android上构建高质量的原生用户界面。 什么是 React NativeReact Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域。 现在你已经有了基本的认识,让咱们来看看在 2019 年的 React nNtive 和 Flutter 中哪个更好? Flutter vs React Native:详细比较让我们详细看看这两个平台之间的差异,并找出使用 React native 和Flutter 的优缺点。 程序设计语言跨平台的应用程序开发框架都使用不同的编程语言。 React native 可以使用 Javascript开发,这不需要任何介绍。长期以来,它一直是开发人员最好的编程语言。 ...

June 13, 2019 · 2 min · jiezi

一款基于Flutter开发的语雀APP

基于语雀API打造APP,由Flutter开发:可以查阅个人仓库可以查阅团队仓库完善地解析markdown语法极速渲染欢迎使用! 下载地址:http://levy.ren 关于我们:https://femessage.github.io/b...

June 12, 2019 · 1 min · jiezi

Flutter-局部路由实现

Flutter是借鉴React的开发思想实现的,在子组件的插槽上,React有this.props.children,Vue有<slot></slot>。 当然Flutter也有类似的Widget,那就是Navigator,不过是以router的形式实现(像<router-view></router-view>???)。 Navigator的使用无非3个属性 initialRoute: 初始路由onGenerateRoute: 匹配路由onUnknownRoute: 404在实现层面 首先:Navigator的高度为infinity。如果直接父级非最上级也是infinity会产生异常,例如,Scaffold -> Column -> Navigator。所以:Navigator需要附件限制高度,例如:Scaffold -> Column -> Container(height: 300) -> Navigator 然后:在onGenerateRoute属性中,使用第一个BuildContext参数,能够在MaterialApp未设置route的情况下使用Navigator.pushNamed(nContext, '/efg');跳到对应的子路由中。 最后:Navigator执行寻找路由顺序是 initialRoute -> onGenerateRoute -> onUnknownRoute,这个和React的Route是类似的。 最后附上源码 import 'package:flutter/material.dart';class NavigatorPage extends StatefulWidget { @override _NavigatorPageState createState() => _NavigatorPageState();}class _NavigatorPageState extends State<NavigatorPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Navigator'), ), body: Column( children: <Widget>[ Text('Navigator的高度为infinity'), Text('如果直接父级非最上级也是infinity会产生异常'), Container( height: 333, color: Colors.amber.withAlpha(111), child: Navigator( // Navigator initialRoute: '/abc', onGenerateRoute: (val) { RoutePageBuilder builder; switch (val.name) { case '/abc': builder = (BuildContext nContext, Animation<double> animation, Animation<double> secondaryAnimation) => Column( // 并没有在 MaterialApp 中设定 /efg 路由 // 因为Navigator的特性 使用nContext 可以跳转 /efg children: <Widget>[ Text('呵呵呵'), RaisedButton( child: Text('去 /efg'), onPressed: () { Navigator.pushNamed(nContext, '/efg'); }, ) ], ); break; case '/efg': builder = (BuildContext nContext, Animation<double> animation, Animation<double> secondaryAnimation) => Row( children: <Widget>[ RaisedButton( child: Text('去 /hhh'), onPressed: () { Navigator.pushNamed(nContext, '/hhh'); }, ) ], ); break; default: builder = (BuildContext nContext, Animation<double> animation, Animation<double> secondaryAnimation) => Center( child: RaisedButton( child: Text('去 /abc'), onPressed: () { Navigator.pushNamed(nContext, '/abc'); }, ) ); } return PageRouteBuilder( pageBuilder: builder, // transitionDuration: const Duration(milliseconds: 0), ); }, onUnknownRoute: (val) { print(val); }, observers: <NavigatorObserver>[] ), ), Text('Navigator执行寻找路由顺序'), Text('initialRoute'), Text('onGenerateRoute'), Text('onUnknownRoute'), ], ), ); }}项目地址: https://github.com/zhongmeizhi/fultter-example-app撸完代码记得给颗星哦。 ...

June 10, 2019 · 1 min · jiezi

Flutter各种API使用

将Flutter各种Widget各种API都实现一次。用于给初学者提供Flutter可视化Widget,方便初学者学习和使用,同时给自己的Flutter提供备忘、代码copy功能。 点击Widget清单的按钮会跳转到各自对应的Widget实现页面。 项目代码地址 https://github.com/zhongmeizhi/flutter-UI 参考链接: 开发文档功能齐全的flutter项目

June 6, 2019 · 1 min · jiezi

flutter之Tabbar实现两种方式直接切换IndexedStack过渡动画切换PageView

学习flutter过程中制作底部导航栏的写法先上图 首页,备用,我的三个栏目切换 方法一(不带过度动画,IndexedStack方式)import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:flutter/services.dart';//引入3页import './car_page.dart';import './my_page.dart';import './index_page.dart';class HomePage extends StatefulWidget{ @override _HomePageState createState() => _HomePageState();}class _HomePageState extends State<HomePage>{ //这是监听安卓的返回键操作 Future<bool> _onWillPop() { return showDialog( context: context, builder: (context) => new AlertDialog( title: new Text('退出App?'), content: new Text('Do you want to exit an App'), actions: <Widget>[ new FlatButton( onPressed: () => Navigator.of(context).pop(false), child: new Text('不'), ), new FlatButton( onPressed: () async { await pop(); }, child: new Text('是的'), ), ], ), ) ?? false; } static Future<void> pop() async { await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } final List<BottomNavigationBarItem> bottomTabs = [ BottomNavigationBarItem( icon: Icon(CupertinoIcons.home), title: Text("首页") ), BottomNavigationBarItem( icon: Icon(CupertinoIcons.search), title: Text("备用") ), BottomNavigationBarItem( icon: Icon(CupertinoIcons.profile_circled), title: Text("我的") ) ]; final List<Widget> tabBodies = [ IndexPage(), CarPage(), MyPage(), ]; int currentIndex = 0; var currenPage; @override void initState(){ currenPage = tabBodies[currentIndex]; super.initState(); } @override Widget build(BuildContext context){ return WillPopScope( onWillPop: _onWillPop, child: Container( child: Scaffold( bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: currentIndex, items: bottomTabs, onTap: (index){ setState(() { currentIndex = index; currenPage = tabBodies[currentIndex]; }); }, ), body: IndexedStack( index: currentIndex, children: tabBodies, ), ), ) ); }}方法二(带过度动画,PageView方式)import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:flutter/services.dart';//引入3个切换的页面import './car_page.dart';import './my_page.dart';import './index_page.dart';class HomePage extends StatefulWidget{ @override _HomePageState createState() => _HomePageState();}//要混入SingleTickerProviderStateMixinclass _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin{ //这是监听安卓的返回键操作 Future<bool> _onWillPop() { return showDialog( context: context, builder: (context) => new AlertDialog( title: new Text('退出App?'), content: new Text('Do you want to exit an App'), actions: <Widget>[ new FlatButton( onPressed: () => Navigator.of(context).pop(false), child: new Text('不'), ), new FlatButton( onPressed: () async { await pop(); }, child: new Text('是的'), ), ], ), ) ?? false; } static Future<void> pop() async { await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } //建立下面三个按钮数组 final List<BottomNavigationBarItem> bottomTabs = [ BottomNavigationBarItem( icon: Icon(CupertinoIcons.home), title: Text("首页") ), BottomNavigationBarItem( icon: Icon(CupertinoIcons.search), title: Text("备用") ), BottomNavigationBarItem( icon: Icon(CupertinoIcons.profile_circled), title: Text("我的") ) ]; //把3个页面排成数组形式方便切换 final List<Widget> tabBodies = [ IndexPage(), CarPage(), MyPage(), ]; int currentIndex = 0; //创建页面控制器 var _pageController; @override void initState(){ //页面控制器初始化 _pageController = new PageController(initialPage : 0); super.initState(); } @override Widget build(BuildContext context){ //WillPopScope监听安卓返回键 return WillPopScope( onWillPop: _onWillPop, child: Container( child: Scaffold( bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: currentIndex, items: bottomTabs, onTap: (index){ setState(() { currentIndex = index; }); //点击下面tabbar的时候执行动画跳转方法 _pageController.animateToPage(index, duration: new Duration(milliseconds: 500),curve:new ElasticOutCurve(4)); }, ), body: PageView( controller: _pageController, children: tabBodies, onPageChanged: (index){ currentIndex = index; }, ), ), ) ); }}

June 5, 2019 · 2 min · jiezi

推荐几个flutter教程小册git原理小册前端面试宝典

June 5, 2019 · 0 min · jiezi

flutter-web初探

距离flutter正式发布已经有半年了。目前flutter发展如日中天,从新加qq群的朋友数量就可以看出来。flutter早已经支持了桌面版的开发,前一段时间又出来了web版。看来离flutter一统江湖,哦不,一统前端的时间已经不远了:)。现在就差小程序这么个前端领域没有被flutter触及到了,对于国人的diy能力,我是深信的。如果真有那么一天,相信flutter也能翻译成小程序的版本,看看taro就知道了。 好了闲话说完,下面正式进入主题: 环境安装flutter安装flutter的最新版本,还是建议直接从官网直接下载,目前地址: https://storage.googleapis.co... 下载完毕后解压缩到一个目录中,并且配置一下环境变量,这个在我们的环境安装篇里面已经讲过了,这里不再重复。 完毕之后运行一下 flutter precache配置web版环境按照官方的要求:https://github.com/flutter/fl... 运行一下这个命令: flutter pub global activate webdev 这个步骤应该没有毛病,相当顺利。 不过出现了这么个警告: 我们依照这里的指令,将export PATH="$PATH":"$HOME/working/flutter/.pub-cache/bin"加入配置。 我们把官方的demo clone下来, git clone https://github.com/flutter/flutter_web.git进入到example中的hello_world,运行下flutter pub upgrade cd examples/hello_worldflutter pub upgrade 然后运行webdev serve 这里提示了dart: command not found 显然,这里是少了dart这个命令,之前没有将dart sdk加入环境变量中,经过一番搜索,发现原来在这里: flutter/bin/cache/dart-sdk/bin 我们将这个加入到环境变量 再次运行: 显然需要运行: pub get 再次运行: 效果: 来个复杂点的例子:进入examples/gallery 运行 pub getwebdev serve效果:

June 4, 2019 · 1 min · jiezi

在vscode中开发flutter常用快捷键

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

June 3, 2019 · 1 min · jiezi

一个优秀的可定制化Flutter相册组件看这一篇就够了

背景在做图片、视频相关功能的时候,相册是一个绕不开的话题,因为大家基本都有从相册获取图片或者视频的需求。最直接的方式是调用系统相册接口,基本功能是满足的,一些高级功能就不行了,例如自定义UI、多选图片等。 我们调研了官方的image_picker,它也是调用系统的相册接口来处理的,可定制程度不高,不能满足我们的要求。所以我们选择自己来开发Flutter相册组件。 我们的组件需要有如下的功能: 在app内完成图片、视频的选取,完全不用依赖系统相册组件可以多选图片,支持指定选定图片的总数目在多选的时候UI反应出选择的序号。可以控制视频、图片的选择。例如:只让用户选择视频,图片是灰色的。大图预览的时候可以放大缩小,也可直接加入到选取列表。设计思路API使用简单,功能丰富灵活,具有较高的订制性。业务方可以选择完全接入组件,也可以选择在组件上面进行UI定制。 Flutter做UI展现层,具体的数据由各Native平台提供。这种模式,天然从工程上把UI代码和数据代码进行了隔离。我们在开发一个native组件的时候常常会使用MVC架构。Flutter组件的开发的思路也基本类似。整体架构如下: 可以看出,在Flutter侧是一个典型的MVC架构,这里Widget就是View,View和Model绑定,在Model改变的时候View会重新build反映出Model的变化。View的事件会触发Controller去Native获取数据然后更新Model。Native和Flutter通过Method Channel进行通信,两层之间没有强依赖关系,只需要按约定的协议进行通信即可。 Native侧的组成部分,UIAdapter主要是负责机型的适配、刘海屏、全面屏之类的识别。Permission负责媒体读写权限的申请处理。Cache主要负责缓存GPU纹理,在大图预览的时候提高响应速度。Decoder负责解析Bitmap,OpenGL负责Bitmap转纹理。 需要说明的是:我们的这一套实现依赖于flutter外接纹理。在整个相册组件看到的大多数图片都是一个GPU纹理,这样给java堆内存的占用相对于以前的相册实现有大幅的降低。在低端机上面如果使用原生的系统相册,由于内存的原因,app有被系统杀掉的风险。现象就是,从系统相册返回,app重新启动了。使用Flutter相册组件,在低端机上面体验会有所改观。 一些细节1. 分页加载 相册列表需要加载大量图片,Flutter的GridView组件有好几个构造函数,比较容易犯的错误是使用了第一个函数,这需要在一开始就提供大量的widget。应该选择第二个构造函数,GridView在滑动的时候会回调IndexedWidgetBuilder来获取widget,相当于一种懒加载。 GridView.builder({ ... List<Widget> children = const <Widget>[], ... })GridView.builder({ ... @required IndexedWidgetBuilder itemBuilder, int itemCount, ... })滑动过程中,图片滑过后,也就是不可见的时候要进行资源的回收,我们这里这里对应的就是纹理的删除。不断的滑动GridView,内存在上升后会处于稳定,不会一直增长。如果快速的来回滑动纹理会反复的创建和删除,这样会有内存的抖动,体验不是很好。 于是,我们维护了一个图片的状态机,状态有None,Loading,Loaded,Wait_Dispose,Disposed。开始加载的时候,状态从None进入Loading,这个时候用户看到的是空白或者是占位图,当数据回调回来会把状态设置为Loaded的这时候会重新build widget树来显示图片icon,当用户滑走的时候状态进入 Wait_Dispose,这时候并不会马上Dispose,如果用户又滑回来则会从Wait_Dispose进入Loaded状态,不会继续Dispose。如果用户没有往回滑则会从Wait_Dispose进入Disposed状态。当进入Disposed状态后,再需要显示该图片的时候就需要重新走加载流程了。 2. 相册大图展示: 当点击GridView的某张图片的时候会进行这张图片的大图展示,方便用户查看的更清楚。我们知道相机拍摄的图片分辨率都是很高的,如果完全加载,内存会有很大的开销,所以我们在Decode Bitmap的时候进行了缩放,最高只到1080p。大图展示可以概括为三个步骤。 1 从文件Decode出Bitmap2 Bitmap转换成为纹理,并释放Bitmap3 纹理交给Flutter进行展示在步骤1中,Android原生的Bitmap Decode经验同样适用,先Decode出Bitmap的宽高,然后根据要展示的大小计算出缩放倍数, 然后Decode出需要的Bitmap。 Android相册的图片大多是有旋转角度的,如果不处理直接显示,会出现照片旋转90度的问题,所以需要对Bitmap进行旋转,采用Matrix旋转一张1080p的图片在我的测试机器上面大概需要200ms,如果使用OpenGL的纹理坐标进行旋转,大于只需要10ms左右,所以采用OpenGl进行纹理的旋转是一个较好的选择。 在进行大图预览的时候会进入一个水平滑动的PageView,Flutter的PageView一般来说是不会去主动加载相邻的page的。举个例子,在显示index是5的page的时候index为4,6的page也不会提前创建的。这里有一个取巧的办法,对于PageController的viewportFraction参数我们可以设置成为0.9999。对于前面这个例子,就是在显示index是5的page的时候,index为4,6的page也需要显示0.0001。这样index为4,6的page显示不到1个像素,基本上看不出来: PageController(viewportFraction=0.9999)还有另外一种办法,就是在Native侧做预加载。例如:在加载第5张图片的时候,相邻的4,6的图片纹理提前进行加载,当滑动到4,6的时候直接使用缓存的纹理。 纹理缓存后,一个直接的问题:什么时候释放纹理?等到预览页面退出的时候释放所有的纹理显示不是很合适,如果用户一直浏览内存则会无限增长。所以,我们维护了一个5个纹理的LRU缓存,在滑动过程中,最老的纹理会被释放掉。在页面退出的时候整个LRU的缓存会进行销毁。 3. 关于内存 相册图片使用GPU纹理,会大幅减少Java堆内存的占用,对整个app的性能有一定的提升。需要注意的是,GPU的内存是有限的需要在使用完毕后及时删除,不然会有内存的泄漏的风险。另外,在Android平台删除纹理的时候需要保证在GPU线程进行,不然删除是没有效果的。 在华为P8,Android5.0上面进行了对比测试,Flutter相册和原native相册总内存占用基本一致,在GridView列表页面,新增最大内存13M左右。它们的区别在于原native相册使用的是Java堆内存,Flutter相册使用的是Native内存。 总结相册组件API简单、易用,高度可定制。Flutter侧层次分明,有UI订制需求的可以重写Widget来达到目的。另外这是一个不依赖于系统相册的相册组件,自身是完备的,能够和现有的app保持UI、交互的一致性。同时为后面支持更多和相册相关的玩法打好基础。 后续计划由于我们使用的是GPU纹理,可以考虑支持显示高清4K图片,而且客户端内存不会有太大的压力。但是4k图片的Bitmap转纹理需消耗更多的时间,UI交互上面需要做些loading状态的支持。 组件功能丰富,稳定后,进行开源,回馈给社区。 本文作者:闲鱼技术-邻云阅读原文 本文为云栖社区原创内容,未经允许不得转载。

May 31, 2019 · 1 min · jiezi

移动端跨平台方案如何选择

May 31, 2019 · 0 min · jiezi

Flutter-packages-vs-plugins-vs-app

Flutter App(flutter create hello_world)整个文件结构 android文件夹结构 lib入口文件 Flutter Packages (flutter create --template=package hello_world)整个文件结构 lib入口文件 Flutter plugins (flutter create --template=plugin hello_world)整个文件结构 android文件夹详情 example/android文件夹详情 lib入口文件 补充中

May 30, 2019 · 1 min · jiezi

Flutter-混合开发-交互通信

上篇我们介绍了 Flutter 模块集成到已有的项目工程,接下来我们看看 Native 跟 Flutter 间的交互问题。 交互通信 Flutter 与原生之间的通信依赖灵活的消息传递方式: 1,Flutter 部分通过平台通道将消息发送到其应用程序的所在的宿主环境(原生应用)。 2,宿主环境通过监听平台通道,接收消息。然后它会调用平台的 API,响应 Flutter 发送的消息。 Flutter主动 调用 宿主环境 在 Flutter 中通过 MethodChannel 的 API 可以发送与方法相对于的消息,宿主环境 iOS 中通过 FlutterMethodChannel 接受方法的调用并返回结果。 Flutter 需要引入 services.dart 模块才可以使用 MethodChannel import 'package:flutter/services.dart';Flutter 中的调用代码 const methodChannel = const MethodChannel("com.pages.flutter/call_native");RaisedButton( child: Text("call_native_method_no_params"), onPressed: (){ methodChannel.invokeMethod("call_native_method_no_params",null); }, ), RaisedButton( child: Text("call_native_method_params"), onPressed: (){ Map<String,String> params = {"params":"flutter params"}; methodChannel.invokeMethod("call_native_method_params",params); }, ), RaisedButton( child: Text("call_native_method_native_result_callback"), onPressed: (){ _nativeCallbackWithParams(); }, ), Text(_content,style: TextStyle(color: Colors.red),)Future<Null> _nativeCallbackWithParams() async{ dynamic result; try { result = await methodChannel.invokeMethod( "call_native_method_native_result_callback", null); } on PlatformException catch (e) { result = "Failed to get params: '${e.message}'."; } setState(() { _content = result; });}iOS 中的调用代码 ...

May 29, 2019 · 2 min · jiezi

Flutter-混合开发集成到原生-iOS-项目

对于 Flutter 的业务应用,我们更希望它可以集成到已有的项目中,这篇详细的介绍下如何将 Flutter 集成到 iOS 项目工程中,对于后续的通信、交互、管理等内容会在后面的篇章中介绍。 创建 Flutter 模块 创建 iOS 工程项目,命名为 FlutterMixDemo ,当然你也可以用已有的工程来集成。 注意1:将我们项目 BitCode 选项设置为 NO , Flutter 目前还不支持。 接下来我们需要创建 Flutter 模块,进入已有工程目录,这里拿我的工程目录举例: flutterMix/FlutterMixDemo/ (FlutterMixDemo 是我的 iOS 工程项目)进入在 flutterMix 目录下,终端执行命令:flutter create -t module flutter_module 注意2:flutter_module 是自己命名的,但要记得字母都要小写,不然会报错。 该命令会创建一个 Flutter 项目模块,我们可以看下它的项目结构及内容。 将 Flutter 模块以 pods 的方式加入到已有项目中 在我们的已有项目 FlutterMixDemo 中初始化 pods ,当然如果你的项目中已初始化过 pods ,请忽略。 cd FlutterMixDemopod init这时我们项目中会多一个 Podfile 文件,我们在该文件最后面添加命令如下: target 'FlutterMixDemo' doend# 新加命令flutter_application_path = '../flutter_module'eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)注意3:flutter_application_path 是 Flutter 模块的路径,记得修改为你的模块名称。 ...

May 28, 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

Dealing-with-box-constraints

相关概念BoxConstraints对RenderBox布局进行不可变的布局约束,如果满足如下条件之一,就说明一个Size遵从BoxConstraints约束: minWidth <= Size.width <= maxWidthminHeight <= Size.height <= maxHeight另外约束本身也要满足如下关系: 0.0 <= minWidth <= maxWidth <= double.infinity0.0 <= minHeight <= maxHeight <= double.infinity关键术语坐标轴tightly: minimum == maximum坐标轴loose: minimum==0.0坐标轴bounded: maximum != infinite坐标轴unbounded: maximum == infinite坐标轴infinite: minimum == infinite盒布局模型布局模型把约束从父传给子组件,然后再把具体几何约束从子传给父组件。渲染对象定位子组件和渲染子组件两者是独立的,父组件使用子组件的尺寸来决定他们的位置。子组件并不知道自己的位置的,所以子组件位置变了,子组件也不会重新布局或者重绘。

May 24, 2019 · 1 min · jiezi

Flutter-原理之跨平台

通过最近对 Flutter 开发的大致了解,感受最深的简单概括就是:Widget 就是一切外加组合和响应式,我们开发的界面,通过组合其他的 Widget 来实现,当界面发生变化时,不会像我们原来 iOS 或者 Andriod 开发一样去直接修改 UI 元素,而是去去更新状态,根据新的状态来完成 UI 的构建,这一点是不是跟 Vue、React 很像。 关于 Widget 的具体使用,我也不打算去写,如果需要对某个知识点需要深入探索下的话,可能会记录下,其实对 Widget 学习大家可以参考 Flutter Api 文档地址如下:https://api.flutter.dev/> ,... 。 要理解 Flutter 跨平台实现的原因,先了解下屏幕显示图像的基本原理。 屏幕显示器都是有一个个的物理显示单元构成,每个物理单元称之为一个像素点,每一个像素点可以承载多种颜色的显示,屏幕显示器能够显示图像就是因为不同的像素点上呈现的不同的颜色,最终呈现出来一个完整的图像。 通常说的同一面积屏幕显示器,屏幕分辨率越高,显示器可以呈现的元素就会越多,我们看到的画面就会越清晰。 我们知道手机屏幕刷新频率是 60Hz ,当一帧画面显示完成后,准备下一帧时,显示器会发出一个垂直同步信号,这样在 1s 内显示就完成了 60 次这样的操作,而每次操作中,都是完成 CPU 将计算好的内容,同步到 GPU ,GPU 对要显示的内容进过渲染操作,放入到了缓冲区中,等待显示器去显示,整个操作都是有操作系统的硬件系统来完成的。 iOS 、Andriod移动设备的 GUI 显示都是这样一个原理,其实不同操作系统 UI 控件,只是操作系统去操作硬件系统 API 的一层封装,如果我们直接操作底层 API 去完成 GUI 开发是一件非常痛苦的事。 Flutter 只是用一种编程语言,也就是采用一套Dart API ,底层通过 OpenGL(操作系统 API 的一个封装库)这种跨平台的绘制库,来实现一套代码跨端使用,也就是说 Flutter 所谓跨平台只是 Dart 调用 OpenGL ,然后 OpenGL 再去调用操作系统底层的 API 。跟 ReactNative 、weex 不同的是,他们需要依赖 JavaScriptCore 引擎去跟原生应用的通信。 ...

May 24, 2019 · 1 min · jiezi

Flutter高内聚组件怎么做阿里闲鱼打造开源高效方案

fish_redux是闲鱼技术团队打造的flutter应用开发框架,旨在解决页面内组件间的高内聚、低耦合问题。开源地址:https://github.com/alibaba/fish-redux 从react_redux说起redux对于前端的同学来说是一个比较熟悉的框架了,fish_redux借鉴了redux单项数据流思想。在flutter上说到redux,大家可能第一反应会类比到react上的react_redux。在react_redux中有个重要的概念——connect, connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])简单得说,connect允许使用者从Redux store中获取数据并绑定到组件的props上,可以dispatch一个action去修改数据。 那么fish_redux中的connector是做什么的呢?为什么说connector解决了组件内聚的问题?我们应该如何理解它的设计呢? connector in fish_redux尽管都起到了连接的作用,但fish_redux与react_redux在抽象层面有很大的不同。 fish_redux本身是一个flutter上的应用框架,建立了自己的component体系,用来解决组件内的高内聚和组件间的低耦合。从connector角度来说,如何解决内聚问题,是设计中的重要考量。 fish_redux自己制造了Component树,Component聚合了state和dispatch,每一个子Component的state通过connector从父Component的state中筛选。如图所示: 可以看到,fish_redux的connector的主要作用把父子Component关联起来,最重要的操作是filter。state从上之下是一个严谨的树形结构,它的结构复用了Component的树形结构。类似一个漏斗形的数据管道,管理数据的分拆与组装。它表达了如何组装一个Component。 而对于react_redux来说,它主要的作用在于把react框架和redux绑定起来,重点在于如何让React component具有Redux的功能。 从图中可以看到,react_redux和React是平行的结构,经过mapStateToProps后的state也不存在严谨的树形结构,即对于一个React component来说,它的state来自于Redux store而不是父component的state。从框架设计的角度来说,react_redux最重要的一个操作就是attach。 源码分析说完概念,我们从源码的角度来看看fish_redux中的connector是如何运作的,以fish_redux提供的example为例。 class ToDoListPage extends Page<PageState, Map<String, dynamic>> { ToDoListPage() : super( ... dependencies: Dependencies<PageState>( adapter: ToDoListAdapter(), slots: <String, Dependent<PageState>>{ 'report': ReportConnector() + ReportComponent() }), ... );}在ToDoListPage的构造函数中,向父类构造传递了一个Dependencies对象,在构造Dependencies时,参数slots中包含了名叫"report"的item,注意这个item的生成,是由一个ReportConnector+ReportComponent产生的。 从这里我们得出一个简单却非常重要的结论: 在fish_redux中,一个Dependent = connector + Component 。Dependent代表页面拼装中的一个单元,它可以是一个Component(通过buildComponent函数产生),也可以是一个Adapter(由buildAdapter函数产生)。这样设计的好处是,对于View拼装操作来说,Dependent对外统一了API而不需要透出更多的细节。 根据上面我们得出的结论,connector用来把一个更小的Component单元链接到一个更大的Component或Adapter上。这与我们之前的描述相符合。 connector到底是什么知道了connector的基本作用,我们来看一下它到底链接了哪些东西以及如何链接。 先来看一下ReportConnector类的定义: class ReportConnector extends ConnOp<PageState, ReportState>ReportConnector继承了ConnOp类,所有connector的操作包括+操作,都来自于ConnOp类。 set/get 既然是数据管道,就会有获取和放置 set函数的入参很好得表达了T和P的意思,即把一个P类型的subState合并到T类型的state中。 ...

May 24, 2019 · 1 min · jiezi

一个令你颤抖的flutter动画Basic-Animations

效果 实现过程详解,请看这里: http://tryenough.com/flutter-animation-6这个包含一系列的动画实例和动画控制: Swipe It 透明度从1到0的变换根据黑色区域的宽度改变贝塞尔曲线的大小Tap Here 文案从左到右出现 Easy 区域从屏幕外移动到屏幕内 Easy 区域反转动画实现过程详解,请看这里: http://tryenough.com/flutter-animation-6

May 23, 2019 · 1 min · jiezi

让前端开发者失业的技术Flutter-Web初体验

Flutter是一种新型的“客户端”技术。它的最终目标是替代包含几乎所有平台的开发:iOS,Android,Web,桌面;做到了一次编写,多处运行。掌握Flutter web可能是Web前端开发者翻盘的唯一机会。 在前些日子举办的Google IO 2019 年度开发者大会上,Flutter web作为一个很亮眼的技术受到了开发者的追捧。这是继Flutter支持Android、IOS等设备之后,又一个里程碑式的版本,后续还会支持windows、linux、Macos、chroms等其他嵌入式设备。Flutter本身是一个类似于RN、WEEX、hHybrid等多端统一跨平台解决方案,真正做到了一次编写,多处运行,它的发展超出了很多人的想象,值得前端开发者去关注,今天我们来体验一下Flutter Web。 概览先了解一下Flutter, 它是一个由谷歌开发的开源移动应用软件开发工具包,用于为Android和iOS开发应用,同时也将是Google Fuchsia下开发应用的主要工具。自从FLutter 1.5.4版本之后,支持了Web端的开发。它采用Dart语言来进行开发,与JavaScript相比,Dart在 JIT(即时编译)模式下,速度与 JavaScript基本持平。但是当Dart以 AOT模式运行时,Dart性能要高于JavaScript。 Flutter内置了UI界面,与Hybrid App、React Native这些跨平台技术不同,Flutter既没有使用WebView,也没有使用各个平台的原生控件,而是本身实现一个统一接口的渲染引擎来绘制UI,Dart直接编译成了二进制文件,这样做可以保证不同平台UI的一致性。它也可以复用Java、Kotlin、Swift或OC代码,访问Android和iOS上的原生系统功能,比如蓝牙、相机、WiFi等等。我们公司的Now直播、企鹅辅导等项目、阿里的闲鱼等商业化项目已经大量在使用。 架构 Flutter的顶层是用drat编写的框架,包含Material(Android风格UI)和Cupertino(iOS风格)的UI界面,下面是通用的Widgets(组件),之后是一些动画、绘制、渲染、手势库等。框架下面是引擎,主要用C / C ++编写,引擎包含三个核心库,Skia是Flutter的2D渲染引擎,它是Google的一个2D图形处理函数库,包含字型、坐标转换,以及点阵图,都有高效能且简洁的表现。Skia是跨平台的,并提供了非常友好的API。第二是Dart 运行时环境以及第三文本渲染布局引擎。最底层的嵌入层,它所关心的是如何将图片组合到屏幕上,渲染变成像素。这一层的功能是用来解决跨平台的。 了解了FLutter 之后,我来说一下今天的重头戏,Flutter for Web。要想知道Flutter为什么能在web上运行,得先来看看它的架构。 通过对比,可以发现,web框架层和mobile的几乎一模一样。因此只需要重新实现一下引擎和嵌入层,不用变动Flutter API就可以完全可以将UI代码从Android / IOS Flutter App移植到Web。Dart能够使用Dart2Js编译器把Dart代码编译成Js代码。大多数原生App元素能够通过DOM实现,DOM实现不了的元素可以通过Canvas来实现。 安装Flutter Web开发环境搭建,以我的windows环境为例进行讲解,其他环境类似,安装环境比较繁琐,需要耐心,有Android开发经验最好。 1、在Windows平台开发的话,官方的环境要求是Windows 7 SP1或更高版本(64位)。2、Java环境,安装Java 1.8 + 版本之上,并配置环境变量,因为android开发依赖Java环境。对于Java程序开发而言,主要会使用JDK的两个命令:javac.exe、java.exe。路径:C:Javajdk1.8.0_181bin。但是这些命令由于不属于windows自己的命令,所以要想使用,就需要进行路径配置。单击“计算机-属性-高级系统设置”,单击“环境变量”。在“系统变量”栏下单击“新建”,创建新的系统环境变量(或用户变量,等效)。 (1)新建->变量名"JAVA_HOME",变量值"C:Javajdk1.8.0_181"(即JDK的安装路径) (2)编辑->变量名"Path",在原变量值的最后面加上“;%JAVA_HOME%bin;%JAVA_HOME%jrebin” (3)新建->变量名“CLASSPATH”,变量值“.;%JAVA_HOME%lib;%JAVA_HOME%libdt.jar;%JAVA_HOME%libtools.jar” 3、Android Studio编辑器,安装Android Studio, 3.0或更高版本。我们需要用它来导入Android license和管理Android SDK以及Android虚拟机。(默认安装即可)安装完成之后设置代理,左上角的File-》setting-》搜索proxy,设置公司代理,用来加速下载Android SDK。 之后点击右上角方盒按钮(SDK Manager),用来选择安装SDK版本,最好选Android 9版本,API28,会有一个很长时间的下载过程。SDK是开发必须的代码库。默认情况下,Flutter使用的Android SDK版本是基于你的 adb (Android Debug Bridge,管理连接手机,已打包在SDK)工具版本。 如果您想让Flutter使用不同版本的Android SDK,则必须将该 ANDROID_HOME 环境变量设置为SDK安装目录。 ...

May 22, 2019 · 3 min · jiezi

flutter-学习使用自定义view并添加手势动作

原文链接http://tryenough.com/flutter-gesture-canvas 在这篇指导中,你能学习如何使用gesture,并学习如何绘制一个饼图。 当我们使用CustomPainter绘制自己的形状时,如果你不允许与用户交互,对用户来说会有点沉闷,所以我们应该知道如何使用手势来丰富我们的交互。 0. 我们要实现什么像往常一样,先展示效果。我们希望当我们在屏幕上移动手指的时候,饼图可以跟着相应的方向进行转动。 1. 定义一个CustomPainter首先,定义一个Painter: class CircleTrianglePainter extends CustomPainter { CircleTrianglePainter({this.scrollLen}); final double scrollLen;@overridevoid paint(Canvas canvas, Size size) {}@overridebool shouldRepaint(CircleTrianglePainter oldDelegate) => oldDelegate.scrollLen != scrollLen;然后,用三角弧线画圆: void _drawTriCircle(Canvas canvas, Paint paint, {Offset center, double radius, List<double> sources, List<Color> colors, double startRadian}) { assert(sources != null && sources.length > 0); assert(colors != null && colors.length > 0); var total = 0.0; for (var d in sources) { total += d; } List<double> radians = []; for (var data in sources) { radians.add(data * 2 * pi / total); } for (int i = 0; i < radians.length; i++) { paint.color = colors[i % colors.length]; canvas.drawArc(Rect.fromCircle(center: center, radius: radius), startRadian, radians[i], true, paint); startRadian += radians[i]; }}在 paint(canvas, size) 方法中绘制我们的形状: ...

May 22, 2019 · 2 min · jiezi

七分设计感的纯Flutter项目Mung三部曲

React版MungReact-Native版MungFlutter版MungMung-Flutter1. Mung-Flutter:是一个基于Flutter编写,使用豆瓣开源API开发的一个项目。 2. 功能概述启动页:添加了启动页主要是让最开始进入时不至于显示白屏。数据保存 :支持断网加载缓存数据。主题换肤 :现在只支持切换主题颜色,本项目没几张图片。查看电影详情 :支持查看电影详情包括评论。一键搜索: 支持标签和语句查找相关的电影。查看剧照: 支持缩放图片。3.1 动态演示(Android版) 3.2 运行结果图 4. 使用到的框架flutter_swiper :Banner栏图片轮播的效果。rxdart :和Rxjava、RxJs、RxSwift差不多,这里主要用它的BehaviorSubject配合Bloc模式实现状态管理。shared_preferences :简单的数据保存,比较细致的数据存储如列表等还是建议使用数据库。dio :实现网络请求,一个非常不错的三方网络包,功能非常多,如果刚入门或者项目比较急建议使用这个。flutter_spinkit : 加载时显示的加载组件,挺不错,建议看下。photo_view: 图片缩放组件,因为安卓里的photoview正好选了,使用了一个简单的功能,暂时没发现问题。5. 项目全局状态管理现在据我了解的比较成熟的状态管理有。 InheritedWidget(自带的其他三方好像都是基于它开发,只是封装了下,更加方便)scoped_model: 不错。redux和前端的redux是一个意思,但我写过demo用过,个人愚见:差远了。Bloc:(Business Logic Component)paolo soares 和 cong hui 在2018年Google dartconf上提出的,它其实是一个模式InheritedWidget+stream配合使用。本项目使用的就是Bloc。 6. 思考这个开发的第一个flutter,都有这个项目来说该用的主流框架都恰到好处的用了,因为项目太小,适合入门和快速开发。对于flutter个人感觉。 上个月看了一个消息Flutter团队好像在今年不会推出热更新功能,好像是基于安全和可实现性考虑,这里要说下flutter编译模式: 开发阶段使用的是 Kernel Snapshot 模式编译,生产模式使用AOT。flutter上月好像推出了web端和桌面的适配,这个应该对flutter发展有很大帮助。我之前一年多一直使用React-Native开发项目,感觉Flutter的组件比RN多,而且多很多,组件兼容性更好,而且更精致,但是嵌套的模式真心丑,而且巨乱,我开发时把组件拆分成多个函数这样会让界面清新一点。状态管理,暂时还没有一个绝对好的状态管理功能,现在有些项目使用bloc或者bloc+redux,但个人认为不久的将来会有一个好的状态管理功能占据绝对的地址,想RN的redux、mobx一样。组件生命周期函数很少,尤其是开发大型项目时,之前使用RN开发时就觉得RN比原生安卓生命周期少,自己还得去添加全局监听去管理生命周期,flutter就更少了。性能,应该flutter,网上一大堆对比文章一番一大把,个人使用也明显感觉到flutter性能很好,这是现实原理的问题,尤其是列表,比fRN好很多,而且动画等也多,自定义组件还没看,不做评价。社区,毫无疑问RN社区会比Flutter对于现在这个时间段来说,而且RN支持热更新对原生加(RN、Flutter)来说,RN也更站优势,三方组件来说RN已经很多了,开源项目比较多。7. 提示2019-5-12左右豆瓣把开源API关了,现在使用的别的开发者的地址,项目Baser_url是抽出来的后期可以自己改,现在项目使用的是https://douban.uieee.com/v2,可以正常运行。 8.下载地址安卓版ios版(没有企业账号-????)

May 22, 2019 · 1 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

Package-dependencies-For-Flutter

概念依赖是一个pub包管理的核心概念,它在pubspec.yaml文件中被指定。开发人员只需要把自己直接依赖的包列出来,不需要写间接依赖的包,pub会帮我们处理那些间接依赖,可以使用pub deps命令或者flutter packages pub deps命令查看包依赖信息。 用法dependencies: [依赖的包名]: [source]: 告诉pub这个包如何被定位,不是一个具体的pub siet或者是git url, 而是获取包的方式,这些方式有sdk、Hosted packages、Git packages、Path packages [description]: 让pub定位到source的额外信息 version: 包的版本号不同source的写法SDK(表明这个包来自于哪个sdk,使用这个参数要求dart版本必须在1.19.0之上)dependencies: flutter_driver: sdk: flutter version: ^0.0.1Hosted packages(从pub site上下载的包)dependencies: transmogrify: hosted: name: transmogrify url: http://your-package-server.com version: ^1.4.0Git packages(从git上获取的包)dependencies: kittens: git: url: git://github.com/munificent/kittens.git ref: some-branchPath packages(依赖的本地文件)dependencies: transmogrify: path: /Users/me/transmogrify不同的版本号约束写法semantic versioning例子:>=1.2.3、>1.2.3、<=1.2.3、<1.2.3、'>=1.2.3 <2.0.0' Caret syntax(要求dart版本号在1.8.3及以上,并且dart会把^转换成semantic versioning的形式)例子:^1.3.0 依赖类型dependencies项目中要依赖的包,例子如下: dependencies: path: ^1.3.0 collection: ^1.1.0 string_scanner: ^0.1.2dev_dependencies放一些跟项目逻辑无关的依赖包,比如和test相关的, 例子如下: dev_dependencies: test: '>=0.5.0 <0.12.0'dependency_overrides覆盖dependencies包依赖,例子如下: dependency_overrides: transmogrify: path: ../transmogrify_patch/

May 21, 2019 · 1 min · jiezi

Flutter-之点击空白处取消TextField焦点

GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { // 触摸收起键盘 FocusScope.of(context).requestFocus(FocusNode()); }, child: *******}注:一定要放到最外层

May 18, 2019 · 1 min · jiezi

Flutter切换tab后保留tab状态

BottomNavigationBar+PageView+AutomaticKeepAliveClientMixin 要点 子页面的class class _HomePageState with AutomaticKeepAliveClientMixin{//要点1 @override bool get wantKeepAlive => true;//要点2 Widget build(BuildContext context){ super.build(context);//要点3 }光上面其实还不够,还需要搭配其他Widget使用,例如BottomNavigationBar+PageView活着TabBar+TabbarView,这样页面才不会销毁。 //PageView是重点的重点 Scaffold( body:PageView.builder( physics: NeverScrollableScrollPhysics(),//禁止页面左右滑动切换 controller: _pageController, onPageChanged: _pageChanged, itemCount: _pages.length, itemBuilder: (context,index)=>_pages[index] ) bottomNavigationBar:...)class MyApp extends StatefulWidget{ @override _MyAppState createState() => _MyAppState();} class _MyAppState extends State<MyApp>{ var _pages = [HomePage(),ListViewDemo(),DemoPage(),UserListPage()]; int _tabIndex = 0; var _pageController =PageController(); @override void dispose() { super.dispose(); _pageController.dispose(); } Widget build(BuildContext context){ return MaterialApp( theme: ThemeData( primaryColor: Colors.pinkAccent ), home:Scaffold( body:PageView.builder(//要点1 physics: NeverScrollableScrollPhysics(),//禁止页面左右滑动切换 controller: _pageController, onPageChanged: _pageChanged,//回调函数 itemCount: _pages.length, itemBuilder: (context,index)=>_pages[index] ), bottomNavigationBar:BottomNavigationBar( items: <BottomNavigationBarItem>[ BottomNavigationBarItem(title:Text('主页'),icon: Icon(Icons.home)), BottomNavigationBarItem(title:Text('商城'),icon: Icon(Icons.shopping_basket)), BottomNavigationBarItem(title:Text('测试'),icon: Icon(Icons.pageview)), BottomNavigationBarItem(title:Text('我的'),icon: Icon(Icons.account_box)), ], onTap: (index){ print('onTap'); _pageController.jumpToPage(index); }, type:BottomNavigationBarType.fixed, currentIndex: _tabIndex, ), ) ); } void _pageChanged(int index){ print('_pageChanged'); setState(() { if(_tabIndex != index) _tabIndex = index; }); } } class DemoPage extends StatefulWidget { _DemoPageState createState() => _DemoPageState();} class _DemoPageState extends State<DemoPage> with AutomaticKeepAliveClientMixin{//要点1 int _count = 0; @override bool get wantKeepAlive => true;//要点2 @override Widget build(BuildContext context) { super.build(context);//要点3 return Scaffold( appBar: AppBar(title:Text('Demo Page')), body: Center(child:Text('当前计数:$_count')), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: (){ setState(() { _count++; }); }, ), ); }}来源:CSDN 原文:https://blog.csdn.net/weixin_... 版权声明:本文为博主原创文章,转载请附上博文链接! ...

May 16, 2019 · 1 min · jiezi

windowsvscode-flutterandroid-studio开发环境搭建

环境要求由于Flutter SDK和Android Studio以及后续安卓模拟器体积很大,所以建议C盘的空间至少要有10G以上的可用空间。 下载Flutter SDK进入Flutter官网下载最新版本的Flutter SDK,如果遇到网络问题,请百度解决。 下载完成后,解压得到下面的文件夹,为了避免windows环境下的路径问题,所以我把flutter文件夹解压到了C盘根目录下。将flutter sdk 放到C盘后,打开CMD,执行flutter doctor检测flutter 环境是否正常,从下图可知,我这边的flutter版本是v1.2.1的版本。由于我这边Android Studio、安卓模拟器和VSCode的环境已经配置好了,所以都是显示绿色的对勾。创建一个新的flutter项目 配置VSCode环境Vscode是微软出品的轻量级编辑器,前端必备。使用vscode开发flutter需要安装专门的Flutter插件,安装方式如下图:打开扩展-->搜索flutter-->点击绿色安装按钮(该插件会将flutter和dart所需环境配置好) 安装Android Studio进入官网下载最新版的Android Studio,由于我的电脑系统是window 10 ,所以显示的下载版本是windows的版本。点击file-->setting-->plugin,安装Android studio 中的flutter和Dart扩展安装完成后打开创建的flutter项目,Android studio 会自动加载相关文件启动安卓模拟器

May 15, 2019 · 1 min · jiezi

Flutter-开发环境搭建-For-Mac

谷歌发布 Flutter for web,正式宣布 Flutter 成为全平台框架,支持手机、Web、桌面电脑和嵌入式设备。现在学跨平台应用开发,第一个要看的可能不是 React Native,而是 Flutter,来自阮一峰的评价。 学习一门开发框架,首先从配置框架的开发环境开始,这篇文章记录了自己在 Mac 电脑上如何搭建 Flutter 开发环境,参考中文文档,也是有坑要踩的。 系统要求 Flutter 支持在 Window、MacOS、Linux等操作系统环境下开发 。 安装 Flutter 系统环境变量配置Google 提供的服务,国内无法访问的或速度慢的要死,所以下载资源需要翻墙或者官方提供的镜像,Flutter 官方为中国开发者搭建了临时镜像 for china。 在 .bash_profile 文件中添加镜像地址 : export PUB_HOSTED_URL=https://pub.flutter-io.cnexport FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn终端命令: 1、vim ~/.bash_profile2、将上面的镜像地址添加到该文件中,保存退出。3、source ~/.bash_profile 添加的地址生效下载 Flutter SDK Flutter 官网下载地址分为三个不同的版本: Stable channel (稳定版)Dev channel (开发版)最新的完全测试过的版本。也包含了新功能,但是也会有一些 " bad " dev builds,可以查看 Bad Builds 列表。 Beta channel (测试版)每隔几周都会选取近几个月中最好的一个 dev 版本,当作 beta 版,这个版本是通过了Google的 codelabs 测试的。 建议大家选择稳定版进行开发,不过官网下载确实很慢,不过可以参考 Using Flutter in China 方法替换 host ,亲测有效。 ...

May 14, 2019 · 2 min · jiezi

一行命令将Json文件转成Dart-类

Flutter官方提供的Json转Dart类的方案需要先手动写一个Dart model类,然后通过build_runner和json_serializable包提供的相关命令和标注然后再自动生成toJson()和fromJson方法,这种方案最大问题在于需要开发者手动写一个Model类。在一个项目中,我们需要的Model类可能非常多,如果都需要手动反复去做的话会很麻烦、无味。为了解决这个问题,我们做了一个新的开发工具package Json_model, 使用它,可以只用一行命令,就能将Json文件转成Dart 类,我们再也不用手动去写Dart类。 安装请参考Github文档。 使用在工程根目录下创建一个名为 "jsons" 的目录;创建或拷贝Json文件到"jsons" 目录中 ;运行 pub run json_model (Dart VM工程)or flutter packages pub run json_model(Flutter中) 命令生成Dart model类,生成的文件默认在"lib/models"目录下例子Json文件: jsons/user.json { "name":"wendux", "father":"$user", //可以通过"$"符号引用其它model类 "friends":"$[]user", // 可以通过"$[]"来引用数组 "keywords":"$[]String", // 同上 "age":20}生成的Dart model类: import 'package:json_annotation/json_annotation.dart';part 'user.g.dart';@JsonSerializable()class User { User(); String name; User father; List<User> friends; List<String> keywords; num age; factory User.fromJson(Map<String,dynamic> json) => _$UserFromJson(json); Map<String, dynamic> toJson() => _$UserToJson(this);}@JsonKey您也可以使用json_annotation包中的“@JsonKey”标注特定的字段。 这个功能在特定场景下非常有用,比如Json文件中有一个字段名为"+1",由于在转成Dart类后,字段名会被当做变量名,但是在Dart中变量名不能包含“+”,我们可以通过“@JsonKey”来映射变量名; { "@JsonKey(ignore: true) dynamic":"md", "@JsonKey(name: '+1') int": "loved", //将“+1”映射为“loved” "name":"wendux", "age":20}生成文件如下: ...

May 14, 2019 · 1 min · jiezi

从-React-Native-到-Flutter移动跨平台方案的真相

作者:LeanCloud 郑鹏 2018 年 12 月,Google 发布了 Flutter 1.0 正式版,似乎再次点燃了人们对移动跨平台开发的热情。上一次出现类似的情况,是在 15 年年初,Facebook 发布 React Native 的时候。四年不到的时间里,有两家大公司相继推出了自己的移动跨平台方案(当然还有 16 年的时候,微软收购了 Xamarin,不过没有前两个那么引人注目罢了),同时这些方案也受到了市场的追逐。这些现象,似乎预示着,跨平台开发才是移动开发的未来,或者说,跨平台开发才是一种更好的开发方式。 既然它是热点,那肯定有可以讨论的地方。不过,在说 React Native 和 Flutter 之前,我觉得要先谈一谈「跨平台开发」。 移动跨平台方案那什么是「跨平台开发」呢? 通常意义上来说,如果你想在 iOS 以及 Android 系统里,提供有相同内容的 App,那么使用 Apple 提供的构建工具,开发一个 App,然后上架到 AppStore,同时使用 Google 提供的构建工具,开发一个 App,然后上架到 Google Play。这两个 App 的实现,除了使用的工具不同之外,大部分业务逻辑是相同的。你可以发现,在这个过程中,产生了「重复」。 在重构时,如果项目里有大量的重复代码,或者重复逻辑,我们一般会将这些代码或逻辑以函数,模块或库的形式做封装,这个过程最大化的消除了重复的代码,最终达到简化项目的代码这一目的。 所以在我看来,「跨平台开发」也是基于这个思想而产生的,人们想要一套减少甚至不用写重复逻辑的解决方案,然后市场给予了人们期望的方案。跨平台方案的最大特点,可以用 Sun 当年在推广 Java 时,所使用的一句口号:”Write once, run anywhere” 作为总结。这一句话,也被如今的 React Native 以及 Flutter 引用或继承。 React NativeReact Native 是由 Facebook 所主导的跨平台方案,得益于 Javascript 以及 ReactJS 的流行,React Native 在推出时,便受到了大量的追捧。除了跨平台的特性,React Native 最大的特点就是,可以使用 Javascript 来构建移动应用,并且最终应用的表现形式,可以做到和使用原生开发套件开发的应用相差无几。 ...

May 13, 2019 · 2 min · jiezi

Android-Studio启动虚拟器报错VTx-is-disabled-in-BIOS惠普电脑解决方案

在Android Studio中创建好一个模拟器后,启动时报如下错误:“Intel HAXM is required to run this AVD,VT-x is disabled in BIOS”。 检查Android Studio中是否装好了HAXM installer首先检查一下Android Studio中是否装好了HAXM installer,如果没有安装,则到SDK Manager中下载安装Download Intel x86 Emulator Accelerator (HAXM installer),然后到sdk的目录下沿着 extras > intel > Hardware_Accelerated_Execution_Manager 目录找到intelhaxm-android.exe这个文件安装定并运行。 修改BIOS权限如果上一步没有解决问题,就需到BIOS界面里修改VT-x设置了。对于惠普电脑,进入BIOS的方法是:1、关机2、开机,在屏幕刚刚亮,显示HP的时候点击esc键盘,然后按F10进入设置界面(这一步需要在屏幕亮的第一时间点击esc键,否则不成功)3、按照以下步骤设置VT-x权限

May 12, 2019 · 1 min · jiezi

大侦探福老师幽灵Crash谜踪案

闲鱼Flutter技术的基础设施已基本趋于稳定,就在我们准备松口气的时候,一个Crash却异军突起冲击着我们的稳定性防线!闲鱼技术火速成立侦探小组执行嫌犯侦查行动,经理重重磨难终于在一个隐蔽的角落将其绳之以法! 幽灵Crash问题要从闲鱼Flutter基础设施上一次大规模升级说起。2018年我们对闲鱼的Flutter基建作了比较大的重构,目标在于提高基建的稳定性和可扩展性。这个过程当然是挑战重重,在上一次大规模的重构集成发版后,我们虽然没有发现非常明显的异常问题,但是Crash率却出现了一个比较明显的增长。虽然总体数值还在可控范围之内,但这一个Crash却占据了几乎一大半。这个问题引起了我们警觉,我们立刻成立专项小组重点进行排查。 一般Crash Log能够为我们定位Crash提供主要信息,我们一起看看这个Crash的Log: Thread 0 Crashed:0 libobjc.A.dylib 0x00000001c1b42b00 objc_object::release() :16 (in libobjc.A.dylib)1 libobjc.A.dylib 0x00000001c1b4338c (anonymous namespace)::AutoreleasePoolPage::pop(void*) :676 (in libobjc.A.dylib)2 CoreFoundation 0x00000001c28e8804 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ :28 (in CoreFoundation)3 CoreFoundation 0x00000001c28e8534 __CFRunLoopDoTimer :864 (in CoreFoundation)4 CoreFoundation 0x00000001c28e7d68 __CFRunLoopDoTimers :248 (in CoreFoundation)5 CoreFoundation 0x00000001c28e2c44 __CFRunLoopRun :1880 (in CoreFoundation)6 CoreFoundation 0x00000001c28e21cc _CFRunLoopRunSpecific :436 (in CoreFoundation)7 GraphicsServices 0x00000001c4b59584 _GSEventRunModal :100 (in GraphicsServices)8 UIKitCore 0x00000001efb59054 _UIApplicationMain :212 (in UIKitCore)9 Runner 0x0000000102df4eb4 main main.m:49 (in Runner)10 libdyld.dylib 0x00000001c23a2bb4 _start :4 (in libdyld.dylib)这是一个很典型的野指针Crash Log,是其中一种俗称的Over released问题。但是具体是哪个对象和方法,很难直接从Log上面得知,况且ARC下面的野指针更令人费解。 ...

May 10, 2019 · 2 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-完整项目mvvm架构

Github 安卓请扫码下载体验,ios没有证书,无法下载。 项目结构 该项目的特点1、使用mvvm架构编写。 MVVM架构在Flutter中的简单实践2、Provide和RxDart 的使用,详细请参考 Flutter | 状态管理特别篇 —— Provide部分封装介绍1、refresh组件:刷新组件是在pull_to_refresh的基础上进行的再次封装,该库本身是存在一些问题的,所以就自己改了一下使用。希望该库持续更新,还有其他的刷新库,这里就不详细说了。主要是我们在使用时最好能够读懂别人的组件库的代码,这样才能更好的解决问题。也是一种学习方式。pull__to__refresh2、OpacityTapWidget组件:OpacityTapWidget组件解决了2个问题:1)点击效果:点击时child有一个透明度的变化 2)点击的热区问题: OpacityTapWidget内部设置padding增加了点击的热区。 new OpacityTapWidget( onTap: () { Navigator.of(context).pop(); }, child: new Icon(Icons.close, color: Colors.white,size: 27,),)3、TapWidget组件:和OpacityTapWidget不一样的是TapWidget点击的效果是背景颜色的变化。

May 7, 2019 · 1 min · jiezi