完美解决iOS上与flutter页面手势冲突问题
UIScrollview 的滑动手势在此场景下优先级最高,这个场景的scrollview的滑动手势是在左右滑动下触发,正常上下滑动不会触发UIScrollview的手势,这个时候flutterview是可以滑动的,但是手指稍微有一点左右偏移,就会触发原生手势,触发原生手势之后,touch事件就不会触发,flutterview获得不了touch事件就无法识别手势,就无法滑动了。判断flutter是否
场景
横滑tab上嵌套flutter页面,iOS 的UIScrollview内放Flutter页面,scrollview可左右滑动,scrollview的手势优先级最高,滑动flutter页面的时候会触发iOS原生手势,flutter页面很难滑动
手势原理
iOS的所有手势优先级高于touch事件,滑动手势的优先级高于touch事件
flutter的滑动手势是由 iOS的touch事件传递到flutterview上,flutter对touch事件进行处理得到的flutter的手势动作
滑动冲突原因
UIScrollview 的滑动手势在此场景下优先级最高,这个场景的scrollview的滑动手势是在左右滑动下触发,正常上下滑动不会触发UIScrollview的手势,这个时候flutterview是可以滑动的,但是手指稍微有一点左右偏移,就会触发原生手势,触发原生手势之后,touch事件就不会触发,flutterview获得不了touch事件就无法识别手势,就无法滑动了
解决
iOS原生手势的优先级最高,flutter的手势是touch事件,所以原生手势比flutter天生高一个级别,要解决这个问题必须把flutter和原生提到一个级别,进行手势冲突竞争处理,我们在原生中在flutterview上加一个原生手势,这个手势是flutter在原生中竞争的代理手势,用这个代理手势和原生手势竞争,这个代理手势的优先级最高,用户触摸屏幕时先让这个手势响应,把touch事件传递到flutter响应手势,如果flutter页面可以响应手势,那这个原生的代理手势就竞争成功了,如果flutter不能响应手势就告诉原生代理手势响应失败了,让iOS的Scrollview的手势响应,
此方案的flutter端代码用在安卓上相同的原理也可以解决冲突问题,安卓上的不需要代理手势直接修改滑动手势是否响应就可以,
判断flutter是否有手势可以响应,可以利用flutter的手势竞技场,在flutter的widget最上层加一个PanGestureRecognizer手势优先级是最低的,如果有其他手势响应会触发rejectGesture,如果没有其他手势触发会响应这个手势触发acceptGesture,
实现
设置代理手势的优先级高于UIScrollview高于的滑动手势
[superScrollView.panGestureRecognizer requireGestureRecognizerToFail:self.flutterGesture];
将flutter代理手势的touch事件传递到flutter
iOS的手势有个属性cancelsTouchesInView 默认是YES,响应手势的时候不会触发touch事件,给flutter的代理手势设置为YES就可以在代理手势响应的时候把touch事件传递到flutterView
flutterGesture.cancelsTouchesInView = NO;
判断flutter是否可以响应手势
class _PointerTracker extends PanGestureRecognizer {
MethodChannel channel = const MethodChannel('com.qqreader.flutter/scroll_channel');
bool _flutterGestureIsWorking = false;
@override
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
_flutterGestureIsWorking = true;
_notify();
}
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
_flutterGestureIsWorking = false;
_notify();
}
void _notify() {
try{
if (_flutterGestureIsWorking) {
channel.invokeMethod("isWork", "");
} else {
channel.invokeMethod("noWork", "");
}
} catch(e){
// ignored, really.
}
}
}
以上代码基本可以解决滑动冲突的问题了,但是会发现flutter页面滑动的时候反应会很慢,那是因为UIScrollview的delaysContentTouches 属性导致的,这个属性默认是YES,touch事件会延迟传递给内部的view flutter收到手势的时机会被延迟,会出现滑得很快但是页面走的很慢,delaysContentTouches设置为NO就可以解决了
superScrollView.delaysContentTouches = NO;
源码
iOS端
@interface QRNativeScrollFixViewController ()
@property (nonatomic, strong) UIPanGestureRecognizer *flutterGesture;
@property (nonatomic, strong) FlutterMethodChannel *scrollChannel;
@end
@implementation QRNativeScrollFixViewController
- (void)viewDidLoad{
[super viewDidLoad];
UIPanGestureRecognizer *flutterGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(flutterGestureAction)];
flutterGesture.delegate = self;
flutterGesture.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:flutterGesture];
self.flutterGesture = flutterGesture;
self.scrollChannel = [FlutterMethodChannel
methodChannelWithName:@"com.qqreader.flutter/scroll_channel"
binaryMessenger:self.engine.binaryMessenger];
[self handleMethod];
}
- (void)flutterGestureAction{
//do nothing
}
- (void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
UIScrollView *superScrollView = [self getScrollView];
superScrollView.delaysContentTouches = NO;
if(superScrollView){
[superScrollView.panGestureRecognizer requireGestureRecognizerToFail:self.flutterGesture];
}
}
- (UIScrollView *)getScrollView{
UIView *superView = self.view.superview;
while (superView) {
if([superView isKindOfClass:UIScrollView.class]){
return (UIScrollView *)superView;
} else {
superView = superView.superview;
}
}
return nil;
}
- (void)flutterHandleTouch:(BOOL)isWorking{
if(isWorking){
self.flutterGesture.state = UIGestureRecognizerStateBegan;
} else {
self.flutterGesture.state = UIGestureRecognizerStateFailed;
}
}
- (void)handleMethod {
@weakify(self);
[self.scrollChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
@strongify(self);
if ([@"isWork" isEqualToString:call.method]) {
[self flutterHandleTouch:YES];
}
if ([@"noWork" isEqualToString:call.method]) {
[self flutterHandleTouch:NO];
}
result(nil);
}];
}
@end
flutter源码
使用时直接用QRNativeScrollFixWidget 嵌套在最外层就可以
class QRNativeScrollFixWidget extends StatelessWidget {
const QRNativeScrollFixWidget({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return RawGestureDetector(
behavior: HitTestBehavior.translucent,
gestures: {
_PointerTracker: GestureRecognizerFactoryWithHandlers<_PointerTracker>(
() => _PointerTracker(),
(_PointerTracker instance) {
instance
..onStart = (DragStartDetails details) {
print('开始');
}
..onEnd = (detail) {
print('结束');
};
},
)
},
child: child,
);
}
}
class _PointerTracker extends PanGestureRecognizer {
MethodChannel channel = const MethodChannel('com.qqreader.flutter/scroll_channel');
bool _flutterGestureIsWorking = false;
@override
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
_flutterGestureIsWorking = true;
_notify();
}
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
_flutterGestureIsWorking = false;
_notify();
}
void _notify() {
try{
if (_flutterGestureIsWorking) {
channel.invokeMethod("isWork", "");
} else {
channel.invokeMethod("noWork", "");
}
} catch(e){
// ignored, really.
}
}
}
更多推荐
所有评论(0)