【问题标题】:Can Flutter detect a hovering stylus?Flutter 可以检测到悬停的手写笔吗?
【发布时间】:2020-11-11 10:26:08
【问题描述】:

某些手机,尤其是三星 Galaxy Note 系列设备,具有可以在靠近屏幕但​​不触摸屏幕时检测到的触控笔(触控笔?)。 Flutter 可以检测和处理这种事件吗?

(以下是我对此的调查,如果你已经知道答案,请随意跳过这个????)

Listener 类可以检测触控笔在触摸屏幕时执行的操作,MouseRegion 类应该检测使用悬停指针执行的操作。所以我写了这个简单的小部件来测试这两个类:

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String  _message = "Nothing happened";
  String _location = "Nothing happened";

  void onEnter(PointerEnterEvent event) {
    setState(() {
      _message = "Pointer entered";
    });
  }

  void onExit(PointerExitEvent event) {
    setState(() {
      _message = "Pointer exited";
    });
  }

  void onHover(PointerHoverEvent event) {
    setState(() {
      _location = "Pointer at ${event.localPosition.dx} ${event.localPosition.dy} distance ${event.distance}";
    });
  }

  void onDown(PointerDownEvent event) {
    setState(() {
      _message = "Pointer down";
    });
  }

  void onUp(PointerUpEvent event) {
    setState(() {
      _message = "Pointer up";
    });
  }

  void onMove(PointerMoveEvent event) {
    setState(() {
      _location = "Pointer moving at ${event.localPosition.dx} ${event.localPosition.dy} pressure ${event.pressure}";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            MouseRegion(
              onEnter: onEnter,
              onExit: onExit,
              onHover: onHover,
              child: Listener(
                  onPointerDown: onDown,
                  onPointerUp: onUp,
                  onPointerMove: onMove,
                  child: Container(
                      width: 500,
                      height: 500,
                      color: Colors.red
                  )
              )
            ),
            Text(_message),
            Text(_location)
          ]
        )
      ),
    );
  }
}

使用蓝牙鼠标,当我将指针移到该区域上时,MouseRegion 小部件会发出事件,但当我使用触控笔执行相同操作时,什么也没有发生。

然而,Listener 类会在我用触控笔触摸 区域时发出事件,并且事件实例甚至包括特定于触控笔的信息,例如压力。 PointerEvent 类甚至包含一个 distance 字段,根据它的描述,它应该指示从指针到屏幕的距离,这似乎正是我正在寻找的功能。

This comment 表示 Flutter“还没有准备好”支持可悬停的手写笔,但他似乎并不完全确定这一点,而且它是在一年前发布的,所以也许有些改变了。

最后,当我在运行应用程序时将触控笔悬停在屏幕上时,Android Studio 的控制台上会显示以下消息:

D/ViewRootImpl(16531): updatePointerIcon pointerType = 20001, calling pid = 16531
D/InputManager(16531): setPointerIconType iconId = 20001, callingPid = 16531

所以它似乎确实检测到了一些东西。在我看来,Flutter 正在积极丢弃与手写笔相关的事件,只处理鼠标事件,因为在本机端,鼠标和笔操作都可以由 MotionEvent 类处理。

我错过了什么吗?是否有其他类能够处理这种事件?或者在某处启用它的一些设置?还是目前真的不行?

【问题讨论】:

    标签: android flutter


    【解决方案1】:

    我希望十分钟后会有人来这里说“哦,你可以使用这个类,你不知道如何使用谷歌吗?”,但显然情况并非如此。所以我决定研究一下 Flutter 的源代码。

    所以我从MouseRegion 类开始,它使用_RawMouseRegion,它使用RenderMouseRegion。然后,它使用MouseTrackerAnnotation 注册一些事件处理回调。该类的实例由MouseTracker 拾取,MouseTracker 接收指针事件并调用所有对它们感兴趣的回调。这是在_handleEvent 函数中完成的,其前两行是:

    if (event.kind != PointerDeviceKind.mouse)
          return;
    

    所以我想我找到了罪魁祸首。只需将PointerDeviceKind.stylus 添加到if 语句即可解决此问题。或者也许这样做会使地球开始倒转或其他什么。 GitHub 问题可能是获得答案的最佳位置。

    但并非全部丢失。 MouseTracker 类从PointerRouter 获取其事件,其单例实例可在GestureBinding.instance.pointerRouter 获得。 PointerRouter 有一个 addGlobalRoute 方法,可让您注册自己的回调以接收事件,其中包括 MouseTracker 忽略的触控笔事件。

    我确定这不是推荐的做事方式,因为它绕过了 Flutter 的许多内部内容,而这些内容可能是有原因的。但是,虽然没有“官方”的做事方式(而且我怀疑这个非常具体的用例远不及他们的优先级列表的顶部),但可以通过这种方式解决它。嘿,我什至还直接使用PointerRouter 找到了regular widget,所以它可能不太危险。

    PointerRouter 为您提供的事件不如MouseRegion 中的事件方便,因为它的位置在全局坐标中。但是您可以获取小部件的RenderBox 并使用globalToLocal 将位置转换为本地坐标。这是一个小的工作示例:

    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      //We'll use the render box of the object with this key to transform the event coordinates:
      GlobalKey _containerKey = GlobalKey();
      //Store the render box:
      //(The method to find the render box starts with "find" so I suspect it is doing
      //some non trivial amount of work, so instead of calling it on every event, I'll
      //just store the renderbox here):
      RenderBox _rb;
      String  _message = "Nothing happened";
    
      _MyHomePageState() {
        //Register a method to receive the pointer events:
        GestureBinding.instance.pointerRouter.addGlobalRoute(_handleEvent);
      }
    
      @override
      void dispose() {
        //See? We're even disposing of things properly. What an elegant solution.
        super.dispose();
        GestureBinding.instance.pointerRouter.removeGlobalRoute(_handleEvent);
      }
    
      //We'll receive all the pointer events here:
      void _handleEvent(PointerEvent event) {
        //Make sure it is a stylus event:
        if(event.kind == PointerDeviceKind.stylus && _rb != null) {
          //Convert to the local coordinates:
          Offset coords = _rb.globalToLocal(event.position);
    
          //Make sure we are inside our component:
          if(coords.dx >= 0 && coords.dx < _rb.size.width && coords.dy >= 0 && coords.dy < _rb.size.height) {
            //Stylus is inside our component and we have its local coordinates. Yay!
            setState(() {
              _message = "dist=${event.distance} x=${coords.dx.toStringAsFixed(1)} y=${coords.dy.toStringAsFixed(1)}";
            });
          }
        }
      }
    
      @override
      void initState() {
        //Doing it this way, as suggested by this person: https://medium.com/@diegoveloper/flutter-widget-size-and-position-b0a9ffed9407
        WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
        super.initState();
      }
    
      _afterLayout(_) {
        _rb = _containerKey.currentContext.findRenderObject();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
              child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Container(
                        //Event position will be converted to this container's local coordinate space:
                        key: _containerKey,
                        width: 200,
                        height: 200,
                        color: Colors.red
                    ),
                    Text(_message)
                  ]
              )
          ),
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-23
      • 2019-12-19
      • 1970-01-01
      • 2010-10-07
      • 1970-01-01
      • 2021-03-09
      • 1970-01-01
      • 2016-09-03
      相关资源
      最近更新 更多