滑动列表项以获取更多选项(Flutter)

Posted

技术标签:

【中文标题】滑动列表项以获取更多选项(Flutter)【英文标题】:Swipe List Item for more options (Flutter) 【发布时间】:2018-03-20 23:44:35 【问题描述】:

前几天我决定为 Pinterest 的应用程序选择一个 Ui 来练习使用 Flutter 构建应用程序,但我坚持使用在水平拖动时显示“更多”和“删除”按钮的滑块。 Picture on the right.

我没有足够的知识来使用手势结合动画来在颤振中创建这样的东西。这就是为什么我希望你们中的某个人可以为像我这样的每个人做一个例子,我们可以理解如何在 ListView.builder 中实现这样的东西。

(Source)

来自 macOS 邮件应用程序的 gif 示例:

【问题讨论】:

【参考方案1】:

已经有一个用于这种手势的小部件。它叫做Dismissible

你可以在这里找到它。 https://docs.flutter.io/flutter/widgets/Dismissible-class.html

编辑

如果您需要完全相同的转换,您可能必须自己实现。 我做了一个基本的例子。您可能想要稍微调整一下动画,但至少它可以工作。

class Test extends StatefulWidget 
  @override
  _TestState createState() => new _TestState();


class _TestState extends State<Test> 
  double rating = 3.5;

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      body: new ListView(
        children: ListTile
            .divideTiles(
              context: context,
              tiles: new List.generate(42, (index) 
                return new SlideMenu(
                  child: new ListTile(
                    title: new Container(child: new Text("Drag me")),
                  ),
                  menuItems: <Widget>[
                    new Container(
                      child: new IconButton(
                        icon: new Icon(Icons.delete),
                      ),
                    ),
                    new Container(
                      child: new IconButton(
                        icon: new Icon(Icons.info),
                      ),
                    ),
                  ],
                );
              ),
            )
            .toList(),
      ),
    );
  


class SlideMenu extends StatefulWidget 
  final Widget child;
  final List<Widget> menuItems;

  SlideMenu(this.child, this.menuItems);

  @override
  _SlideMenuState createState() => new _SlideMenuState();


class _SlideMenuState extends State<SlideMenu> with SingleTickerProviderStateMixin 
  AnimationController _controller;

  @override
  initState() 
    super.initState();
    _controller = new AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
  

  @override
  dispose() 
    _controller.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    final animation = new Tween(
      begin: const Offset(0.0, 0.0),
      end: const Offset(-0.2, 0.0)
    ).animate(new CurveTween(curve: Curves.decelerate).animate(_controller));

    return new GestureDetector(
      onHorizontalDragUpdate: (data) 
        // we can access context.size here
        setState(() 
          _controller.value -= data.primaryDelta / context.size.width;
        );
      ,
      onHorizontalDragEnd: (data) 
        if (data.primaryVelocity > 2500)
          _controller.animateTo(.0); //close menu on fast swipe in the right direction
        else if (_controller.value >= .5 || data.primaryVelocity < -2500) // fully open if dragged a lot to left or on fast swipe to left
          _controller.animateTo(1.0);
        else // close if none of above
          _controller.animateTo(.0);
      ,
      child: new Stack(
        children: <Widget>[
          new SlideTransition(position: animation, child: widget.child),
          new Positioned.fill(
            child: new LayoutBuilder(
              builder: (context, constraint) 
                return new AnimatedBuilder(
                  animation: _controller,
                  builder: (context, child) 
                    return new Stack(
                      children: <Widget>[
                        new Positioned(
                          right: .0,
                          top: .0,
                          bottom: .0,
                          width: constraint.maxWidth * animation.value.dx * -1,
                          child: new Container(
                            color: Colors.black26,
                            child: new Row(
                              children: widget.menuItems.map((child) 
                                return new Expanded(
                                  child: child,
                                );
                              ).toList(),
                            ),
                          ),
                        ),
                      ],
                    );
                  ,
                );
              ,
            ),
          )
        ],
      ),
    );
  

编辑

Flutter 不再允许在 SlideTransition animation 属性中输入 Animation&lt;FractionalOffset&gt;。根据这篇文章https://groups.google.com/forum/#!topic/flutter-dev/fmr-C9xK5t4 它应该被替换为AlignmentTween 但这也不起作用。相反,根据这个问题:https://github.com/flutter/flutter/issues/13812 将其替换为原始 Tween 并直接创建 Offset 对象可以代替。不幸的是,代码不太清楚。

【讨论】:

