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