颤动悬停式触摸处理 - 无需抬起手指即可检测来自不同小部件的触摸

Posted

技术标签:

【中文标题】颤动悬停式触摸处理 - 无需抬起手指即可检测来自不同小部件的触摸【英文标题】:flutter hover-like touch handling - detect touch from different widgets without lifting the finger 【发布时间】:2020-03-06 12:46:28 【问题描述】:

假设我在屏幕上有 3 个Containers,它们通过改变颜色来对触摸做出反应。当用户的手指放在它们上时,它们应该会改变颜色,当触摸结束时它们应该恢复正常。 我想要的是这些容器在用户手指/指针在它们上时做出反应,即使触摸开始于容器外部屏幕上的随机区域。所以基本上我正在寻找的是就像CSS悬停一样。

如果我分别用GestureDetector 包装每个容器,它们不会对在它们之外开始的触摸事件做出反应。在另一个问题上(不幸的是我现在没有链接)建议用一个GestureDetector 包装所有容器,并为每个容器分配一个GlobalKey 以使它们彼此不同。

这是检测触摸手势的

class MyBoard extends StatefulWidget 
  static final keys = List<GlobalKey>.generate(3, (ndx) => GlobalKey());

  ...


class _MyBoardState extends State<MyBoard> 
  ...

  /// I control the number of buttons by the number of keys,
  /// since all the buttons should have a key
  List<Widget> makeButtons() 
    return MyBoard.keys.map((key) 
      // preapre some funcitonality here
      var someFunctionality;
      return MyButton(key, someFunctionality);
    ).toList();
  

  @override
  Widget build(BuildContext context) 
    return GestureDetector(
      onTapDown: (tapDownDetails) 
        handleTouch(context, tapDownDetails.globalPosition);
      ,
      onTapUp: (tapUpDetails) 
        finishTouch(context);
      ,
      onPanUpdate: (dragUpdateDetails) 
        handleTouch(context, dragUpdateDetails.globalPosition);
      ,
      onPanEnd: (panEndDetails) 
        finishTouch(context);
      ,
      onPanCancel: () 
        finishTouch(context);
      ,
      child: Container(
        color: Colors.green,
        width: 300.0,
        height: 600.0,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: makeButtons(),
        ),
      ),
    );
  

这里是简单的MyButton

class MyButton extends StatelessWidget 
  final GlobalKey key;
  final Function someFunctionality;
  MyButton(this.key, this.someFunctionality) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Consumer<KeyState>(
      builder: (buildContext, keyState, child) 
        return Container(
          width: 100.0,
          height: 40.0,
          color: keyState.touchedKey == this.key ? Colors.red : Colors.white,
        );
      ,
    );
  

_MyBoardState 中,我就是这样处理检测哪个MyButton 被触摸的:

MyButton getTouchingButton(Offset globalPosition) 
    MyButton currentButton;
    // result is outside of [isTouchingMyButtonKey] for performance
    final result = BoxHitTestResult();

    bool isTouchingButtonKey(GlobalKey key) 
      RenderBox renderBox = key.currentContext.findRenderObject();
      Offset offset = renderBox.globalToLocal(globalPosition);
      return renderBox.hitTest(result, position: offset);
    

    var key = MyBoard.keys.firstWhere(isTouchingButtonKey, orElse: () => null);
    if (key != null) currentButton = key.currentWidget;

    return currentButton;
  

我正在使用提供程序包,而不是用setState 重建整个MyBoard,只会重建相关MyButton 的相关部分。仍然每个按钮都有一个监听器,它在每次更新时都会重建,我使用的是GlobalKey。 On the flutter docs 它说:

全局键相对昂贵。如果您不需要上面列出的任何功能,请考虑使用 Key、ValueKey、ObjectKey 或 UniqueKey。

但是,如果我们查看getTouchingButton 方法,我需要GlobalKey 来获取renderBox 对象并获取currentWidget。我还必须为每个GlobalKey 进行一次hitTest。当onPanUpdate 被触发时重复此计算,这意味着当用户的手指移动一个像素时。

UI 更新速度不够快。只需轻按一下(以常规速度轻按和轻按),您通常不会在 MyButtons 上看到任何变化。

如果我需要总结我的问题,当手指悬停在不同的小部件上时,如何以更有效和优雅的方式检测到相同的单次触摸(不抬起)?

【问题讨论】:

嗨 Letitbe,我正在寻求实现类似的东西(当手指在小部件范围内经过时,Flutter 没有一个简单的命中解决方案) - 你有一个工作的 sn -p 在 github 上? 我在这里创建了一个回购github.com/FickleLife/flutter_touch_hittest_demo,它正在编译但无法正常工作,所以我显然遗漏了一些东西...... @user1048175 不幸的是,我没有回购作为示例。从问题中获取代码并按照答案中的描述进行更新。不久;以前,我保持按钮简单,使它们成为无状态小部件,并在创建每个小部件时传递 onTouch 函数(不是代码 sn-ps 中的函数名称)。但是作为解决方案,我编写了有状态的小部件,保持状态 public 并在状态中实现了大部分硬编码的触摸功能。请注意,我仍然将 GlobalKey 传递给每个按钮,通过这些键找到它们的状态并调用状态的方法(这就是它们公开的原因)。 【参考方案1】:

由于还没有人回答,我正在分享我自己最近想出的解决方案。现在我可以看到每次触摸时的视觉反应。它足够快,但仍然感觉 UI 上有一点延迟,感觉有点 hacky 而不是具体的解决方案。如果有人可以提供更好的解决方案,我可以将其标记为已接受的答案


我的解决方案:

首先要做的事;因为我们必须检测平移手势并且我们正在使用onPanUpdateonPanEnd,所以我可以删除onTapDownonTapUp,而只使用onPanStart。这也可以检测不动的简单点击触摸。

我也不再使用ProviderConsumer,因为它会在每次更新时重建所有Consumers。当MyButtons 的数量增加时,这确实是一个大问题。我没有让MyButton 保持简单和虚伪,而是将触摸处理工作移入其中。每个MyButton 都保存着当前是否被触摸的数据。

更新按钮是这样的:

class NewButton extends StatefulWidget 
  NewButton(Key key) : super(key: key);
  @override
  NewButtonState createState() => NewButtonState();


class NewButtonState extends State<NewButton> 
  bool isCurrentlyTouching = false;

  void handleTouch(bool isTouching) 
    setState(() 
      isCurrentlyTouching = isTouching;
    );
    // some other functionality on touch
  

  @override
  Widget build(BuildContext context) 
    return Container(
      width: 100.0,
      height: 40.0,
      color: isTouched ? Colors.red : Colors.white,
    );
  

注意 NewButtonState 不是私人的。我们将使用globalKey.currentState.handleTouch(bool) 来检测触摸手势。

【讨论】:

我正在与类似的事情作斗争。我遇到了这个帖子***.com/questions/50639652/…Rémi Rousselet 的回答可能也适用于这种情况。

以上是关于颤动悬停式触摸处理 - 无需抬起手指即可检测来自不同小部件的触摸的主要内容,如果未能解决你的问题,请参考以下文章

触摸时图像,手指抬起时消失?

无需从屏幕上移开手指即可触摸按钮

如何触摸 UIImageView,然后用另一个 UIImageView 替换,当手指移动时它会拖动,而不用抬起手指

苹果手机。在一个手指触摸屏幕时检测另一个手指触摸

PointerEvents:检测“通过”元素的触摸

如何检测“长按”