Skip to content

Gesture conflict resolution for iOS and flutter.iOS和flutter手势冲突问题解决.

License

Notifications You must be signed in to change notification settings

TonyReet/iOS-flutter-gesture-conflict

Repository files navigation

项目介绍

iOS和flutter的手势冲突

文件目录

flutter_iOS_gesture_demo:iOS项目目录 flutter_ios_gesture_demo_module:flutter项目目录

在iOS项目执行pod install 即可运行混合工程,查看手势问题

具体问题

iOS和flutter都有手势处理,但是在某些情况下可能产生手势冲突,比如iOS有一个抽屉手势,而flutter有一个水平的滑动手势,这个时候就会产生冲突的问题,具体问题看下面情况。

  • iOS抽屉手势 原生抽屉

1、需求场景

绿色部分为放置flutter的控制器(ContentViewController),当在屏幕左侧滑动的时候,会划出iOS的抽屉控制器(LeftTableViewController),并且此抽屉手势也是iOS控制。 iOS抽屉手势的代码网上很多,此处是从项目里面抽出来的,就不再赘述,代码地址

2、flutter页面

假设我们flutter页面是有横向滑动的view,需要集成到iOS里面去,如下图: flutter页面

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的抽屉手势呢?

3、集成效果

我们将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];

看一下效果: 首次集成flutter.gif

我们可以看到,在listView上面从左向右滑动时,大概率会触发iOS的抽屉手势,这和我们的需求不符。 相信大家都知道原因了,这就是iOS的手势优先级高于flutter手势的优先级,所以会触发iOS的抽屉手势。

4、解决手势问题

知道原因,解决起来也方便了。这里提供一个方案,当我们的手势在flutter的页面上操作时,由flutter自行判断是否需要触发抽屉的动作,那么在flutter端处理的思路就清晰了。当我们在listView上滑动时候,不需要iOS参与,当我们在flutter其他区域存在手势时,调用iOS原生的触发方法。

流程: 流程图.png

我们判断手势是否在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);
            });
        }
    }
}

很开心的开始看效果,结果还是有问题,仔细看图,当触发抽屉滑动的时候,边缘有明显的抖动。 flutter抖动.gif

5、抖动处理

查看原因发现,当我们将滑动的消息发送到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);
            });
        }
    }

效果: 最后效果.gif

可以看到,当在ListView上面滑动的时候,listView左右滑动正常,并且没有误触iOS时候,当在flutter下方的非listView区域滑动时,能够触发iOS的抽屉手势,并且没有抖动。

文章地址:https://www.jianshu.com/p/47729e23b3f3

flutter和native混合开发的项目,很多需要共用一套文件,以减小包大小,比如共用图片,字体资源等。图片资源的共用方案很多,但是flutter和native共用字体方案资料比较少。

iOS实现共用字体

共用资源的方案的话,主要从两方面解决。

1、资源在native,通过某种方式将资源从native传给flutter使用。

这种方案一直没有想到处理的办法,如果知道怎么处理的同学请告知下,谢谢。

2、资源在flutter,通过某种方式将资源从flutter传给native使用。

iOS加载字体有2种方式 a:在工程里面添加字体文件 xxx.otf,yyy.otf,然后在Info.plist添加字段"Fonts provided by application",并且添加对应的字体,如图: 123.png

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实现共用字体

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]

About

Gesture conflict resolution for iOS and flutter.iOS和flutter手势冲突问题解决.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published