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

Luk*_*ner 29 animation android ios dart flutter

有一天前,我决定从Pinterest为应用程序选择一个Ui来练习使用Flutter构建应用程序,但我仍然坚持使用Slider,它在水平拖动时显示"更多"和"删除"按钮.图片在右边.

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

在此输入图像描述 (资源)

来自macOS邮件App的gif示例:

在此输入图像描述

Rom*_*tel 50

我创建了一个用于执行此类布局的包:flutter_slidable(感谢RémiRousselet为基础的想法)

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

抽屉(iOS)动画

您将使用此代码:

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'),
    ),
  ],
);
Run Code Online (Sandbox Code Playgroud)

  • 您是如何学习设计此类高级UI的? (4认同)
  • 看起来很棒。我实施到我的项目。我想知道是否可以实现在滑动全屏方向时运行功能的动作 (3认同)
  • 很棒的小部件。我不知道为什么Flutter中没有包含这些出色的小部件。一个问题,当我们再次单击该元素时,是否可以关闭该选项?(以避免再次滑动以再次将其关闭) (2认同)

Rém*_*let 23

这种姿势已有一个小部件.它被称为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(),
                            ),
                          ),
                        ),
                      ],
                    );
                  },
                );
              },
            ),
          )
        ],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

编辑

扑不再允许输入Animation<FractionalOffset>SlideTransition animation属性.根据这篇文章https://groups.google.com/forum/#!topic/flutter-dev/fmr-C9xK5t4它应该被替换,AlignmentTween但这也不起作用.相反,根据这个问题:https: //github.com/flutter/flutter/issues/13812替换它而不是原始Tween和直接创建Offset对象的工作.不幸的是,代码不太清楚.

  • Dismissible 包含一个为此目的而创建的“background”参数。 (2认同)
  • @Darky,这在 iOS 设计语言中已经很好了,为什么 flutter 会阻止我这样做,只是因为它不遵循材料设计,也许我正在制作两个视图,一个用于 android,一个用于 iOS,请检查:https:// i.imgur.com/jvsfElV.gif?1 (2认同)

小智 8

更新了空安全代码Flutter:2.x \n首先,您需要在项目中添加flutter_slidable包并添加以下代码,然后让我们享受...

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


Dmi*_*nov 6

我查看了很多文章和答案,发现 @R\xc3\xa9mi Rousselet 答案最适合在没有第三方库的情况下使用。

\n

只需对 @R\xc3\xa9mi\ 的代码进行一些改进,使其可以在现代 SDK 中使用,而不会出现错误和 null 安全性。

\n

我还平滑了一点移动,使按钮的速度看起来与手指移动相同。\n并且我在代码中添加了一些注释:

\n
import \'package:flutter/material.dart\';\n\nclass SlidebleList extends StatefulWidget {\n  const SlidebleList({Key? key}) : super(key: key);\n\n  @override\n  State<SlidebleList> createState() => _SlidebleListState();\n}\n\nclass _SlidebleListState extends State<SlidebleList> {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: ListView(\n        children: ListTile.divideTiles(\n          context: context,\n          tiles: List.generate(42, (index) {\n            return SlideMenu(\n              menuItems: <Widget>[\n                Container(\n                  color: Colors.black12,\n                  child: IconButton(\n                    icon: const Icon(Icons.more_horiz),\n                    onPressed: () {},\n                  ),\n                ),\n                Container(\n                  color: Colors.red,\n                  child: IconButton(\n                    color: Colors.white,\n                    icon: const Icon(Icons.delete),\n                    onPressed: () {},\n                  ),\n                ),\n              ],\n              child: const ListTile(\n                title: Text("Just drag me"),\n              ),\n            );\n          }),\n        ).toList(),\n      ),\n    );\n  }\n}\n\nclass SlideMenu extends StatefulWidget {\n  final Widget child;\n  final List<Widget> menuItems;\n\n  const SlideMenu({Key? key,\n    required this.child, required this.menuItems\n  }) : super(key: key);\n\n  @override\n  State<SlideMenu> createState() => _SlideMenuState();\n}\n\nclass _SlideMenuState extends State<SlideMenu> with SingleTickerProviderStateMixin {\n  late AnimationController _controller;\n\n  @override\n  initState() {\n    super.initState();\n    _controller = AnimationController(\n        vsync: this, duration: const Duration(milliseconds: 200));\n  }\n\n  @override\n  dispose() {\n    _controller.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    //Here the end field will determine the size of buttons which will appear after sliding\n    //If you need to appear them at the beginning, you need to change to "+" Offset coordinates (0.2, 0.0)\n    final animation =\n    Tween(begin: const Offset(0.0, 0.0),\n        end: const Offset(-0.2, 0.0))\n        .animate(CurveTween(curve: Curves.decelerate).animate(_controller));\n\n    return GestureDetector(\n      onHorizontalDragUpdate: (data) {\n        // we can access context.size here\n        setState(() {\n          //Here we set value of Animation controller depending on our finger move in horizontal axis\n          //If you want to slide to the right, change "-" to "+"\n          _controller.value -= (data.primaryDelta! / (context.size!.width*0.2));\n        });\n      },\n      onHorizontalDragEnd: (data) {\n        //To change slide direction, change to data.primaryVelocity! < -1500\n        if (data.primaryVelocity! > 1500)\n          _controller.animateTo(.0); //close menu on fast swipe in the right direction\n        //To change slide direction, change to data.primaryVelocity! > 1500\n        else if (_controller.value >= .5 || data.primaryVelocity! < -1500)\n          _controller.animateTo(1.0); // fully open if dragged a lot to left or on fast swipe to left\n        else // close if none of above\n          _controller.animateTo(.0);\n      },\n      child: LayoutBuilder(builder: (context, constraint) {\n        return Stack(\n          children: [\n            SlideTransition(\n                position: animation,\n                child: widget.child,\n            ),\n            AnimatedBuilder(\n                animation: _controller,\n                builder: (context, child) {\n                  //To change slide direction to right, replace the right parameter with left:\n                  return Positioned(\n                    right: .0,\n                    top: .0,\n                    bottom: .0,\n                    width: constraint.maxWidth * animation.value.dx * -1,\n                    child: Row(\n                      children: widget.menuItems.map((child) {\n                        return Expanded(\n                          child: child,\n                        );\n                      }).toList(),\n                    ),\n                  );\n                })\n          ],\n        );\n      })\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n