1.问题描述
原型设计图如下:
![](https://img.haomeiwen.com/i3687616/6b6b5049a07f3ef4.png)
不要关心写代码过程,最终的结果就是中间的视频tab一栏全部是用flutter写的,其余的是原生。如图:
![](https://img.haomeiwen.com/i3687616/6f13a562d5d1f790.png)
由于下方的tab是可以滚动切换的,也就是说视频页的时候,往左滑动,会到讲义页。这个时候问题来了,可以看到flutter页面里面也有一个左右滑动的widget,而flutter整个页面是放在原生的scrollview里面的。这个时候,如果scrollview是可以滚动的,那flutter内部不会响应事件,也就是flutter页面的内容不能滚动。如果把scrollview的滚动给禁止了,那flutter页面内的滚动一切正常,但是无法实现往左滑动切换tab的需求了。这要怎么办呢?
2.解决问题的思路
分析一下,事件的优先级,肯定是flutter的滚动优先,如果我们滚动的地方是在flutter的滚动widget里,那么就是flutter的widget自己滚动,如果滚动的是widget外部,那么就让scrollview滚动。这么一分析,那就知道,肯定是要让外部的scrollview不能滚动,这样才能让flutter响应事件,然后我们给flutter页面最外层套一个手势,监听滚动事件,然后把偏移量传递给原生,原生再用代码来设置scrollview的offset,是不是就可以实现了。同时还要设置在视频tab的时候scrollview不能滚动,但是到了讲义tab,又能够滚动,响应原生的事件。
3.实现代码
1. flutter代码
flutter这边很简单,就是在页面的最外层套一个GestureDetector,然后监听水平滑动手势。
PS:安卓没有该问题!
Widget build(BuildContext context) {
return Scaffold(
body: Platform.isIOS
/// iOS平台上,由于flutter横着滑动与系统scroll view手势发生冲突,需要特殊处理
? GestureDetector(
child: buildPage(context),
onHorizontalDragUpdate: (detail) {
/// 将水平方向偏移量传递给iOS原生
methodIOSVideoHorizontalScroll({
'event': 'in',
'value': detail.delta.dx
});
},
onHorizontalDragStart: (_) {
methodIOSVideoHorizontalScroll({'event': 'start'});
},
onHorizontalDragEnd: (detail) {
methodIOSVideoHorizontalScroll({
'event': 'end',
'value': detail.primaryVelocity
});
},
)
: buildPage(context));
}
在代码中,我把滑动进行中的偏移量传递给了原生,滑动结束时的速度也传递给了原生。传递给原生的代码。
methodIOSVideoHorizontalScroll(Map<String, dynamic> content) {
Map params = {
ACTION: NativeAction.resolveIOSVideoListGestureConflicts,
CONTENT: content
};
_methodChannelWithParams(params);
}
具体的代码根据各自不同的传递方式有所不同,但最终的思路都是不变的,就是把滑动进行中的偏移量和结束时的速度给原生。偏移量我们用来设置scrollview的offset,结束时的速度,如果有速度,我们就可以理解为原生的轻扫手势。
2.原生代码
首先是接收flutter传递过来的数据
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
// flutter传参给iOS
if([call.method isEqualToString:METHOD_CHANNEL_CHATTING]){
NSDictionary *dic = call.arguments;
if (dic == nil) {
return;;
}else {
if () {}......
} else if (action == resolveIOSVideoListGestureConflicts) {
// 将消息告诉当前vc,当前vc需要做出处理来解决手势冲突
id content = [dic objectForKey:@"content"];
if (self.flutterNoSlidingInVideoListSlideWidget && [content isKindOfClass:[NSDictionary class]]) {
NSString *msg = [(NSDictionary *)content objectForKey:@"event"];
double value = [(NSNumber *)[(NSDictionary *)content objectForKey:@"value"] doubleValue];
self.flutterNoSlidingInVideoListSlideWidget(msg, value);
}
}
}
}
}];
然后是页面处理逻辑
// 解决手势冲突
[DAVideoFlutterManager shareManager].flutterNoSlidingInVideoListSlideWidget = ^(NSString * _Nonnull msg, double value) {
// 如果当前只有视频一个tab,则不需要处理flutter的手势
if (weakself.myCategoryView.titles.count == 1) {
return;
}
if ([msg isEqualToString:@"start"]) {
weakself.lastOffsetX = 0;
} else if ([msg isEqualToString:@"end"]) {
weakself.lastOffsetX = 0;
NSInteger current = 0;
// 缓慢滑动,过了列表半个屏幕,跳到第二个页面
if (weakself.listContainerView.scrollView.contentOffset.x > DAVI_ScreenWidth/2) {
current = 1;
}
// 如果结束时,是有速度的,可以理解为轻扫屏幕,小于0表示往左轻扫
if (value < 0) {
current = 1;
}
[weakself.myCategoryView selectItemAtIndex:current];
[weakself.myCategoryView reloadDataWithoutListContainer];
} else if ([msg isEqualToString:@"in"]) {
// 计算偏移量
CGFloat offsetX = weakself.listContainerView.scrollView.contentOffset.x-value-weakself.lastOffsetX;
// 如果是往右偏移,则不移动
if (offsetX < 0) {
offsetX = 0;
weakself.lastOffsetX = 0;
} else {
weakself.lastOffsetX += value;
}
weakself.listContainerView.scrollView.contentOffset = CGPointMake(offsetX, 0);
}
};
具体的处理方法看代码以及注释。其中有一件事情我们需要特别注意。那就是计算scrollview的偏移量的时候,flutter传递过来的手势是相对于flutter页面本身的,而不是相对于外面的原生的view,所以外部scrollview计算偏移量的时候,得把自身上一次的偏移量给记录下来,然后加上flutter传递过来的偏移量。
3.其余代码
其余代码就不贴上来了,主要是flutter和原生手势冲突这块实现了,其余的都是小意思了。
网友评论