2019年5月

同页面使用多个floatingActionButton会报错
flutter: The following assertion was thrown during a scheduler callback:
flutter: There are multiple heroes that share the same tag within a subtree.
flutter: Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must
flutter: have a unique non-null tag.
flutter: In this case, multiple heroes had the following tag: <default FloatingActionButton tag>
flutter: Here is the subtree for one of the offending heroes:

heroTag的问题
修改成heroTag: null
关于heroTag解释:
/// The tag to apply to the button's [Hero] widget.
///
/// Defaults to a tag that matches other floating action buttons.
///
/// Set this to null explicitly if you don't want the floating action button to
/// have a hero tag.
///
/// If this is not explicitly set, then there can only be one
/// [FloatingActionButton] per route (that is, per screen), since otherwise
/// there would be a tag conflict (multiple heroes on one route can't have the
/// same tag). The material design specification recommends only using one
/// floating action button per screen.

[!] CocoaPods could not find compatible versions for pod "multi_image_picker":
In Podfile:

multi_image_picker (from `.symlinks/plugins/multi_image_picker/ios`)

Specs satisfying the multi_image_picker (from .symlinks/plugins/multi_image_picker/ios) dependency were found, but they required a higher minimum deployment target.

pod 里的platform :ios, '8.0' 版本低了,改成9.0 即可

节流与防抖
函数节流是指一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。
而函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。
Flutter的节流
函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。
放到业务中分析节流函数:

class MyStatefulWidgetState extends State<OrderPageEdit> {

bool canScanning; //是否可以扫描
//扫描控制流
final Stream<dynamic> _barScanner =
EventChannel('com.freshport.freshport/barcode').receiveBroadcastStream();
StreamSubscription<dynamic> _barScannerSubscription;

@override
void initState() {
super.initState();
_barScannerSubscription = _barScanner.listen((data) {
if (!canScanning) return;
setState(() {
canScanning = false;
});
scanning(data);
});
}

@override
void dispose() {
_barScannerSubscription.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Widget;
}

//扫面获取
scanning(goodsCode) async {
final result = await fetch.fetch(url: 'www.nicai.com');
setState(() {
canScanning = true;
});
if (result.result) {
} else {}
}
}

解释一下这段代码,因为这个项目是有扫描条形码进行货物移库的操作,我们的期望是扫描一次,从数据库中读取完成增加到列表中一个货物。因此,在此之前即使扫描也无法读取。因此我在_barScanner的监听函数中增加一个flag标志位的判断,这个标志位用于判断是否在读取中,读取完成后将flag置成true。此时就可以继续扫描。
当然,我这个节流函数并未像有些截留函数那样带有明显的不可触发时间,这个函数的不可触发时间为加载的时间。

Flutter的防抖
防抖函数的定义为多次触发事件后,事件处理函数只执行一次,并且是在触发操作结束时执行。其原理是对处理函数进行延时操作,若设定的延时到来之前,再次触发事件,则清除上一次的延时操作定时器,重新定时。
防抖函数多用于处理实时搜索,拖拽,登录用户名密码格式验证。在js的环境中,我们一般使用定时函数setTimeout进行防抖处理。同样的原理,在Flutter中,我们会原则定时函数(或者叫延时函数进行处理)。
在一个输入框对应的时时搜索中,我使用了防抖处理:

class MyStatefulWidgetState extends State<GoodsCodeList> {

//检索输入
final TextEditingController _textController = TextEditingController();
//设置防抖周期为3s
Duration durationTime = Duration(seconds: 3);
Timer timer;

@override
void initState() {
super.initState();
}

@override
void dispose() {
_textController.clear();
timer?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return TextField(
controller: _textController,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(5.0),
hintText: "请输入商品编码",
prefixIcon: Icon(Icons.search, color: Colors.black),
focusedBorder: OutlineInputBorder(

borderSide: BorderSide(color: Colors.black),

),
border:

OutlineInputBorder(borderRadius: BorderRadius.circular(3.0))),

onChanged: (String text) {
//限制加节流
if (text.trim().length < 5) return;
setState(() {
timer?.cancel();
timer = new Timer(durationTime, () {

//搜索函数

});
});
});
}
}

如代码所示,先设置一个 Timer 对象,当输入框TextField持续输入时,会一直触发 Timer对象的cancel事件,既取消,然后会重新给Timer赋值新的周期为3s的定时函数。当3s中内不输入信息时,这个定时函数会触发。但是三秒钟内再次输入,这个定时函数又会被取消然后赋值新的周期为3s的定时函数。
这就是防抖函数的实际应用。
收尾
我们在js的代码中会经常接触到函数节流与防抖,是因为在js中,DOM操作(onresize, onscroll等等操作)是最消耗性能的,但是一些场景中同一事件会多次触发,为了减少操作,从而有了防抖和节流的概念。其实在很多开发中,我们还是可以使用防抖和节流减少不必要的一些操作和ajax请求的。

简介
环境是Mac下Android Studio进行的开发,AS也是谷歌官推的,安装flutter插件后,开发起来相对于其他IDE来说,方便很多,自带了三种模板:

Flutter Application: Flutter应用
Flutter Plugin:Flutter插件
Flutter Package:纯Dart组件

Plugin其实就是一个特殊的Package。Flutter Plugin提供Android或者iOS的底层封装,在Flutter层提供组件功能,使Flutter可以较方便的调取Native的模块。很多平台相关性或者对于Flutter实现起来比较复杂的部分,都可以封装成Plugin。其原理如下
177500-71c6550f14232a51.png
消息在client和host之间通过平台通道(platform channels)来进行的,之间的通讯都是异步的。
创建组件
直接在Android Studio中新建一个Flutter Plugin的工程,当然也可以使用命令行来进行,例如创建一个flutter_text_plugin。

