flutter 中的 flow

更新日期: 2022-06-29阅读: 808标签: 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

来自:https://xie.infoq.cn/article/97547788a6900c6916f3d2b11

链接: https://www.fly63.com/article/detial/11821

移动跨平台框架Flutter介绍和学习线路

Flutter是一款移动应用程序SDK,一份代码可以同时生成iOS和Android两个高性能、高保真的应用程序。Flutter目标是使开发人员能够交付在不同平台上都感觉自然流畅的高性能应用程序。我们兼容滚动行为、排版、图标等方面的差异。

初识flutter

关注flutter已经好久,因为没有发正式版,所以一直也不想过早的躺浑水,但是最近无意中看到几篇文章,再加上美团和咸鱼等app也一直在做灰度测试,所以上周开始看了一下官方文档,地址:https://flutter.io/docs/get-started/install,然后在此做一下总结。

深入理解Flutter多线程

Flutter默认是单线程任务处理的,如果不开启新的线程,任务默认在主线程中处理。和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程叫做isolate。

Flutter1.5 开始,将成为全平台 UI 框架!

Flutter 1.5 的发布,同期也宣布发布 Flutter for Web 的 Preview 版本,正式开启了 Flutter 的全平台 UI 框架之路。早在年初发布的 Flutter 2019 Roadmap 中,就有提到,会在今年支持移动设备之外的平台,对 Web 的支持,算是完成了一个新的里程碑吧。

Flutter支持Web开发了!

Flutter作为一个可移植的UI框架,已经支持现代Web应用开发了!我们很开心已经发布了SDK预览版,这样你可以在Web浏览器里直接运行你的Flutter UI代码。

Flutter 混合开发 (交互通信)

Flutter 与原生之间的通信依赖灵活的消息传递方式:1,Flutter 部分通过平台通道将消息发送到其应用程序的所在的宿主环境(原生应用)。2,宿主环境通过监听平台通道,接收消息。

Flutter 局部路由实现

Flutter是借鉴React的开发思想实现的,在子组件的插槽上,React有this.props.children,Vue有<slot></slot>。当然Flutter也有类似的Widget,那就是Navigator,不过是以router的形式实现(像<router-view></router-view>)。

Flutter Kotlin 到底该如何选择?

这两个技术在当下如何选择,我之前在公众号上的回复是:如果你已经处于一个比较满意的公司,并考虑长期发展,公司并未使用这两个技术,你可以专心钻研公司当下使用的,或者未来将要使用的,这些才能助你在公司步步高升。

Flutter 与 iOS 原生 WebView 对比

本文对比的是 UIWebView、WKWebView、flutter_webview_plugin(在 iOS 中使用的是 WKWebView)的加载速度,内存使用情况。测试网页打开的速度,只需要获取 WebView 在开始加载网页和网页加载完成时的时间戳

Flutter For Web

用来构建漂亮、定制化应用的跨平台的 UI 框架 Flutter 现在已经支持 Web 开发了。我们很高兴推出了一个预览版的 SDK 可以让开发者直接使用 Flutter UI 和业务逻辑代码构建 web 应用

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!