Flutter:解决CupertinoContextMenu手势冲突
前置知识
两个TapGestureRecognizer的冲突
两个Tap gesture Recognizer,一个套在另一个上层
时间过短的tap,arena刚开启,kPressTimeout还未完成就将关闭,两个tap不会自己承认成功。arena默认选择子child的tap作winner,顺序
onPointerDown
inside onTapDown
inside onTapUp
inside onTap一旦超过100ms(kPressTimeout),两个tap的onTapDown都会触发
接下来无论持续按多久
只要平移距离在preAcceptSlopTolerance(flutter里这个值限定死了是kTouchSlop,令人感叹)内
arena里没有其他手势宣布胜利(在本情形里没有其他主动胜利的gesture)
那么两个tap都准备成功,最后选出子child的tap作winner,reject 外层的tap,最终触发顺序:
onPointerDown
inside onTapDown and onTapDown
inside onTapUp
inside onTap(与onTapUp一般来说是同时触发,但这里标明顺序是因为flutter里这么写的=w=)and onTapCancel(一个手势win同时会触发另一个的lose)
理解CupertinoContextMenu部分原理
1 | // The duration of the transition used when a modal popup is shown. Eyeballed |
打开_ContextMenuRoute之前的三个阶段:
onTapDown:叠加诱饵子(Decoy child),并开始[_openController]动画。
triggerred after a period of time after Listener sent onPointerDown. Typically, the time elapsed is the [kPressTimeout] in flutter.
holding: 只要手指一直按住,openController就会保持动画
一旦指针抬起屏幕,就会调用onTapUp(如果对方获胜,则调用onTapCancel)
1.如果动画进度大于中点:继续完成。动画完成后,_ContextMenuRoute将被推进,诱饵子将被删除。
2.其他:反向执行动画直到完毕。一旦完毕,就删除诱饵子。
总之,ContextMenu的成功触发并不取决于此点击手势的获胜。
冲突原因
如果我们在“CupertinoContextMenu”的子树上放置一个“TapGestureRecognizer”。
为了简单起见,我们将“CupertinoContextMenu”中的“TapGestureRecognizer”命名为tg2,将“CupertinoContextMenu”子树中的“TapGestureRecognizer”命名为tg1,按压持续时间命名为t1。
当 kPressTimeout+_previewLongPressTimeout/2
(500ms) < t1 < kPressTimeout+_previewLongPressTimeout
(900ms)时,此时
1.虽然tg2被拒绝了,但_ContextMenuRoute
仍然被打开。
2.tg1的onTap方法被触发
问题是,此ContextMenu中的GestureRecognizer是TapGestureRecognizer,而不是LongPressGestureRecognizer,Tap不声称获胜,但GestureArena选择最深的Tap来获胜。
1.当t1大于500ms时,这意味着它已通过“PrimaryPointerGestureRecognizer.deadline”加上“openController”持续时间的一半,意味着“_ContextMenuRoute”即将打开。在“GestureArena”收到指针活动后,tg1与tg2竞争。默认的“子胜利”起作用了,并调用了tg1的onTap,因此两者都被触发了。
2.当t1小于900ms时,因为路线此时已打开,并且两者都将被竞技场拒绝。
解决方法
原因是“TapGestureRecognizer”不会自行宣布胜利。添加以下代码。
1 | // call this when animation's value first reaches [_midpoint] |
Related issues
https://github.com/flutter/flutter/issues/70716
https://github.com/flutter/flutter/issues/52226
https://github.com/flutter/flutter/issues/81057
Related PR
https://github.com/flutter/flutter/pull/131030