2、弹性伸缩布局:深入理解flexGrow、flexShrink、flexBasis属性,实现子组件按比例伸缩。
好,咱们接着聊弹性伸缩布局。
上一章我们讲了 Flex 的基本概念,说白了就是「怎么排」。这一章我们深入一点,聊聊「怎么变」。
你想想看,屏幕有大有小,内容有多有少。如果每个子组件都固定死宽度,那肯定不行。这时候就需要三个关键属性:flexGrow、flexShrink 和 flexBasis。我刚开始接触鸿蒙开发时,也经常搞混它们。后来踩了几个坑,才真正理解。
2.1 flexBasis:组件的「理想尺寸」
flexBasis 是基础尺寸。你可以把它理解为「在没有伸缩之前,我希望这个组件占多大」。它有点像 width,但优先级更高。
核心要点:
- 如果主轴是水平方向,
flexBasis控制宽度 - 如果主轴是垂直方向,
flexBasis控制高度 - 默认值是
auto,此时会参考组件的width或height
// 示例:设置 flexBasis
Flex({
direction: FlexDirection.Row
}) {
Text('A')
.flexBasis(100) // 理想宽度 100vp
.height(50)
.backgroundColor(Color.Red)
Text('B')
.flexBasis(200) // 理想宽度 200vp
.height(50)
.backgroundColor(Color.Green)
}
嗯,这里要注意:flexBasis 只是「理想值」。如果容器空间不够或者有富余,它就会被 flexGrow 和 flexShrink 调整。
2.2 flexGrow:空间多了,怎么分?
当所有子组件的 flexBasis 加起来,比容器小的时候,多余的空间怎么分配?flexGrow 说了算。
我在项目中遇到过这样一个场景:一个底部导航栏,三个按钮。我希望它们平分整个宽度。当时我直接给每个按钮设了 flexGrow(1),完美解决。
// 三个按钮平分空间
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceBetween
}) {
Text('首页')
.flexGrow(1) // 占 1 份
.height(50)
.backgroundColor(Color.Blue)
Text('发现')
.flexGrow(1) // 占 1 份
.height(50)
.backgroundColor(Color.Orange)
Text('我的')
.flexGrow(1) // 占 1 份
.height(50)
.backgroundColor(Color.Pink)
}
为什么会这样?因为 flexGrow 的值代表「权重」。三个都是 1,那就各拿三分之一。如果我把「首页」改成 flexGrow(2),那它就拿走一半,剩下两个各拿四分之一。
我的习惯:
如果想让某个组件「自适应填充剩余空间」,给它设 flexGrow(1) 就够了。其他组件保持默认 flexGrow(0),它们就不会变大。
2.3 flexShrink:空间不够,谁先缩?
反过来,当所有子组件的 flexBasis 加起来,比容器大的时候,就需要压缩了。flexShrink 决定谁先缩、缩多少。
默认情况下,每个组件的 flexShrink 都是 1。也就是说,大家按比例一起缩。但有时候我们不想让某个组件被压缩。
我曾经做过一个聊天列表,左边是头像,右边是昵称。昵称可以很长,但我不想让头像被压扁。解决办法就是给头像设 flexShrink(0)。
// 头像不压缩,昵称自适应
Flex({
direction: FlexDirection.Row
}) {
// 头像:固定大小,绝不压缩
Image($r('app.media.avatar'))
.width(40)
.height(40)
.flexShrink(0)
// 昵称:可以压缩
Text('这是一个非常非常长的用户昵称')
.flexShrink(1)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
注意:
flexShrink 的压缩比例是基于 flexBasis 的。如果一个组件 flexBasis 是 200,另一个是 100,且 flexShrink 都是 1,那么需要压缩 60vp 时,第一个组件会缩 40vp,第二个缩 20vp。说白了,基础大的缩得多。
2.4 三者配合:一个完整的例子
我们来看一个实际布局。一个卡片,左边是图标,中间是文字,右边是操作按钮。我希望:
- 图标固定大小,不伸缩
- 文字自适应,能撑就撑,能缩就缩
- 按钮固定大小,不伸缩
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center
}) {
// 图标:固定 40vp
Image($r('app.media.icon'))
.width(40)
.height(40)
.flexShrink(0)
.flexGrow(0)
// 文字:自适应
Text('这是一段可能会很长的描述文字')
.flexGrow(1)
.flexShrink(1)
.flexBasis(0) // 从 0 开始计算,完全由 grow/shrink 决定
// 按钮:固定宽度
Button('操作')
.width(80)
.flexShrink(0)
.flexGrow(0)
}
你看,这里我给文字设了 flexBasis(0)。这意味着它的「理想尺寸」是 0,然后通过 flexGrow(1) 去占满剩余空间。如果空间不够,它也会通过 flexShrink(1) 去压缩。而图标和按钮完全不受影响。
2.5 避坑指南
最后,分享几个我踩过的坑:
- flexBasis 和 width 同时设置? 以
flexBasis为准。我建议只用flexBasis,别混用,容易乱。 - flexGrow 和 flexShrink 同时为 0? 那这个组件就是「死尺寸」,不增不减。适合固定宽高的元素。
- 子组件内容溢出? 记得配合
maxLines和textOverflow,否则文字会撑破容器。
一句话总结:
flexBasis 定基调,flexGrow 管分配,flexShrink 管压缩。三者配合,就能实现各种弹性伸缩效果。
下一章,我们会聊聊更复杂的场景——嵌套 Flex 和交叉轴对齐。到时候你会发现,掌握了这三个属性,很多布局问题都能迎刃而解。