- 记录flutter和native(主要是iOS)交互相关的问题
- iOS和flutter的手势冲突
- iOS实现共用字体
flutter_iOS_gesture_demo:iOS项目目录 flutter_ios_gesture_demo_module:flutter项目目录
在iOS项目执行pod install 即可运行混合工程,查看手势问题
iOS和flutter都有手势处理,但是在某些情况下可能产生手势冲突,比如iOS有一个抽屉手势,而flutter有一个水平的滑动手势,这个时候就会产生冲突的问题,具体问题看下面情况。
绿色部分为放置flutter的控制器(ContentViewController),当在屏幕左侧滑动的时候,会划出iOS的抽屉控制器(LeftTableViewController),并且此抽屉手势也是iOS控制。 iOS抽屉手势的代码网上很多,此处是从项目里面抽出来的,就不再赘述,代码地址
假设我们flutter页面是有横向滑动的view,需要集成到iOS里面去,如下图:
flutter页面主要代码:
Column(children: <Widget>[
Container(
child: ListView.separated(
itemCount: 10,
shrinkWrap: true,
scrollDirection: Axis.horizontal,
separatorBuilder: (BuildContext context, int index) {
return Container(
width: 5,
);
},
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
child: Container(
margin: EdgeInsets.all(5),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('测试标题:${index}',
style: TextStyle(fontSize: 19, color: Colors.white),
maxLines: 1,
overflow: TextOverflow.ellipsis),
Container(
width: 22,
height: 2,
color: Colors.white,
),
Text(
'测试内容:${index}',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 13, color: Colors.white),
)
],
),
width: 180,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: RandomColor().rColor),
));
},
),
height: 180),
Expanded(
child:Container()
)
]
- 最上方是一个横向滑动的listView,我们先明确一个需求,当我们在listView上面滑动的时候,只触发listView的左右滑动效果,当我们不在listView上面滑动的时候,才触发iOS的抽屉手势。
-那么将flutter集成到iOS以后,当我横向滑动listView的时候,是触发listView滑动,还是会触发iOS的抽屉手势呢?
我们将flutter页面集成到iOS项目中,集成方法网上有很多,这里使用google官方方案
在ContentViewController添加代码:
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
self.flutterViewController = [[FlutterViewController alloc] initWithEngine:appDelegate.flutterEngine nibName:nil bundle:nil];
self.flutterViewController.view.frame = self.view.bounds;
[self.view addSubview:self.flutterViewController.view];
我们可以看到,在listView上面从左向右滑动时,大概率会触发iOS的抽屉手势,这和我们的需求不符。 相信大家都知道原因了,这就是iOS的手势优先级高于flutter手势的优先级,所以会触发iOS的抽屉手势。
知道原因,解决起来也方便了。这里提供一个方案,当我们的手势在flutter的页面上操作时,由flutter自行判断是否需要触发抽屉的动作,那么在flutter端处理的思路就清晰了。当我们在listView上滑动时候,不需要iOS参与,当我们在flutter其他区域存在手势时,调用iOS原生的触发方法。
我们判断手势是否在flutter页面上:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
// 若为FlutterView(即点击了flutter),则不截获Touch事件
if ([NSStringFromClass([touch.view class]) isEqualToString:@"FlutterView"]) {
// 当手势在flutter上,由flutter处理
NSLog(@"flutterView");
return NO;
}
NSLog(@"native View");
return YES;
}
iOS和flutter交互使用channel,代码如下:
iOS
FlutterMethodChannel *scrollMethodChannel = [FlutterMethodChannel methodChannelWithName:@"scrollMethodChannel" binaryMessenger:self.flutterViewController];
self.scrollMethodChannel = scrollMethodChannel;
__weak typeof(self) weakSelf = self;
[self.scrollMethodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
[weakSelf flutterInvokeNativeMethod:call result:result];
}];
flutter
static const MethodChannel _scrollMethodChannel =
MethodChannel('scrollMethodChannel');
static String _scrollBeganKey = 'scrollBeganKey';
static String _scrollUpdateKey = 'scrollUpdateKey';
static String _scrollEndKey = 'scrollEndKey';
flutter端,只需要把非listView的手势通知iOS就行,listView手势不需要处理。那么,只需要处理Expanded里面的 Container
即可。
Column(children: <Widget>[
Container(
child: ListView.separated(...),// 将listView代码省略,主要提现container
height: 180),
Expanded(
child: GestureDetector(
onHorizontalDragStart: (detail) {
Map<String, dynamic> resInfo = {
"offsetX": detail.globalPosition.dx,
"velocityX": detail.globalPosition.dx
};
_scrollMethodChannel.invokeMethod(_scrollBeganKey, resInfo);
},
onHorizontalDragEnd: (detail) {
Map<String, dynamic> resInfo = {
"offsetX": 0,
"velocityX": detail.primaryVelocity
};
_scrollMethodChannel.invokeMethod(_scrollEndKey, resInfo);
},
onHorizontalDragUpdate: (detail) {
Map<String, dynamic> resInfo = {
"offsetX": detail.globalPosition.dx,
"velocityX": detail.primaryDelta
};
_scrollMethodChannel.invokeMethod(_scrollUpdateKey, resInfo);
},
child: Container(color: Colors.yellow),
))
]
看代码其实比较简单,使用GestureDetector 的onHorizontalDragxxx
方法监听开始滑动,滑动ing,和滑动结束的动作,并且将嘴硬的坐标和滑动的速度信息等传递给iOS,iOS拿到数据后,进行view的移动处理即可。
iOS拿到数据后的处理方式:
// 定义的block:
@property (nonatomic, copy) void(^scrollGestureBlock)(CGFloat offsetX,CGFloat velocityX,TYSideState state);
- (void)flutterInvokeNativeMethod:(FlutterMethodCall * _Nonnull )call result:(FlutterResult _Nonnull )result{
if (!call.arguments)return;
NSLog(@"测试%@",call.arguments);
CGFloat offsetX = [call.arguments[@"offsetX"] floatValue];
CGFloat velocityX = [call.arguments[@"velocityX"] floatValue];
/// 开始滑动
if ([call.method isEqualToString:@"scrollBeganKey"]){
if (self.scrollGestureBlock){
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollGestureBlock(0, velocityX, TYSideStateBegan);
});
}
}
/// 滑动更新
if ([call.method isEqualToString:@"scrollUpdateKey"]){
if (self.scrollGestureBlock){
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollGestureBlock(offsetX, velocityX, TYSideStateUpdate);
});
}
}
/// 结束滑动
if ([call.method isEqualToString:@"scrollEndKey"]){
if (self.scrollGestureBlock){
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollGestureBlock(0, velocityX, TYSideStateEnded);
});
}
}
}
很开心的开始看效果,结果还是有问题,仔细看图,当触发抽屉滑动的时候,边缘有明显的抖动。
查看原因发现,当我们将滑动的消息发送到iOS以后,iOS会修改flutterView的x坐标,比如从0修改到10。但是flutter的手势此时一直没有中断,并且时从0开始计算偏移量,但是iOS修改x坐标以后,偏移量就会有10的误差,这个时候,就想到当iOS修改完x后,将x保存起来。在下次flutter消息到来的时候,加上此偏移量即可。
保存x偏移:
if ([self.rootViewController isKindOfClass:[ContentViewController class]]){
ContentViewController *vc = (ContentViewController *)self.rootViewController;
vc.currentViewOffsetX = xoffset;
}
使用x偏移:
/// 滑动更新
if ([call.method isEqualToString:@"scrollUpdateKey"]){
if (self.scrollGestureBlock){
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollGestureBlock(offsetX + self.currentViewOffsetX, velocityX, TYSideStateUpdate);
});
}
}
/// 结束滑动
if ([call.method isEqualToString:@"scrollEndKey"]){
if (self.scrollGestureBlock){
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollGestureBlock(self.currentViewOffsetX, velocityX, TYSideStateEnded);
});
}
}
可以看到,当在ListView上面滑动的时候,listView左右滑动正常,并且没有误触iOS时候,当在flutter下方的非listView区域滑动时,能够触发iOS的抽屉手势,并且没有抖动。
文章地址:https://www.jianshu.com/p/47729e23b3f3
flutter和native混合开发的项目,很多需要共用一套文件,以减小包大小,比如共用图片,字体资源等。图片资源的共用方案很多,但是flutter和native共用字体方案资料比较少。
共用资源的方案的话,主要从两方面解决。
这种方案一直没有想到处理的办法,如果知道怎么处理的同学请告知下,谢谢。
iOS加载字体有2种方式 a:在工程里面添加字体文件 xxx.otf,yyy.otf,然后在Info.plist添加字段"Fonts provided by application",并且添加对应的字体,如图:
b:可以通过动态注册字体的方法加载字体
具体实现:
+ (void) loadCustomFont:(NSString*)fontFileName{
NSString *fontPath = [[NSBundle mainBundle] pathForResource:fontFileName ofType:nil];
if (!fontPath) {
NSLog(@"Failed to load font: no fontPath %@", fontFileName);
return;
}
NSData *inData = [NSData dataWithContentsOfFile:fontPath];
CFErrorRef error;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
NSLog(@"Failed to load font: %@", errorDescription);
CFRelease(errorDescription);
}
CFRelease(font);
CFRelease(provider);
}
如代码所示,只需要确定font文件的路径即可,问题是怎么获取font文件的路径呢? 经过查找,方法也简单,只需要使用lookupKeyForAsset即可获取到font的文件地址
[FlutterDartProject lookupKeyForAsset:@"xxxx"];
lookupKeyForAsset的参数是pubspec.yaml配置的font文件地址,加入我们配置的地址是"fonts/iconfont.ttf"
fonts:
- family: iconfont
fonts:
- asset: fonts/iconfont.ttf
那么lookupKeyForAsset就是
[FlutterDartProject lookupKeyForAsset:@"fonts/iconfont.ttf"];
android比较简单
val assetManager = FlutterMain.getLookupKeyForAsset()
val fontKey = flutterView.getLookupKeyForAsset("fonts/iconfont.ttf")
val myTypeface = Typeface.createFromAsset(assetManager, fontKey)
如果iOS加载字体的时候报错:
Could not register the CGFont '<CGFont (0xxxxxxxxx): YYYYYYY>
可以使用FontCreator(Win)或者FontForge(Mac)修改字体信息[/md]