本文主要介紹Flutter佈局中的Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth四種控制元件,詳細介紹了其佈局行為以及使用場景,並對原始碼進行了分析。
1. Baseline
A widget that positions its child according to the child's baseline.
1.1 簡介
Baseline這個控制元件,做過移動端開發的都會了解過,一般文字排版的時候,可能會用到它。它的作用很簡單,根據child的baseline,來調整child的位置。例如兩個字號不一樣的文字,希望底部在一條水平線上,就可以使用這個控制元件,是一個非常基礎的控制元件。
關於字元的Baseline,可以看下下面這張圖,這具體就涉及到了字型排版,感興趣的同學可以自行了解。
1.2 佈局行為
Baseline控制元件佈局行為分為兩種情況:
- 如果child有baseline,則根據child的baseline屬性,調整child的位置;
- 如果child沒有baseline,則根據child的bottom,來調整child的位置。
1.3 繼承關係
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Baseline
複製程式碼
1.4 示例程式碼
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Text(
'TjTjTj',
style: new TextStyle(
fontSize: 20.0,
textBaseline: TextBaseline.alphabetic,
),
),
),
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Container(
width: 30.0,
height: 30.0,
color: Colors.red,
),
),
new Baseline(
baseline: 50.0,
baselineType: TextBaseline.alphabetic,
child: new Text(
'RyRyRy',
style: new TextStyle(
fontSize: 35.0,
textBaseline: TextBaseline.alphabetic,
),
),
),
],
)
複製程式碼
上述執行結果是左右兩個文字跟中間的Container底部在一個水平線上,這也印證了Baseline的佈局行為。
1.5 原始碼解析
const Baseline({
Key key,
@required this.baseline,
@required this.baselineType,
Widget child
})
複製程式碼
1.5.1 屬性解析
baseline:baseline數值,必須要有,從頂部算。
baselineType:bseline型別,也是必須要有的,目前有兩種型別:
- alphabetic:對齊字元底部的水平線;
- ideographic:對齊表意字元的水平線。
1.5.2 原始碼
我們來看看原始碼中具體計算尺寸的這段程式碼
child.layout(constraints.loosen(), parentUsesSize: true);
final double childBaseline = child.getDistanceToBaseline(baselineType);
final double actualBaseline = baseline;
final double top = actualBaseline - childBaseline;
final BoxParentData childParentData = child.parentData;
childParentData.offset = new Offset(0.0, top);
final Size childSize = child.size;
size = constraints.constrain(new Size(childSize.width, top + childSize.height));
複製程式碼
getDistanceToBaseline這個函式是獲取baseline數值的,存在的話,就取這個值,不存在的話,則取其高度。
整體的計算過程:
- 獲取child的 baseline 值;
- 計算出top值,其為 baseline - childBaseline,這個值有可能為負數;
- 計算出Baseline控制元件尺寸,寬度為child的,高度則為 top + childSize.height。
1.6 使用場景
跟字元對齊相關的會用到,其他場景暫時沒有想到。
2. FractionallySizedBox
A widget that sizes its child to a fraction of the total available space
2.1 簡介
FractionallySizedBox控制元件會根據現有空間,來調整child的尺寸,所以說child就算設定了具體的尺寸數值,也不起作用。
2.2 佈局行為
FractionallySizedBox的佈局行為主要跟它的寬高因子兩個引數有關,當引數為null或者有具體數值的時候,佈局表現不一樣。當然,還有一個輔助引數alignment,作為對齊方式進行佈局。
- 當設定了具體的寬高因子,具體的寬高則根據現有空間寬高 * 因子,有可能會超出父控制元件的範圍,當寬高因子大於1的時候;
- 當沒有設定寬高因子,則填滿可用區域;
2.3 繼承關係
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > FractionallySizedBox
複製程式碼
2.4 示例程式碼
new Container(
color: Colors.blue,
height: 150.0,
width: 150.0,
padding: const EdgeInsets.all(10.0),
child: new FractionallySizedBox(
alignment: Alignment.topLeft,
widthFactor: 1.5,
heightFactor: 0.5,
child: new Container(
color: Colors.red,
),
),
)
複製程式碼
執行效果如下所示
2.5 原始碼解析
const FractionallySizedBox({
Key key,
this.alignment = Alignment.center,
this.widthFactor,
this.heightFactor,
Widget child,
})
複製程式碼
2.5.1 屬性解析
alignment:對齊方式,不能為null。
widthFactor:寬度因子,跟之前介紹的控制元件類似,寬度乘以這個值,就是最後的寬度。
heightFactor:高度因子,用作計算最後實際高度的。
其中widthFactor和heightFactor都有一個規則
- 如果不為null,那麼實際的最大寬高度則為child的寬高乘以這個因子;
- 如果為null,那麼child的寬高則會盡量充滿整個區域。
2.5.2 原始碼
FractionallySizedBox內部具體渲染是由RenderFractionallySizedOverflowBox來實現的,通過命名就可以看出,這個控制元件可能會Overflow。
我們直接看實際計算尺寸的程式碼
double minWidth = constraints.minWidth;
double maxWidth = constraints.maxWidth;
if (_widthFactor != null) {
final double width = maxWidth * _widthFactor;
minWidth = width;
maxWidth = width;
}
double minHeight = constraints.minHeight;
double maxHeight = constraints.maxHeight;
if (_heightFactor != null) {
final double height = maxHeight * _heightFactor;
minHeight = height;
maxHeight = height;
}
複製程式碼
原始碼中,根據寬高因子是否存在,來進行相對應的尺寸計算。這個過程非常簡單,不再贅述。
2.6 使用場景
當需要在一個區域裡面取百分比尺寸的時候,可以使用這個,比方說,高度40%寬度70%的區域。當然,AspectRatio也可以達到近似的效果。
3. IntrinsicHeight
A widget that sizes its child to the child's intrinsic height.
3.1 簡介
IntrinsicHeight的作用是調整child到固定的高度。這個控制元件筆者也是看了很久,不知道它的作用是什麼,官方說這個很有用,但是應該儘量少用,因為其效率問題。
3.2 佈局行為
這個控制元件的作用,是將可能高度不受限制的child,調整到一個合適並且合理的尺寸。
3.3 繼承關係
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > IntrinsicHeight
複製程式碼
3.4 示例程式碼
new IntrinsicHeight(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Container(color: Colors.blue, width: 100.0),
new Container(color: Colors.red, width: 50.0,height: 50.0,),
new Container(color: Colors.yellow, width: 150.0),
],
),
);
複製程式碼
當沒有IntrinsicHeight包裹著,可以看到,第一三個Container高度是不受限制的,當外層套一個IntrinsicHeight,第一三個Container高度就調整到第二個一樣的高度。
3.5 原始碼解析
建構函式如下:
const IntrinsicHeight({ Key key, Widget child })
複製程式碼
3.5.1 屬性解析
除了child,沒有提供額外的屬性。
3.5.2 原始碼
當child不為null的時候,具體的佈局程式碼如下:
BoxConstraints childConstraints = constraints;
if (!childConstraints.hasTightHeight) {
final double height = child.getMaxIntrinsicHeight(childConstraints.maxWidth);
assert(height.isFinite);
childConstraints = childConstraints.tighten(height: height);
}
child.layout(childConstraints, parentUsesSize: true);
size = child.size;
複製程式碼
首先會檢測是否只有一個高度值滿足約束條件,如果不是的話,則返回一個最小的高度。然後調整尺寸。
3.6 使用場景
說老實話,不知道在什麼場景使用,可以替代的控制元件也有的。谷歌說很有用,效率會有問題,建議一般的就別用了。
4. IntrinsicWidth
A widget that sizes its child to the child's intrinsic width.
4.1 簡介
IntrinsicWidth從描述看,跟IntrinsicHeight類似,一個是調整高度,一個是調整寬度。同樣是會存在效率問題,能別使用就儘量別使用。
4.2 佈局行為
IntrinsicWidth不同於IntrinsicHeight,它包含了額外的兩個引數,stepHeight以及stepWidth。而IntrinsicWidth的佈局行為跟這兩個引數相關。
- 當stepWidth不是null的時候,child的寬度將會是stepWidth的倍數,當stepWidth值比child最小寬度小的時候,這個值不起作用;
- 當stepWidth為null的時候,child的寬度是child的最小寬度;
- 當stepHeight不為null的時候,效果跟stepWidth相同;
- 當stepHeight為null的時候,高度取最大高度。
4.3 繼承關係
Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > IntrinsicWidth
複製程式碼
4.4 示例程式碼
new Container(
color: Colors.green,
padding: const EdgeInsets.all(5.0),
child: new IntrinsicWidth(
stepHeight: 450.0,
stepWidth: 300.0,
child: new Column(
children: <Widget>[
new Container(color: Colors.blue, height: 100.0),
new Container(color: Colors.red, width: 150.0, height: 100.0),
new Container(color: Colors.yellow, height: 150.0,),
],
),
),
)
複製程式碼
分別對stepWidth以及stepHeight設定不同的值,可以看到不同的效果,當step值比最小寬高小的時候,這個值其實是不起作用的。感興趣的同學可以自己試試。
4.5 原始碼解析
建構函式
const IntrinsicWidth({ Key key, this.stepWidth, this.stepHeight, Widget child })
複製程式碼
4.5.1 屬性解析
stepWidth:可以為null,效果參看上面所說的佈局行為。
stepHeight:可以為null,效果參看上面所說的佈局行為。
4.5.2 原始碼
我們先來看看佈局程式碼中_applyStep函式
static double _applyStep(double input, double step) {
assert(input.isFinite);
if (step == null)
return input;
return (input / step).ceil() * step;
}
複製程式碼
如果存在step數值的話,則會是step的倍數,如果step為null,則返回原始的尺寸。
接下來我們看看child不為null時候的佈局程式碼
BoxConstraints childConstraints = constraints;
if (!childConstraints.hasTightWidth) {
final double width = child.getMaxIntrinsicWidth(childConstraints.maxHeight);
assert(width.isFinite);
childConstraints = childConstraints.tighten(width: _applyStep(width, _stepWidth));
}
if (_stepHeight != null) {
final double height = child.getMaxIntrinsicHeight(childConstraints.maxWidth);
assert(height.isFinite);
childConstraints = childConstraints.tighten(height: _applyStep(height, _stepHeight));
}
child.layout(childConstraints, parentUsesSize: true);
size = child.size;
複製程式碼
寬度方面的佈局跟IntrinsicHeight高度部分相似,只是多了一個step的額外數值。總體的佈局表現跟上面分析的佈局行為一致,根據step值是否是null來進行判斷,但是注意其對待高度與寬度的表現略有差異。
4.6 使用場景
這個控制元件,說老實話,筆者還是不知道該在什麼場景下使用,可能會有些特殊的場景。但是從IntrinsicWidth與IntrinsicHeight佈局差異看,Flutter基礎控制元件封的確實很隨性,一些可有可無甚至是重複的控制元件,我覺得精簡精簡挺好的,哈哈。
5. 後話
筆者建了一個Flutter學習相關的專案,Github地址,裡面包含了筆者寫的關於Flutter學習相關的一些文章,會定期更新,也會上傳一些學習Demo,歡迎大家關注。