乐趣区

关于flutter:flutter系列之在flutter中使用流式布局

简介

咱们在开发 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

退出移动版