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

时间:2017-10-09 17:31:49

标签: android ios animation dart flutter

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

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

enter image description here (Source)

来自macOS邮件App的gif示例:

enter image description here

4 个答案:

答案 0 :(得分:29)

我创建了一个用于进行这种布局的程序包:flutter_slidable(感谢RémiRousselet的基本想法)

使用此软件包,可以更轻松地为列表项创建上下文操作。例如,如果要创建描述的动画类型:

Drawer (iOS) animation

您将使用以下代码:

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'),
    ),
  ],
);

答案 1 :(得分:21)

已经有了这种手势的小部件。它被称为Dismissible

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

修改

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

enter image description here

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不再允许Animation<FractionalOffset> SlideTransition属性中的animation类型。根据这篇文章https://groups.google.com/forum/#!topic/flutter-dev/fmr-C9xK5t4,它应该替换为AlignmentTween,但这也不起作用。相反,根据此问题:https://github.com/flutter/flutter/issues/13812将其替换为原始Tween并直接创建Offset对象。不幸的是,代码不太清楚。

答案 2 :(得分:1)

Padding(
        padding: const EdgeInsets.all(8.0),
        child: ListView.builder(
            itemCount: the_length_of_your_items,
            itemBuilder: (BuildContext context,int index) {
              return Slidable(
                actionPane: SlidableDrawerActionPane(),
                actionExtentRatio: 0.25,
                child: Container(
                  color: Colors.white,
                  child: ListTile(
                    leading: Text(('the_leading')),
                    title: Text(('the_title')),
                    trailing: Text(('the_trailing')),
                    subtitle: Text(('the_subtitle')),
                  ),
                ),
                actions: <Widget>[
                  IconSlideAction(
                     caption: 'Archive',
                    color: Colors.blue,
                    icon: Icons.archive,
                    onTap: () {print('archive');},
                  ),
                  IconSlideAction(
                    caption: 'Share',
                    color: Colors.indigo,
                    icon: Icons.share,
                    onTap: () {print('share');},
                  ),
                ],
                secondaryActions: <Widget>[
                  IconSlideAction(
                    caption: 'More',
                    color: Colors.black45,
                    icon: Icons.more_horiz,
                    onTap: ()  {print('more');},
                  ),
                  IconSlideAction(
                    caption: 'Delete',
                    color: Colors.red,
                    icon: Icons.delete,
                    onTap: () {print('delete');},
                  ),
                ],
              );
            }),
      ),

答案 3 :(得分:0)

我有一个任务需要与尝试Romain Rastel和RémiRousselet的答案相同的可滑动菜单操作。但我有复杂的小部件树。可滑动解决方案的问题是它们在其他小部件上(在列表视图的左侧小部件上)。我在这里找到了一个更好的解决方案,有人在这里写了一篇不错的文章medium,而GitHub示例是here