写在开始
此篇文章假定读者已经知道LazyVGrid+LazyHGrid的基本用法,以LazyVGrid为例,在开始之前在明确下Grid渲染列的几种类型:
- fixed: 列会拥有固定的宽度,空间的大小不会对其宽度造成影响,显示一列
- flexible: 可以指定最小值和最大值,空间的大小会对其造成影响,但是最终显示的宽度一定是在范围内,不会无限缩小或者放大,显示一列
- adaptive: 可以指定最小值和最大值,空间的大小会对其造成影响,显示尽可能多的列
注:不设置间距-默认间距为8
直接举栗子
fixed
[GridItem(.fixed(60))]
结论:不在意容器宽度,使用固定宽度, 渲染宽度为60
fixed+fixed
[
GridItem(.fixed(60)),
GridItem(.fixed(100))
]
结论:不在意容器宽度,使用固定宽度, 渲染宽度为60 + 8 + 100
flexible
[GridItem(.flexible(minimum: 100, maximum: 200))]
结论:根据容器宽度变化, 渲染宽度为max(min(容器宽度, 200), 100)
flexible+flexible
GridItem(.flexible(minimum: 100)),
GridItem(.flexible(minimum: 180))
事情开始变得有趣起来了,你可能刚开始猜的效果,应该是100+8+180,但是显示结果却完全不同。接下来让我们通过计算来看看这个布局过程。
1、建议宽度为 300
, 减去间距后宽度为 300 - 8 = 292
所以每一列的建议宽度为 292 / 2 = 146
, 比对第一列,最小值为100
,所以第一列直接使用146
作为建议宽度,剩余宽度为146, 比对第二列,最小值为180
,选择 180
作为建议宽度, 最终的计算宽度为 146 + 8 + 180 = 334
2、开始渲染,(334 - 8) / 2 = 163
, 第一列够了,渲染宽度为163
,第二列不够,直接使用180
,所以最终渲染效果为 163 + 8 + 180
注:显示不居中,第一步结束此时居中效果为 (326 + 8 -300) / 2 = 17
, 第二不绘制时多余 351 - 300 - 17 = 34
,所以最终显示效果为 左侧超出 17, 右侧超出 34
然后你信心满满以为自己已经天下无敌了,但是现实狠狠的给你了一个大逼兜子。还是[Flexible+Flexible]
GridItem(.flexible(minimum: 180)),
GridItem(.flexible(minimum: 100))
别怀疑人生,其实还是原来的配方,还是原来的味道,让我们开始
1、建议宽度为 300
, 减去间距后宽度为 300-8=292
, 所以每一列的建议宽度为 292/ 2 = 146
, 比对第一列,最小值为180
,所以第一列直接占用 180
,剩余宽度为 292 - 180 = 112
,然后比对第二列,最小值为100
,所以第二列可以占用112
,最终计算结果为 180 + 8 + 112 = 300
2、以300
开始渲染,步骤同1
flexible + fixed + flexible
GridItem(.flexible(minimum: 180)),
GridItem(.fixed(minimum: 100)),
GridItem(.flexible(minimum: 100))
1、起始宽度
300
, 减去固定宽度和间距之后为300 - 100 - 8 - 8 = 184
, 减去固定列之后,剩余两列, 所以第一列的建议宽度为 184 / 2 = 92
, 比对第一列,最小值为 180
,所以第一列直接占用 180
,剩余宽度为 184 - 180 = 4
, 比对第二列,最小值100
, 所以第二列占用 100
,最终计算结果为 180 + 8 + 100 + 8 + 100 = 396
2、渲染以396
开始,减去固定宽度和间距之后为396 - 100 - 8 - 8 = 280
,减去固定列之后,剩余两列,所以第一列的渲染宽度为280 / 2 = 140
, 比对第一列,最小值为 180
, 所以第一列占用 180
,剩余宽度为 280 - 180 = 100
, 比对第二列,最小值为 100
,所以 第二列占用 100
,进行渲染,渲染宽度为 180 + 8 + 100 + 8 + 100 = 396
注: 渲染器起始和最终渲染宽度一致,显示效果为居中显示
GridItem(.flexible(minimum: 100)),
GridItem(.fixed(minimum: 100)),
GridItem(.flexible(minimum: 180))
1、起始宽度为300
,减去固定宽度和间距之后为300 - 100 - 8 - 8 = 184
, 减去固定列之后,剩余两列,所以第一列的建议宽度为 184 / 2 = 92
, 比对第一列,最小值100
,所以使用100
,剩余宽度为184 - 100 = 84
,比对第二列,最小值 180
, 所以第二列占用 180
,最终计算结果为 100 + 8 + 100 + 8 + 180 = 396
2、渲染以396
开始,减去固定宽度和间距之后为396 - 100 - 8 - 8 = 280
,减去固定列之后,剩余两列,以第一列的渲染宽度为280 / 2 = 140
, 比对第一列,最小值为 100
, 所以第一列占用 140
, 剩余宽度为 280 - 140 = 140
,比对第二列,最小值为180
, 所以第二列占用 180
, 最终渲染宽度为 140 + 8 + 100 + 8 + 180 = 436
注:起始396作为起始计算渲染,起始居中 布局为 48 - 300 - 48
, 起始位置固定, 渲染宽度为436
,最终渲染结果为 48 - 300 - 88
结论 - 渲染宽度 = f(f(建议宽度))
步骤一:计算
1、从 建议宽度 开始
2、减去固定宽度列的宽度,以及列之间的间距,计算出剩余宽度
3、遍历每列:
flexible: 根据剩余宽度和剩余列数的平均值,以及当前列的限制,确定列的宽度,之后 从剩余宽度中减去列的宽度
fixed: 直接取值
adaptive: 下一章 - 待定
4、最终得到渲染的起始宽度
步骤二:渲染
1、从 步骤一的 结果开始
2 - 4 重复步骤一
5、得到最终的渲染结果
按照结论步骤,简单写一下这个过程的计算代码
extension View {
func draw(_ items: [GridItem], contentWidth: CGFloat) -> [CGFloat] {
let result1 = calculate(items, contentWidth: contentWidth).reduce(into: 0) { $0 += $1 }
let spacings = items.dropLast().reduce(into: 0) { $0 += ($1.spacing ?? 8) }
return calculate(items, contentWidth: result1 + spacings)
}
func calculate(_ items: [GridItem], contentWidth: CGFloat) -> [CGFloat] {
var result = Array<CGFloat>(repeating: 0, count: items.count)
var dynamicWidth = contentWidth
var dynamicCount = items.count
/// 减去固定宽度列的宽度,以及列之间的间距
for (index, item) in items.enumerated() {
/// 最后一行间距不计算
if index != items.count - 1 {
dynamicWidth -= (item.spacing ?? 8)
}
if case .fixed(let width) = item.size {
dynamicCount -= 1
dynamicWidth -= width
}
}
if dynamicCount > 0 {
/// 遍历所有列
for (index, item) in items.enumerated() {
switch item.size {
case let .flexible(minimum, maximum):
let singleWidth = dynamicWidth / CGFloat(dynamicCount)
let width = min(maximum, max(minimum, singleWidth))
result[index] = width
dynamicCount -= 1
dynamicWidth -= width
case .fixed(let width):
result[index] = width
case .adaptive:
/// 此处下一章补全
continue
@unknown default:
continue
}
}
}
return result
}
}
配套Demo