写点什么

flutter 系列之:flutter 中的 flow

作者:程序那些事
  • 2022 年 6 月 26 日
  • 本文字数:2606 字

    阅读完需:约 9 分钟

flutter系列之:flutter中的flow

简介

我们在开发 web 应用的时候,有时候为了适应浏览器大小的调整,需要动态对页面的组件进行位置的调整。这时候就会用到 flow layout,也就是流式布局。


同样的,在 flutter 中也有流式布局,这个流式布局的名字叫做 Flow。事实上,在 flutter 中,Flow 通常是和 FlowDelegate 一起使用的,FlowDelegate 用来设置 Flow 子组件的大小和位置,通过使用 FlowDelegate.paintChildre 可以更加高效的进行子 widget 的重绘操作。今天我们来详细讲解 flutter 中 flow 的使用。

Flow 和 FlowDelegate

先来看下 Flow 的定义:


class Flow extends MultiChildRenderObjectWidget
复制代码


Flow 继承自 MultiChildRenderObjectWidget,说它里面可以包含多个子 widget。


再来看下它的构造函数:


  Flow({    Key? key,    required this.delegate,    List<Widget> children = const <Widget>[],    this.clipBehavior = Clip.hardEdge,  }) : assert(delegate != null),       assert(clipBehavior != null),       super(key: key, children: RepaintBoundary.wrapAll(children));
复制代码


可以看到 Flow 中主要有三个属性,分别是 delegate,children 和 clipBehavior。


children 很好理解了,它就是 Flow 中的子元素。


clipBehavior 是一个 Clip 类型的变量,表示的是如何对 widget 进行裁剪。这里的默认值是 none。


最后一个非常重要的属性就是 FlowDelegate,FlowDelegate 主要用来控制 Flow 中子 widget 的位置变换。所以,当我们在 Flow 中定义好子 widget 之后,剩下的就是定义 FlowDelegate 来控制如何展示这些子 widget。


FlowDelegate 是一个抽象类,所以我们在使用的时候,需要继承它。


FlowDelegate 有几个非常重要的方法:


 Size getSize(BoxConstraints constraints) => constraints.biggest;
复制代码


这个方法用来定义 Flow 的 size,对于 Flow 来说,它的 size 是和子 widget 的 size 是独立的,Flow 的大小通过 getSize 方法来定义。


接下来是 getConstraintsForChild 方法:


  BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) => constraints;
复制代码


getConstraintsForChild 用来控制子 widget 的 Constraints。


paintChildren 用来控制如何绘制子 widget,也是我们必须要实现的方法:


  void paintChildren(FlowPaintingContext context);
复制代码


FlowDelegate 还有两个方法,分别用来判断是否需要 Relayout 和 Repaint,这两个方法的参数都是 FlowDelegate:


bool shouldRelayout(covariant FlowDelegate oldDelegate) => false;bool shouldRepaint(covariant FlowDelegate oldDelegate);
复制代码

Flow 的应用

有了上面的介绍,我们基本上已经了解如何构建 Flow 了,接下来我们通过一个具体的例子来加深对 Flow 的理解。


在本例中,我们主要是使用 Flow 来排列几个图标。


首先我们定义一个图标的数组:


  final List<IconData> buttonItems = <IconData>[    Icons.home,    Icons.ac_unit,    Icons.adb,    Icons.airplanemode_active,    Icons.account_box_rounded,  ];
复制代码


然后通过每个图标对应的 IconData 来构建一个 IconButton 的 widget:


  Widget flowButtonItem(IconData icon) {    return Padding(      padding: const EdgeInsets.symmetric(vertical: 10.0),      child: IconButton(        icon: Icon(icon,          size: 50,            color: Colors.blue        ),          onPressed: () {            buttonAnimation.status == AnimationStatus.completed                ? buttonAnimation.reverse()                : buttonAnimation.forward();          },
) ); }
复制代码


这里我们使用的是 IconButton,为了在不同 IconButton 之间留一些空间,我们将 IconButton 封装在 Padding 中。


在 onPressed 方法中,我们希望能够处理一些动画效果。这里的 buttonAnimation 是一个 AnimationController 对象:


AnimationController  buttonAnimation = AnimationController(      duration: const Duration(milliseconds: 250),      vsync: this,    );
复制代码


有了 flowButtonItem 之后,我们就可以构建 Flow 了:


  Widget build(BuildContext context) {    return Flow(      delegate: FlowButtonDelegate(buttonAnimation: buttonAnimation),      children:      buttonItems.map<Widget>((IconData icon) => flowButtonItem(icon)).toList(),    );  }
复制代码


Flow 的 child 就是我们刚刚创建的 flowButtonItem,FlowButtonDelegate 是我们需要新建的类,因为之前在构建 flowButtonItem 的时候,我们希望进行一些动画的绘制,而 FlowDelegate 又是真正用来控制子 Widget 绘制的类,所以我们需要将 buttonAnimation 作为参数传递给 FlowButtonDelegate。


下面是 FlowButtonDelegate 的定义:


class FlowButtonDelegate extends FlowDelegate {  FlowButtonDelegate({required this.buttonAnimation})      : super(repaint: buttonAnimation);
final Animation<double> buttonAnimation;
@override bool shouldRepaint(FlowButtonDelegate oldDelegate) { return buttonAnimation != oldDelegate.buttonAnimation; }
@override void paintChildren(FlowPaintingContext context) { double dy = 0.0; for (int i = 0; i < context.childCount; ++i) { dy = context.getChildSize(i)!.height * i; context.paintChild( i, transform: Matrix4.translationValues( 0, dy * buttonAnimation.value, 0, ), ); } }
复制代码


FlowButtonDelegate 继承自 FlowDelegate,并且传入了 buttonAnimation 对象。


这里我们根据 buttonAnimation 是否发生变化来决定是否进行 Repaint。


如果需要进行 Repaint,那么就要调用 paintChildren 的方法。


在 paintChildren 中,我们根据 child 自身的 height 和 buttonAnimation 的值来进行动画的绘制。


那么 buttonAnimation 的值是如何变化的呢?这就要回顾之前我们创建 flowButtonItems 的 onPress 方法了。


在 onPress 方法中,我们调用了 buttonAnimation.reverse 或者 buttonAnimation.forward 这两个方法来修改 buttonAnimation 的值。


运行之后的效果如下:



初始状态下,所有的组件都是在一起的。


当我们点击上面的图标的时候,我们可以得到下面的界面:



图标在动画中展开了。

总结

Flow 是一种比较复杂的 layout 组件,如果和动画进行结合使用,可以得到非常完美的效果。


本文的例子:https://github.com/ddean2009/learn-flutter.git

发布于: 2022 年 06 月 26 日阅读数: 34
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
flutter系列之:flutter中的flow_flutter_程序那些事_InfoQ写作社区