flutter create --org com.example --plugin flutter_text_plugin

如果想支持swift或者kotlin,可以用如下命令进行创建:

flutter create --org com.example --plugin -i swift -a kotlin flutter_text_plugin

更多的参数选项,大家可以 查看帮助文档,当然还是比较推荐直接用AS进行创建,简单直观。用AS打开项目,可以看到项目的组织结构
root

android
example
ios
lib
...

android以及ios文件夹是我们将要编写插件的native层的地方,lib文件夹是编写与native层映射的地方,native与flutter之间不能直接通信,必须通过MethodChannel来间接调用。example文件夹则是例子工程,编写的插件可以直接在这个项目中进行验证。在本文中,我们主要在android目录下进行,也就是android部分。
编写Android部分
用AS打开flutter_text_plugin/android项目,这样子开发起来比较方便。但是打开过后,会发现出现了很多错误,提示找不到flutter相关的东西,我们仔细看这个项目,会发现跟我们平时用AS建的Android项目有所不同,少了很多部分,目录也有所不同。这是因为这个android项目不需要能够直接去运行,因此减少了很多东西。但是对于初次接触的人来说,可能是一头懵逼,例如该如何添加第三方库,如何添加proguard rule等等。
引入flutter库
android插件工程是没有引入flutter库的,所以才会出现错误提示,我们在项目根目录建立一个libs文件夹,用来存放flutter库。
flutter库就在我们的flutter sdk中,路径如下
<SDK Project>/bin/cache/artifacts/engine
engine下面包含了各种平台的flutter库,我们随便拷贝一个Android平台的库到libs文件夹下,右键flutter.jar,弹出菜单选择Add As Library...。
经过这一步,项目中不会再报错了,但是,由于整个flutter plugin包含了flutter库,因此不能只是简单的添加就了事了,点击菜单Project Structure...,找到flutter_text_plugin的Dependencies中,将flutter库的Scope从Implementation改成Compile Only。至此,引入flutter库的工作完成了,可以进行插件的编写操作了。
添加第三方库

添加第三方库有两种,一种是jar包引入,另一种通过gradle的方式进行。由于进行了第一步flutter库的引入,这一步就简单多了。查看build.gradle文件,可以看到最下面出现了如下的信息。
dependencies {
compileOnly files('libs/flutter.jar')
}
看到这个,是不是就明朗多了,添加静态库以及添加在线库都可以在这个地方进行。例如我添加一个bugly静态库以及okhttp3库:
dependencies {
compileOnly files('libs/flutter.jar')
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation files('libs/bugly_crash_release.jar')
}
添加proguard rule
由于了bugly以及okhttp3库,因此需要添加progurad rule。我们发现项目中没有proguard-rules.pro文件,因此这一步也需要我们自己去创建,在根目录下,建立proguard-rules.pro文件,将混淆规则添加进去,然后修改build.gradle文件,添加如下信息,跟普通Android项目差不多:
buildTypes {

release {
  minifyEnabled true
  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
  minifyEnabled false
  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

}
Android权限

添加了bugly以及okhttp3库,需要对应的权限申明,才能正常运行。直接在manifest文件下,添加对应的权限
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS"/>
插件开发
至此,准备工作都已就绪,你可以把这个项目当做一个独立的Android项目,在上面进行各种封装操作,然后在FlutterTestPlugin文件下,将接口暴露出来。通过platform channels与flutter层关联起来。
发布
当插件开发完毕,可以将插件发布让其他人使用,在发布之前,确保pubspec.yaml,、README.md以及CHANGELOG.md文件的内容都正确填写完毕。可以通过dry-run命令来看准备是否就绪。
flutter packages pub publish --dry-run
检查无误后,可以执行下面的命令,发布到Pub上。
flutter packages pub publish
如何引用

对插件的引用有两种,已经发布的和未发布的。

引用发布的库

flutter项目的很多资源管理都在根目录的pubspec.yaml下面,类似于js中的一些包管理一样,在dependencies加上我们需要引入的库,例如引入url_launcher库:
dependencies:
url_launcher: ^0.4.2
如果这个库包含了一些平台相关的东西,例如需要在native层进行使用的话,则需要在对应的native项目单独做引用。

Android

修改android/build.gradle的dependencies处做引用:
dependencies {

    provided rootProject.findProject(":url_launcher")
}

iOS

修改ios/hello.podspec文件
Pod::Spec.new do |s|
# lines skipped
s.dependency 'url_launcher'
引用冲突
引用不同的库可能会导致一些冲突,例如A和B两个插件,都包含了C插件,但是所需的版本不同。因此我们可以采取以下措施避免这种问题:

尽量使用范围版本而不是指定一个特定的版本。
强制统一冲突的插件版本
对于native层,android可以通过force命令强制指定版本,而iOS这边,Cocoapods则不支持引用的override功能。

引用未发布的库
引用未发布的库有两种方式,通过本地路径和git地址的方式:
基于Path的引用方式:
这种方式主要针对本地的未发布的库,引用的路径可以是相对或者绝对路径。
dependencies:
plugin1:

path: ../plugin1/

基于Git的引用方式:

这种方式针对存放在git上的库,其中path是可选的,可以定位到某个子目录
dependencies:
package1:

git:
  url: git://github.com/flutter/packages.git
  path: packages/package1