原始帖子询问如何在您滑动时将小部件放置在右上角,在提供的 gif 中,这将是带有垃圾桶图标的红色矩形,问题是与滑动效果本身无关,而是在滑动时出现红色矩形。 Dismissible 包含一个用于此目的的background 参数。 但是您需要在某个位置停止滑动效果,以便用户能够与后台中的项目进行交互,否则小部件将在之前调整为全尺寸或零您将能够与背景交互,有没有办法将滑动冻结在某个位置,以便显示足够的背景并能够与之交互? @Darky 这在 ios 设计语言中已经很好了,为什么 Flutter 会因为它不遵循材料设计而阻止我这样做,也许我正在制作两个视图,一个用于 android,一个用于 iOS ,请检查:i.imgur.com/jvsfElV.gif?1 @azizia 并不是说​​颤动会阻止你做任何事情,除了材料。只是库比蒂诺的主题还没有完全完成,我猜?无论如何,如果您想进行这种精确的转换,则必须自己实现。我用一个例子编辑了我的帖子。【参考方案2】:

我创建了一个包来进行这种布局:flutter_slidable(感谢 Rémi Rousselet 的基本想法)

有了这个包,为列表项创建上下文操作变得更加容易。例如,如果您想创建您描述的那种动画:

您将使用此代码:

new Slidable(
  delegate: new SlidableDrawerDelegate(),
  actionExtentRatio: 0.25,
  child: new Container(
    color: Colors.white,
    child: new ListTile(
      leading: new CircleAvatar(
        backgroundColor: Colors.indigoAccent,
        child: new Text('$3'),
        foregroundColor: Colors.white,
      ),
      title: new Text('Tile n°$3'),
      subtitle: new Text('SlidableDrawerDelegate'),
    ),
  ),
  actions: <Widget>[
    new IconSlideAction(
      caption: 'Archive',
      color: Colors.blue,
      icon: Icons.archive,
      onTap: () => _showSnackBar('Archive'),
    ),
    new IconSlideAction(
      caption: 'Share',
      color: Colors.indigo,
      icon: Icons.share,
      onTap: () => _showSnackBar('Share'),
    ),
  ],
  secondaryActions: <Widget>[
    new IconSlideAction(
      caption: 'More',
      color: Colors.black45,
      icon: Icons.more_horiz,
      onTap: () => _showSnackBar('More'),
    ),
    new IconSlideAction(
      caption: 'Delete',
      color: Colors.red,
      icon: Icons.delete,
      onTap: () => _showSnackBar('Delete'),
    ),
  ],
);

【讨论】:

看起来不错。我实施到我的项目。我想知道是否可以实现滑动全屏方向时运行功能的动作 很棒的小部件。我不知道为什么 Flutter 中没有包含这些很棒的小部件。一个问题,当我们再次点击元素时是否可以关闭选项? (以避免再次滑动以再次关闭它们) 很酷的小部件。我的一个问题是如何告诉用户该行是可滑动的? @PandurangaRaoSadhu meybe 你可以使用工具提示或类似的东西 嗨 Roman,flutter_slidable 包非常好,我想知道如何在客户第一次打开页面时仅向第一个项目显示演示滑动动画【参考方案3】:

我有一项任务需要与我尝试过 Romain Rastel 和 Rémi Rousselet 的答案相同的可滑动菜单操作。但我有复杂的小部件树。该可滑动解决方案的问题是它们继续使用其他小部件(到列表视图的左侧小部件)。我在这里找到了一个击球手的解决方案,有人写了一篇很好的文章medium,GitHub 示例是here。

【讨论】:

这只是 Dismissible 的颤振示例代码。与上面提供的答案不同,没有删除按钮。【参考方案4】:

更新了具有空安全性的代码Flutter:2.x 首先你需要在你的项目中添加flutter_slidable包并添加下面的代码然后让我们享受......

 Slidable(
  actionPane: SlidableDrawerActionPane(),
  actionExtentRatio: 0.25,
  child: Container(
    color: Colors.white,
    child: ListTile(
      leading: CircleAvatar(
        backgroundColor: Colors.indigoAccent,
        child: Text('$3'),
        foregroundColor: Colors.white,
      ),
      title: Text('Tile n°$3'),
      subtitle: Text('SlidableDrawerDelegate'),
    ),
  ),
  actions: <Widget>[
    IconSlideAction(
      caption: 'Archive',
      color: Colors.blue,
      icon: Icons.archive,
      onTap: () => _showSnackBar('Archive'),
    ),
    IconSlideAction(
      caption: 'Share',
      color: Colors.indigo,
      icon: Icons.share,
      onTap: () => _showSnackBar('Share'),
    ),
  ],
  secondaryActions: <Widget>[
    IconSlideAction(
      caption: 'More',
      color: Colors.black45,
      icon: Icons.more_horiz,
      onTap: () => _showSnackBar('More'),
    ),
    IconSlideAction(
      caption: 'Delete',
      color: Colors.red,
      icon: Icons.delete,
      onTap: () => _showSnackBar('Delete'),
    ),
  ],
);

【讨论】:

以上是关于滑动列表项以获取更多选项(Flutter)的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:无法或更改 TabController 的选项卡之间滑动的敏感性

重识Flutter 在不同的滑动列表场景,请选择合适的Slivers - part2

如何使用 dart/flutter 中的共享首选项保存和获取列表列表

获取功能项以列出视图

Flutter Firestore QuerySnapshot 没有 getter 'document 的实例

Flutter事件分发和坐标获取