iOS - Flutter 布局类组件-简介及原理

34 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情

简介

先看下布局类的继承类:

Widget说明用途
LeafRenderObjectWidget非容器类组件基类Widget树的叶子节点,用于没有子节点的widget,通常基础组件都属于这一类,如Image。
SingleChildRenderObjectWidget单子组件基类包含一个子Widget,如:ConstrainedBox、DecoratedBox等
MultiChildRenderObjectWidget多子组件基类包含多个子Widget,一般都有一个children参数,接受一个Widget数组。如Row、Column、Stack等

所有的布局类都是间接或直接继承SingleChildRenderObjectWidget和MultiChildRenderObjectWidget的Widget,都会有一个child或者children属性用于接收子的Widget。继承关系:

  • Widget>> RenderObjectWidget>>>(Leaf/SingleChild/MultiChild)RenderObjectWidget

RenderObjectWidget类中定义了创建、更新RenderObject的方法,子类必须实现它们,然后RenderObject对象进行布局计算来实现UI的最终布局和UI渲染。

原理

Flutter 中有两种布局模型

  • 基于RenderBox的盒模型布局
  • 基于Sliver(RenderSlivr)按需加载列表布局

两种布局方式在细节上略有差异,但是大体流程相同:

  • 上层组件向下层则见传递约束条件。
  • 下层组件确定自己的大小,然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。
  • 上层组件确定下层组件相对自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小) 盒模型布局组件的特点:
  • 组件对应的渲染对象都继承自RenderBox类。
  • 在布局过程中父级传递给子级的约束信息由BoxConstraints描述。

Box Constraints

BoxConstraints 是盒模型布局过程中父渲染对象传递给子渲染对象的约束信息,包含最大宽高信息,子组件大小需要在约束的范围内。

const BoxConstraints({
  this.minWidth = 0.0, //最小宽度
  this.maxWidth = double.infinity, //最大宽度
  this.minHeight = 0.0, //最小高度
  this.maxHeight = double.infinity //最大高度
})

这是基本属性还有一些其他属性,BoxConstraints.tight(Size)固定宽高;BoxConstraints.expand()可以生成一个尽可能大的用以填充另一个容器的BoxConstraints等等。

ConstrainedBox

用于对子组件添加额外约束:

ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity, //宽度尽可能大
    minHeight: 50.0 //最小高度为50像素
  ),
  child: Container(
    height: 5.0, 
    child: redBox ,
  ),
)

SizedBox

SizedBox用于给子元素指定固定的宽高

SizedBox(
  width: 80.0,
  height: 80.0,
  child: redBox
)
//等价于
ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  child: redBox, 
)

实际上ConstrainedBoxSizedBox都是通过RenderConstrainedBox来渲染的,我们可以看到ConstrainedBoxSizedBoxcreateRenderObject()方法都返回的是一个RenderConstrainedBox对象:

@override
RenderConstrainedBox createRenderObject(BuildContext context) {
  return RenderConstrainedBox(
    additionalConstraints: ...,
  );
}

多重限制

对于minWidthminHeight来说,是取父子中相应数值较大的。实际上,只有这样才能保证父限制与子限制不冲突。

UnconstrainedBox

虽然任何时候子组件都必须遵守其父组件的约束,但前提条件是它们必须是父子关系。 假如有一个组件 A,它的子组件是B,B 的子组件是 C,则 C 必须遵守 B 的约束,同时 B 必须遵守 A 的约束,但是 A 的约束不会直接约束到 C,除非B将A对它自己的约束透传给了C。 利用这个原理,就可以实现一个这样的 B 组件:

  1. B 组件中在布局 C 时不约束C(可以为无限大)。
  2. C 根据自身真实的空间占用来确定自身的大小。
  3. B 在遵守 A 的约束前提下结合子组件的大小确定自身大小。 而这个 B组件就是  UnconstrainedBox 组件,也就是说UnconstrainedBox的子组件将不再受到约束,大小完全取决于自己。一般情况下,我们会很少直接使用此组件,但在"去除"多重限制的时候也许会有帮助,我们看下下面的代码:
ConstrainedBox(
  constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),  //父
  child: UnconstrainedBox( //“去除”父级限制
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
      child: redBox,
    ),
  )
)

注意,UnconstrainedBox对父组件限制的“去除”并非是真正的去除:上面例子中虽然红色区域大小是90×20,但上方仍然有80的空白空间。也就是说父限制的minHeight(100.0)仍然是生效的,只不过它不影响最终子元素redBox的大小,但仍然还是占有相应的空间,可以认为此时的父ConstrainedBox是作用于子UnconstrainedBox上,而redBox只受子ConstrainedBox限制,这一点务必注意。

那么有什么方法可以彻底去除父ConstrainedBox的限制吗?答案是否定的!请牢记,任何时候子组件都必须遵守其父组件的约束,所以在此提示读者,在定义一个通用的组件时,如果要对子组件指定约束,那么一定要注意,因为一旦指定约束条件,子组件自身就不能违反约束