news 2026/5/21 2:16:19

深入理解 Chromium Views 布局系统 —— LayoutManagerBase 的两种 GetPreferredSize 重载

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解 Chromium Views 布局系统 —— LayoutManagerBase 的两种 GetPreferredSize 重载

背景:一个诡异的 UI Bug

在一次 Chromium 定制开发中,我们遇到了一个极其诡异的 Bug:收藏按钮(StarView)在特定标签页切换操作后消失,但其占位空间仍然存在。按钮不可见,但标题栏的宽度已经为它预留了空间。

更奇怪的是,这个问题只在打开 5 个以上标签页时才能复现,4 个标签页完全正常。

排查这个 Bug 的过程,让我们深入理解了 Chromium Views 布局系统中一个非常微妙但极其重要的设计:LayoutManagerBase的两种GetPreferredSize重载


LayoutManagerBase的两种GetPreferredSize

ui/views/layout/layout_manager_base.cc中,有两个同名但行为截然不同的方法:

// 重载一:无约束版本 gfx::Size LayoutManagerBase::GetPreferredSize(const View* host) const { DCHECK_EQ(host_view_, host); if (!cached_preferred_size_) cached_preferred_size_ = CalculateProposedLayout(SizeBounds()).host_size; return *cached_preferred_size_; } // 重载二:有约束版本 gfx::Size LayoutManagerBase::GetPreferredSize( const View* host, const SizeBounds& available_size) const { DCHECK_EQ(host_view_, host); if (available_size.width().is_bounded()) { return CalculateProposedLayout(available_size).host_size; // 重新计算! } return GetPreferredSize(host); // 使用缓存 }

关键差异

  • 无约束版:使用cached_preferred_size_缓存,只在缓存失效时重新计算,且传入SizeBounds()(无约束)
  • 有约束版:当available_size.width().is_bounded()时,每次都重新调用CalculateProposedLayout(available_size),将约束传入布局算法

SizeBounds的设计哲学

SizeBounds是 Chromium Views 中表示"可用空间约束"的类:

// 无约束:两个维度都是 nullopt SizeBounds() // 有约束:明确指定可用宽度和高度 SizeBounds(int width, int height)

设计初衷:在 Chromium 的布局系统中,视图的 preferred size 有时依赖于可用空间(比如文本换行、弹性布局的 snap-to-zero)。SizeBounds提供了一种机制,让父视图在测量子视图时传递"我能给你多少空间"的信息。

这类似于 Android 的MeasureSpec,但更简洁:只有"有约束"和"无约束"两种状态(通过std::optional实现)。


约束如何向下传递

LayoutPass1调用view->GetPreferredSize(SizeBounds(available, height))时,调用链如下:

View::GetPreferredSize(SizeBounds) → View::CalculatePreferredSize(SizeBounds) → GetLayoutManager()->GetPreferredSize(this, available_size) // 当 layout_manager_use_constrained_space_ = true(默认)时传递约束 → LayoutManagerBase::GetPreferredSize(host, available_size) → CalculateProposedLayout(available_size) // 触发完整布局计算

注意view.cc:2622中的关键开关:

gfx::Size View::CalculatePreferredSize(const SizeBounds& available_size) const { if (HasLayoutManager()) { return GetLayoutManager()->GetPreferredSize( this, layout_manager_use_constrained_space_ ? available_size : SizeBounds()); // ↑ 默认 true,约束向下传递 } return gfx::Size(); }

layout_manager_use_constrained_space_默认为true,意味着约束会一路向下传递到所有子视图的布局计算中。


实际布局(LayoutImpl)与测量(GetPreferredSize)的差异

这里有一个容易忽视的细节:实际布局时始终使用有约束的SizeBounds

// layout_manager_base.cc void LayoutManagerBase::LayoutImpl() { // 使用视图的实际尺寸作为约束 auto proposed_layout = GetProposedLayout(host_view_->size()); // ... } ProposedLayout LayoutManagerBase::GetProposedLayout(const gfx::Size& host_size) const { return CalculateProposedLayout(SizeBounds(host_size)); // 始终有约束 }

这意味着:

  • 测量阶段GetPreferredSize):可以是有约束或无约束
  • 布局阶段LayoutImpl):始终是有约束的(以视图实际尺寸为约束)

