Flutter 玩转自定义布局之单组件布局
“everything is widget !”
似乎当我们刚接触Flutter的时候,便常常听过这句话.
那么,除了官方提供的widget外.
你有没有尝试过自己去实现一个自定义布局的widget呢?
如果没有的话~不妨跟着本文一起动手写个自定义的布局吧 !
介绍
自定义单组件布局类似于Center、Sizebox、Align这些组件, 它们都有一些共同的特性.
比如说: 只有一个child并且是继承于SingleChildRenderObjectWidget. 我们称这样的widget为 布局类的widget , 理由是它们可以去 定位子组件相对位置, 也可以给子组件布局限制.并且还可以对画布做一些操作.
实际上,Flutter本身也提供了一个方便我们进行布局的一个组件.这也就是我们今天的主题 CustomSingleChildLayout.
CustomSingleChildLayout
说起 CustomSingleChildLayout,它可以控制子组件在内部约束中随意定位. 实际上在大部分场景中, 笔者认为都可以通过 Stack+Positioned|Align的方式去替代.那么, CustomSingleChildLayout的优势又在哪里呢? 我们来分析分析.
创建
首先, 我们先创建一个 CustomSingleChildLayout(Key? key,required this.delegate,Widget? child,). child 也就是绘制的子组件, 而 delegate 是负责定位、布局的一种代理.
1 | class _DemoSingleDelegate extends SingleChildLayoutDelegate { |
我们创建一个类继承该类,它默认需要实现一个 shouldRelayout 的方法.这个方法是用来干嘛的呢? 在父类中(即SingleChildLayoutDelegate), 它的构造方法有个 (Listenable? relayout) 的参数. 这个参数需要你传入一个继承于 Listenable 的对象.
它的作用是:这里我们用继承于 ChangeNotifier的类来举个列子,当它调用 notifyListeners() 的方法时.便会去刷新当前的布局, 这样便可以最小化的刷新布局,这也就是CustomSingleChildLayout的优势之一.
深入理解
我们除了知道它可以 最小刷新范围外 ,它可以提供了一些方法.在看这些方法之前.我们先创建一个100*100的色块, 看看默认的它是什么表现?
1 | Widget build(BuildContext context) { |
效果
可以看到确实在屏幕中绘制出了一个100*100的青色色块, 但是我们仔细看一下源码.你会发现似乎又有些不对~ 嗯? 为什么加了 Center 却没有居中呢?
实际上, 如果你对flutter的布局原理有过研究, 这个问题就很容易得出来了. CustomSingleChildLayout 并没有直接受外部的大小的影响,也就是说能多大就多大 ,因此绘制的色块实际上并没有受 Center 的限制. 自然也就布局在了左上角.
我们模拟一下它整体的流程, Scaffold 告诉 Center 没有约束, Center 告诉它的小弟 CustomSingleChildLayout 也没有约束能多大就多大, CustomSingleChildLayout 也把这句话传给了 SizedBox . SizedBox 收到消息后回馈给了 CustomSingleChildLayout ,告诉它:我只需要100*100就可以啦~ 然后, CustomSingleChildLayout 也是层层传递给 Scaffold .最后Scaffold再慢慢安排它小弟的位置,一层层往下安排.自己负责自己的小弟. 于是乎,界面就成了上面的这个样子.
getSize
那么,有没有方法, 可以让它居中呢?
当然有,而且还有很多.我们这次只介绍最符合主题的. SingleChildLayoutDelegate 中有一个 getSize(BoxConstraints constraints) 的方法. 我们知道 SizedBox 实际上受 CustomSingleChildLayout 的约束,
1 | @override |
神奇的事情发生了~色块居中了. 这里同样走的上方的逻辑, 我们给了它相应的限制之后. Center 发现 CustomSingleChildLayout 只需要100*100的大小, 大手一挥.那你就在中间待着吧! 自然而然,同大小的色块也就随着布局居中了.
getConstraintsForChild
在 SingleChildLayoutDelegate 中,还有一个 getConstraintsForChild(BoxConstraints constraints) 的方法.它是干嘛的呢? 它是用于约束子组件的大小. 比如说它目前有100*100的大小, 但是我只愿意分50*50的区域给子组件. 这个时候 getConstraintsForChild 就派上了用场
1 | @override |
我们将约束的最大宽高设置为50, 子组件又有什么样的表现呢?
现在的SizeBox只有原来的四分之一的大小了, 但是它原本有100*100啊? 这是为什么呢?原因是 CustomSingleChildLayout 说: 我只给你50*50的大小, SizeBox 说:不行啊老大,我需要100*100. CustomSingleChildLayout 说: 我是老大,我说的算. 是的,所以子组件是受父组件约束的.
getPositionForChild
我们在将子组件约束到50*50后, 细心到小伙伴可能发现了, 它已经不在居中于屏幕了.
那么,有没有办法可以让子组件再次居中于屏幕呢? 答案是 getPositionForChild(Size size, Size childSize). 它可以改变子组件相对父组件作位移, 我们分析下为什么改变大小后会做偏移? 原来子组件是100*100, 我们把它放置在100*100的父组件中,刚刚好.那么现在子组件改成50*50, 也就是说. 我们如果要把它居中,就要把它往下和右方分别平移25. 我们写代码尝试一下.
1 | @override |
结果:
不出所料,果然又再次居中了.
总结
CustomSingleChildLayout 的优势在于
- 可以最小刷新
- 可以控制自身需要的size
- 可以控制子组件的约束
- 可以控制子组件定位
源码
github.com/weniner/flutter_demo/
结语
这里是WeninerIo,热爱生活且热爱旅行.如果你对这次的分享感兴趣又或者有什么疑惑, 不妨评论区留言 + 关注.期待下一次更好的相遇.