Flutter:实现 peek & pop 效果

Posted

技术标签:

【中文标题】Flutter:实现 peek & pop 效果【英文标题】:Flutter: Implementing a peek & pop effect 【发布时间】:2018-07-23 14:09:48 【问题描述】:

我试图实现 peek & pop 效果 - 当用户点击并按住卡片时,会打开一个对话框,并在用户不再点击屏幕时关闭:

Listener(
    onPointerDown: (PointerDownEvent e) 
      // open dialog
      showDialog(
          context: context,
          builder: (context) => Container(
                child: Card(),
              ));
    ,
    onPointerUp: (PointerUpEvent e) 
      // dismiss dialog
      if (Navigator.of(context).canPop()) 
        Navigator.of(context).pop('dialog');
      
    ,
    child: Card()
)

效果很好,当我按住卡片时会显示对话框,当我不再按住卡片时会关闭。

但我希望在调用 onPointerDown 之前有一些延迟,例如来自 GestureDectector 的 onLongPress - 当我长按时我能够显示对话框,但是当我离开屏幕时从未调用过 onTapUp:

GestureDetector(
  onLongPress: () 
    // open dialog
    showDialog(
        context: context,
        builder: (context) => Container(child: Card()));
  ,
  onTapUp: (TapUpDetails d) 
    // dismiss dialog
    if (Navigator.of(context).canPop()) 
      Navigator.of(context).pop();
    
  ,
  child: Card()
)

我尝试通过以下方式执行此操作,但也从未调用过 onTapUp:

  GestureDetector(
  onTapDown: (TapDownDetails d) 
    // open dialog
    showDialog(context: context, builder: (context) => Card());
  ,
  onTapUp: (TapUpDetails d) 
    // dismiss dialog
    if (Navigator.of(context).canPop()) 
      Navigator.of(context).pop();
    
  ,
  child: Card())

但以下显示水龙头已正确注册:

GestureDetector(
  onTapDown: (TapDownDetails d) 
    print("down")
  ,
  onTapUp: (TapUpDetails d) 
    print("up")
  ,
  child: Card()
)

环顾四周后,我注意到flutter PR 添加了 onLongPressUp - 我将这些更改添加到我的颤振中,然后尝试重新实现我以前的代码,如下所示:

GestureDetector(
  onLongPress: () 
    // open dialog
    showDialog(
        context: context,
        builder: (context) => Container(child: Card()));
  ,
  onLongPressUp: () 
    // dismiss dialog
    if (Navigator.of(context).canPop()) 
      Navigator.of(context).pop();
    
  ,
  child: Card()
)

对话框在长按时显示,但当我不再长按时从未关闭 - 似乎没有调用 onLongPressUp,但以下显示它正在正确注册我的点击:

GestureDetector(
  onLongPress: () 
    print("longpress")
  ,
  onLongPressUp: () 
    print("longpressup")
  ,
  child: Card()
)

在所有这些中,只有使用 Listener 我才能通过点击向下和向上点击来打开和关闭对话框,但我想在调用 onPointerDown 之前添加一个延迟,我还尝试在 onPointerDown 添加一个 Timer但似乎不起作用。

有什么解决办法吗?

【问题讨论】:

我来晚了一点,但你知道,从我的理解来看,这似乎是 BuildContext 的问题。显然,函数正在被调用,并且当您将导航器替换为堆栈时这会起作用,因此问题一定是导航器无法识别它可以弹出的事实。也许这是整个框架的一个更深层次的潜在问题? 【参考方案1】:

这是一个使用StatefulWidgetStack 而不是Navigator 的解决方案。

我在您的代码中遇到的问题是,当您使用showDialog 打开对话框时,Listener 不再接收指针事件,因此最好将所有内容放在一个***小部件下。

我也在使用Timer,当指针上升或用户离开页面时,它会被取消。

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyHomePage(),
    );
  


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