踩坑实践:两种路径的行为差异

在我们的 Bug 中,LocationBarLayout::LayoutPass1中有这样的代码:

void LocationBarLayout::LayoutPass1(int* entry_width, int reserved_width) { for (const auto& decoration : decorations_) { if (!decoration->auto_collapse && (decoration->max_fraction == 0.0)) { const auto available_size = some_feature_flag_enabled ? views::SizeBounds(*entry_width - reserved_width, decoration->height) // 有约束路径 : views::SizeBounds(); // 无约束路径 decoration->computed_width = decoration->view->GetPreferredSize(available_size).width(); *entry_width -= decoration->computed_width; } } }

有约束路径GetPreferredSize(SizeBounds(available, height))→ 触发 FlexLayout 的 snap-to-zero → 可能返回 0 →computed_width = 0→ 按钮宽度为 0 但SetVisible(true)仍被调用 →"占位空间在但按钮不可见"

无约束路径GetPreferredSize(SizeBounds())→ 返回缓存的 preferred size → 正常宽度 → 按钮正常显示


设计启示

  1. GetPreferredSize(SizeBounds)不是简单的查询:它可能触发完整的布局重计算,有性能开销
  2. 约束传递是双向的:父视图的约束会影响子视图的 preferred size 计算结果
  3. 缓存失效时机:无约束版本使用缓存,有约束版本不使用缓存,这在高频调用场景下有显著性能差异
  4. layout_manager_use_constrained_space_:这个开关提供了一个"防火墙",可以阻止约束向下传递,在某些场景下可以用来规避 snap-to-zero 问题
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/21 2:14:45

我真正用于波段交易的7个提示词

当我刚开始做波段交易的时候,我以为成功会来自于找到那个"完美的指标"。 我花了无数个小时看YouTube视频,在交易推特上刷屏,从Reddit下载各种随机策略。每个博主都听起来信心满满,每个策略在图表上都看起来稳赚不赔。 但当我真正开始交易的时候,我却一直在做出情绪…

作者头像 李华
网站建设 2026/5/21 2:13:33

2026青岛楼宇自控厂家/能耗监测系统厂家品牌测评 不同维度优选实力商家!最佳选型指南!

在 “双碳” 目标深化落地、青岛智慧城市建设加速推进的背景下,楼宇自控系统已成为商业综合体、产业园区、政务建筑、医院学校等场景实现节能降碳、智能管控的核心支撑。青岛楼宇自控市场形成了国际品牌主导高端、本土品牌崛起领跑、区域集成商补充的格局&#xff0…

作者头像 李华
网站建设 2026/5/21 2:08:46

别再死磕标注数据了!用扩散模型从海量无标签遥感图像中‘白嫖’语义信息,提升变化检测精度

扩散模型在遥感变化检测中的无监督语义挖掘实战 遥感图像变化检测一直是地理信息科学和计算机视觉交叉领域的重要课题。传统方法高度依赖大量精确标注的训练数据,而标注成本高昂、周期漫长,成为制约算法性能提升的瓶颈。2022年涌现的多项突破性研究证明&…

作者头像 李华
网站建设 2026/5/21 2:08:42

保姆级教程:用G2O搞定视觉SLAM中的BA优化(附ORB-SLAM实战代码片段)

从零构建视觉SLAM后端优化:G2O在BA中的工程实践与ORB-SLAM代码解析 当我们在视觉SLAM系统中完成前端特征提取与帧间匹配后,真正的挑战才刚刚开始——如何将这些带有噪声的观测数据转化为精确的位姿与地图?这正是Bundle Adjustment&#xff08…

作者头像 李华
网站建设 2026/5/21 2:06:47

RHCE第四次作业

查看系统中已使用内存的比例 如果 大于50% 报警 如果小于 50% 则报安全编写脚本:vim work.sh1 mem_total$(free | grep Mem | awk {print $2})2 mem_used$(free | grep Mem | awk {print $3})3 4 mem_percent$(awk -v used"$mem_used" -v total"$mem_total"…

作者头像 李华