国内移动端网站做的最好的漂亮的设计类图片网站
国内移动端网站做的最好的,漂亮的设计类图片网站,p2p网站方案,wordpress get categories酒且盖孛各类XAML UI框架中#xff0c;Grid 是一种非常灵活且常用的布局控件#xff0c;它可以创建复杂的用户界面布局。Grid 允许开发者通过定义行和列来组织界面元素#xff0c;每个元素可以精确地放置在网格的特定区域内
本文以 Avalonia 框架为例#xff0c;讲解 Grid …酒且盖孛各类XAML UI框架中Grid 是一种非常灵活且常用的布局控件它可以创建复杂的用户界面布局。Grid 允许开发者通过定义行和列来组织界面元素每个元素可以精确地放置在网格的特定区域内本文以 Avalonia 框架为例讲解 Grid 控件的工作原理事实上UWP、WinUI 3 和 Avalonia 等XAML框架中 Grid 控件的源码全都源自于 WPF 中的实现以至于绝大部分代码乃至注释都是相同的因此本文中你能了解到的机制也可应用于其他 XAML UI 框架Measure 阶段快速路径当仅仅存在一个行/列(或未定义行列)时将子元素中最大的宽和高设置为为 Grid 的大小即可。gridDesiredSize new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));在一般情况下我们定义了两个及以上以上的行/列就需要一套特定的算法进行计算我们知道Grid的行/列定义有Auto、Star、Pixel三类简化后的定义如下public class Definition{public double MinSize { get; set; }// 对应MinHeight(MinWidth)以Pixel为单位public double MaxSize { get; set; }// 对应MaxHeight(MaxWidth)以Pixel为单位public GridLength Size { get; set; }// 对应Height(Width)对应不同单位下的值double MeasureSize { get; set; }// 测量阶段的结果double FinalSize { get; set; }// 排列阶段的结果double FinalOffset { get; set; }// 最终偏移量}此处将 Width/Height 更改为 Size不再区分行列源代码中为了性能上面的一些属性如FinalSize、MaxSize使用的是同一个字段为便于理解这里将他们拆分开来单独列出。具体代码可以自行前往Avalonia存储库查看在测量 (Measure) 的过程中Grid 遵循可确定大小优先的原则。因此他们将要渲染的元素分为四组来决定测量顺序Px Auto StarPx 1 1 3Auto 1 1 3Star 4 2 4通常情况下测量的顺序如下初始化分组测量Group1解析Star行测量Group2解析Star列测量Group3测量Group4根据 Group2、Group3 元素的存在情况测量组和解析 Star 项的顺序会有所不同但 Group1 和 Group4 的测量顺序始终不变具体流程将在后面介绍我们先依次介绍 Measure 过程中使用的主要算法ValidateDefinitionsStructure、ValidateDefinitionsLayout初始化行/列定义分组前Grid 会先初始化行/列定义的结构如果只有行定义Grid 会自动添加一个 Width * 的列定义同理如果只有列定义会自动添加一个行定义同时如果传入了无限的长或宽其对应的所有 Star 定义都会被视作 Auto 项ValidateCells分组预处理所有子元素分组过程中Grid 中的每个元素都将被视作一个 Cell他们根据所在行/列的类型被分配到不同组中以便稍后进行进一步的计算所有 Cell 以链表的形式被缓存每个 CellGroup 存储的是当前组的头节点在后续测量中使用 Cell.Next 进行遍历MeasureCellsGroup测量组Group 1项的类型为 Px/Auto测量每个元素的机制如下Px 项def.Size 作为元素 Measure 边界Auto 项传入无限尺寸作为元素 Measure 边界同时当某个元素测量完成时还会根据元素的高度更新元素所在项的最小大小项最小尺寸的初始值与最大尺寸由用户设定如RowDefinition.Min/MaxHeightdef.MinSize Math.Clamp(child.DesiredSize, def.MinSize, def.MaxSize)若元素的 RowSpan 1可能跨项则不更新项最小值并且在其他元素均测量完毕后确保这些跨项元素尺寸在其所在所有项尺寸的最小值/最大值之间Group ≠ 1元素在某个 Star 类型的项中根据顺序此时已完成目标组 Star 项的大小解析因此 definitions.MeasureSize 是有值的使用 GetMeasureSizeForRange 方法遍历元素跨越的所有项将这些项的最小值(Auto 项)或测量值(Star 项)相加得到元素测量时的边界private static double GetMeasureSizeForRange(IReadOnlyList definitions,int start/*起始项位置*/,int count/*跨越项数(RowSpan)*/,double spacing){double measureSize -spacing;int i start count - 1;do{measureSize spacing // 跨行元素可忽视所跨项之间的spacing(definitions[i].SizeType LayoutTimeSizeType.Auto ?definitions[i].MinSize :definitions[i].MeasureSize);} while (--i start);return measureSize;}同理每个元素测量完成时也会更新其所在项的最小值也会在所有元素测量完成后确保跨项元素尺寸在正确范围内ResolveStar统一解析Star项分配权重Group1 测量完毕后我们已经得到了所有固定元素的尺寸现在以以下流程计算各 Star 项的尺寸预处理权重遍历 definitions统计Star项的数量并得到最大权重 maxStar同时将所有Star项的 MeasureSize 设置为 1.0表示尚未在后续阶段解析。引入 scale 值用于后续缩放防止浮点溢出若存在 Infinity 权重设置 scale -1.0若不存在 Infinity 权重但权重和可能溢出(starCount * maxStar Double.Max)将 scale 设置为 2 的负次幂计算可用空间预处理约束再次遍历 definitions 计算所有非Star项所占用的空间 takenSize对于 Star 项计算缩放后的权重值 starWeight star * scale并累计到 totalStarWeight若 scale 0存在Infinity权重将 Infinity 项映射为 1其余映射为 0如果项存在最小约束(MinSize 0由用户设置)将该项加入最小约束列表并暂存比率 def.minRatio starWeight / def.MinSize同理若存在最大约束(MaxSize ≠ Infinity)将该项加入最大约束列表暂存 def.maxRatio starWeight / def.MaxSize为提升性能实际实现中最小约束列表和最大约束列表存储在同一列表 definitionIndices 中分别为前后两个部分迭代解析约束项来看一个例子假设可用尺寸 S400这时候我们可以发现如果要满足定义中的 Min/MaxWidth 约束是无法实现 1 : 2 : 3 的比例的。因此我们需要将与比例差距最大的项锁定为 Min/MaxWidth但我们又希望锁定后的比例尽可能接近定义的比例因此 Grid 使用了以下算法是否是否阶段3处理约束项按照 最小比率、最大比率 排序约束列表有剩余空间且存在未处理的约束项计算当前比例是否违反某个约束选择偏差最大的约束将该项尺寸锁定为此约束值更新剩余权重与剩余空间结束先对最小约束列表按照比率降序最大约束列表按照比率升序排序初始 remainingAvailableSizeS400remainingStarWeightW6下面按照每次锁定差异最大的一个 definition 来迭代解析约束项第一次迭代从最小/最大约束列表中分别选出 C1.minRatio 1/120 0.008333 C3.maxRatio 3/150 0.02计算比例 proportion remainingStarWeight / remainingAvailableSize 6/400 0.015判定是否违反比例违反最小约束minRatio proportion 0.008333 0.015成立违反最大约束maxRatio proportion 0.02 0.015成立如果认为比例不好理解可以从反面考虑假设步骤 2 中 def.MinSize / starWeight 代表每颗星的最小大小 minStarSizedef.MaxSize / starWeight 代表每颗星允许的最大大小maxStarSize那么 remainingAvailableSize / remainingStarWeight 代表当前每颗星的大小currentStarSize必须满足 minStarSize ≤ currentStarSize ≤ maxStarSize否则就需要选择偏离最大的项进行锁定二者都需要锁定选择偏离更大的一项对最小约束的偏离proportion / minRatio 0.015 / 0.008333 ≈ 1.8对最大约束的偏离maxRatio / proportion 0.02 / 0.015 ≈ 1.3331.8 1.333 优先锁定 C1 尺寸为 C1.MinSize执行锁定设置 C1 尺寸为约束最小值resolvedSize C1.MinSize 120更新可用空间和剩余权重remainingAvailableSize 400 - resolvedSize 400-120 280remainingStarWeight 6 - C3.starWeight 6 - 1 5第二次迭代maxRatio 0.02仍为 C3 proportion 5/280 ≈ 0.017857仅违反最大约束0.02 0.017857 C3 尺寸锁定为 C3.MaxSize执行锁定设置 C3 尺寸为约束最大值resolvedSize C3.MaxSize 150remainingAvailableSize 280 -150 130remainingStarWeight 5 - 3 2最小/最大约束列表均为空 退出。检测异常场景并重复执行 2、3完成步骤 3 后可能会有以下异常场景remainingAvailableSize 0 且 Star 项全部处理完毕所有 Star 项都被其最大约束锁定但所有最大约束之和仍小于边界遍历所有项解锁存在最小约束的项重新执行步骤 2、3 为其分配空间remainingAvailableSize 0某些项被最小约束锁定但所有最小约束之和超过了边界同理解锁存在最大约束的项重新执行 2、3前缀和算法分配剩余空间相比权重/权重总和*剩余空间此算法可以避免浮点数计算带来的精度丢失可以用一个例子说明此算法假设权重分别为 [1, 2, 3]剩余空间为 6计算权重前缀和 [1, 3, 6]现在以权重从大到小的顺序开始分配分配 6*(3/6) 3剩余空间 6-33分配 3*(2/3) 2剩余空间 3-21分配 1*(1/1) 1剩余空间 1-10完成简化后的算法如下此处默认所有项的类型为 Star Array.Sort(definitions);// 计算前缀和totalStarWeight 0.0;for (int i 0; i definitions.Length; i){DefinitionBase def definitions[i];totalStarWeight def.MeasureSize;// MeasureSize在此处代表Star数量def.SizeCache totalStarWeight;// SizeCache用于缓存前缀和结果}// 从后向前遍历for (int i definitions.Length - 1; i 0; --i){DefinitionBase def definitions[i];double resolvedSize (availableSize - takenSize) * (def.MeasureSize / def.SizeCache);def.MeasureSize resolvedSize;takenSize resolvedSize;}现在你应该对测量中使用的算法有了大概了解。但测量顺序具体是怎么样的呢以及为什么要遵守这样的测量顺序我们再看一下之前的分组表Px Auto StarPx 1 1 3Auto 1 1 3 [A]Star 4 2 [B] 4重点看加粗的部分以 A部分 (在 Group3 中)为例在测量这部分的元素前必须得到 Auto 列的大小。因此测量 Group3 前必须完成对 Group1 和 Group2的测量同样B 部分在测量之前也必须得到 Auto 行的大小在测量 Group2 前必须完成对 Group1 和 Group3 的测量AB 部分的共同特性就是同时存在于 Auto 和 Star 项中我们可以得出以下结论A 部分中存在元素测量 Group1、Group2 后才能测量 Group3B 部分中存在元素测量 Group1、Group3 后才能测量 Group2如果 A、B 部分都存在元素呢比如下面的情况column widthAuto column width*row heightAuto cell 1 2 [A]row height* cell 2 1 [B]由于 cell 2 1 的宽度决定了 Auto 列的宽度进而间接决定 cell 1 2 所在列的宽度需要先计算 cell 2 1。然而由于 cell 1 2 的高度决定了 Auto 行的高度同理 cell 2 1 也理应先被计算—— Auto 与 Star 的行列相互影响这就是 Grid 行列解析中的循环依赖问题。综上所述Grid 共有三种不同的测量顺序流程图如下循环依赖是(一般路径先解析Star行)否是(先解析Star列即可)否否是MeasureOverride分组ValidateCells测量Group1能否解析Star行A中没有元素解析Star行测量Group2解析Star列测量Group3能否解析Star列B中没有元素解析Star列测量Group3解析Star行测量Group2(使用无限高度)解析Star列测量Group3解析Star行测量Group2总宽未改变或循环次数≥5测量Group4结束循环依赖的处理对于产生了循环依赖的布局我们无法直接测量 AB 组的大小。因此我们需要先在测量 Group2也就是 B 组 时使用无限高度以先得到其宽度再解析 Star 列Star列被解析完毕后就可以测量 Group3 中的元素了此时再解析Star行即可重新以正确的行高测量 Group2Grid_1更为极端的情况如果 Group2 中的元素再使用不同高度测量时得到的宽度不同例如纵向排列的 WrapPanel 这代表第一次使用无限高度测量 Group2 后解析得到Star列的宽度是错误的这时候我们需要返回并重新对 Group3 进行测量...在重新测量的过程中我们还要将 Auto 项的尺寸恢复到测量 Group1 后的状态来尽可能保证测量 Group2 时的参数相等此流程会一直循环直到两次Group2宽度的测量结果相同或循环次数 ≥5Grid_2Arrange 阶段由于行列大小计算已经在 Measure 阶段完成Arrange 阶段只需要计算每个行列的最终位置并处理一些特殊设置即可。重新解析 Star 项由于 Measure 阶段提供的约束可能与 Arrange 阶段提供的空间不一致我们需要使用与 ResolveStar 相同的方式再次解析一遍 Star 项至于为何两个阶段提供的空间可能不一致就涉及到 Measure/Arrange 机制了具体可参阅 布局 - WPF | Microsoft LearnSpacing 处理在 Measure 阶段前我们在原边界大小的基础上减去所有间距之和后再进行测量对 spacing 的单独处理仅限于跨行元素见GetMeasureSizeForRange。因此在处理元素最终的偏移量 FinalOffset 时我们需要在每次迭代都加上对应的 spacingfor (int i 0; i definitions.Count; i){definitions[(i 1) % definitions.Count].FinalOffset definitions[i].FinalOffset definitions[i].FinalSize spacing;}布局舍入 (LayoutRounding)为了避免影响前面的测量进程布局舍入在 Arrange 的最后阶段执行遍历所有项将其尺寸根据当前 DPI 四舍五入并把舍入后大小 - 原始大小写入 RoundingErrors计算舍入后大小 roundedTakenSize 与舍入前大小 finalSize 的差值。若不相等就需要进一步调节各项以分配余额或去除溢出将所有项根据 RoundingErrors 排序在差值最大的项上增加或减少像素以 dpiIncrement 1 / dpi 为单位进行调节同时确保不低于 MinSize顺带一提代码中用了一段很长的注释来说明当前的布局舍入算法是存在问题且无法解决的由 Gemini 3 Pro 翻译// 理论和历史说明。当前的问题——即在带有星号权重*-weights、// 最小和最大约束以及布局舍入的情况下向列或行分配空间——由来已久。// 特别是那个著名的特例50列最小值为1可用空间为435——// 这正是根据人口比例向50个州分配美国众议院席位的问题。// 关于这一问题的算法和论文无数最早可追溯到1700年代包括这本书// Balinski, M. 和 H. Young 所著的《Fair Representation》公平代表耶鲁大学出版社纽黑文1982年。//// 所有这些研究得出的一个惊人结果是*任何*算法都难免会遭受一个或多个// 不良特性的困扰例如“人口悖论”或“阿拉巴马悖论”。// 在这种情况下套用我们的术语来说增加一个像素的可用空间实际上// 可能会减少分配给特定列的空间或者增加某列的权重反而可能会减少其分配量。// 这一点值得了解以防有人抱怨这种行为这与其说是一个 Bug// 不如说是问题本身固有的特性。引用上面提到的书或数百篇参考文献之一// 然后将其标记为“不予修复”WontFix。//// 幸运的是我们的场景通常只有少量的列约10列或更少// 每列分配大量的像素约50个或更多人们甚至注意不到// 理论上不可避免的那种1像素的偏差或者即使注意到了也不在乎。// 至少他们不应该在乎——没人应该使用 WPF 网格布局的结果来做// 定量决策它的工作是产生合理的显示效果而不是分配国会席位。//// 我们的算法比当前用于国会席位分配的算法“亨廷顿-希尔”算法// 更容易出现悖论但它的运行速度更快// 复杂度为 O(N log N) vs. O(S * N)其中 N定义的数量S可用像素的数量。// 并且如上所述它在实践中产生了足够好的结果。//// 重申一点所有这些仅在启用布局舍入时适用。// 当允许小数尺寸时该算法在受限于最小/最大约束和// 浮点计算精度的情况下表现尽可能良好。// 但是最终的显示结果会受到抗锯齿问题的影响。天下没有免费的午餐 [TANSTAAFL]。总结通过了解 Grid 的有关算法我们可以得到一些结论Grid 的工作流程相当复杂。尽管存在快速分支仍然会进行很多不必要的判断与计算因此对于不需要定义行列的简单多元素布局需求我们应尽量使用其他控件代替如 Avalonia 中的 Panel 控件尽量不要将非固定大小元素如 WrapPanel同时放在 Star 和 Auto 项中这可能导致循环依赖并多次对元素进行测量。而对于 WrapPanel 这类列表控件测量的开支是非常大的Grid 仍然存在一些未定义的行为参见 WPF/UWP 的 Grid 布局竟然有 Bug还不止一个了解 Grid 中那些未定义的布局规则 - walterlv例如这篇文章中提到的第一个问题就是在 ValidateDefinitionsLayout 的过程中 Star 项被视作 Auto 项导致的但官方文档并没有进行明确的记载如果有时间我可能会再写一篇文章来单独说明这些未定义布局规则的原因