class _MyHomePageState extends State<MyHomePage> 
  Timer _showDialogTimer;
  bool _dialogVisible = false;

  @override
  void dispose() 
    _showDialogTimer?.cancel();
    super.dispose();
  

  void _onPointerDown(PointerDownEvent event) 
    _showDialogTimer = Timer(Duration(seconds: 1), _showDialog);
  

  void _onPointerUp(PointerUpEvent event) 
    _showDialogTimer?.cancel();
    _showDialogTimer = null;
    setState(() 
      _dialogVisible = false;
    );
  

  void _showDialog() 
    setState(() 
      _dialogVisible = true;
    );
  

  @override
  Widget build(BuildContext context) 
    final layers = <Widget>[];

    layers.add(_buildPage());

    if(_dialogVisible) 
      layers.add(_buildDialog());
    

    return Listener(
      onPointerDown: _onPointerDown,
      onPointerUp: _onPointerUp,
      child: Stack(
        fit: StackFit.expand,
        children: layers,
      ),
    );
  

  Widget _buildPage() 
    return Scaffold(
      appBar: AppBar(
        title: Text('Example App'),
      ),
      body: Center(
        child: Text('Press me'),
      ),
    );
  

  Widget _buildDialog() 
    return Container(
      color: Colors.black.withOpacity(0.5),
      padding: EdgeInsets.all(50.0),
      child: Card(),
    );
  

【讨论】:

我没有意识到它可以这样使用,感谢您的解决方案和您的时间!【参考方案2】:

编辑 3:v1.0.0 终于发布了!比以往更流畅、更优化、更美观。非常可定制且非常易于使用。在Pub 或GitHub 上查看。

编辑 2:v0.1.9 不再需要对 Flutter 的普通“binding.dart”进行任何修改!你可以让你的 Flutter 源代码保持愉快。如果您从早期版本更新,您可以将“binding.dart”恢复为原始格式。感谢大家的反馈。

编辑:人们对修改 Flutter 的正常“binding.dart”表示担忧。不用担心,即将发布的 v0.1.9 不需要此修改。现在,您可以暂时按照安装说明开始使用 v0.1.8 进行开发。更新后,您可以将“binding.dart”恢复为原始格式。

我不知道它是否仍然相关,但我正在为此特定目的开发一个 Flutter 包,您可以找到 here 和 here。

这是基于同名 ios 功能的 Flutter 的 Peek & Pop 实现。

这个包的强大之处就是我喜欢称之为“手势识别重新路由”。通常,当带有 GestureDetector 或类似的新小部件被推送到用于检测 Force Press 的初始小部件上时,用户必须重新启动手势以便 Flutter 继续更新它。这个包解决了这个问题。如文档中所述:

///This function is called by the instantiated [PeekAndPopChild] once it is ready to be included in the Peek & Pop process. Perhaps the most
///essential functionality of this package also takes places in this function: The gesture recognition is rerouted from the  [PeekAndPopDetector]
///to the instantiated [PeekAndPopChild]. This is important for avoiding the necessity of having the user stop and restart their Force Press.
///Instead, the [PeekAndPopController] does this automatically so that the existing Force Press can continue to update even when if
///[PeekAndPopDetector] is blocked by the view which is often the case especially when using PlatformViews.

包的核心是“PeekAndPopController”小部件。这个小部件是高度可定制的。您可以控制整个过程,甚至可以根据自己的喜好阻止默认行为并运行您自己的序列。

看看这个video 的一些例子。这是来自 V0.1.0 的视频,因此该软件包现在更加优化 - 它运行得更好,更流畅。

如果您有任何问题,请告诉我!

【讨论】:

欢迎提供解决方案链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的内容您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.

以上是关于Flutter:实现 peek & pop 效果的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Navigator 移除直到

Flutter:Navigator.of(context).pop() 返回黑屏

Flutter:在 Navigation.pop 上刷新父小部件

断言失败:/C:/Flutter/sdk/flutter/packages/flutter/lib/src/widgets/will_pop_scope.dart:135:12 _route == M

Navigator.pop(context) 不返回上一屏 Flutter

Flutter 图像在 navigator pop 上再次下载