Flutter 佈局(三)- FittedBox、AspectRatio、ConstrainedBox詳解

本文主要介紹Flutter佈局中的FittedBox、AspectRatio、ConstrainedBox,詳細介紹了其布舉行爲以及使用場景,並對源碼進行了分析。html

1. FittedBox

Scales and positions its child within itself according to fit.git

1.1 簡介

按照其官方的介紹,它主要作了兩件事情,縮放(Scale)以及位置調整(Position)。github

FittedBox會在本身的尺寸範圍內縮放而且調整child位置,使得child適合其尺寸。作過移動端的,可能會聯想到ImageView控件,它是將圖片在其範圍內,按照規則,進行縮放位置調整。FittedBox跟ImageView是有些相似的,能夠猜想出,它確定有一個相似於ScaleType的屬性。bash

1.2 佈局行爲

FittedBox的佈局行爲還算簡單,官方沒有給出說明,我在這裏簡單說一下。因爲FittedBox是一個容器,須要讓其child在其範圍內縮放,所以其佈局行爲分兩種狀況:ide

  • 若是外部有約束的話,按照外部約束調整自身尺寸,而後縮放調整child,按照指定的條件進行佈局;
  • 若是沒有外部約束條件,則跟child尺寸一致,指定的縮放以及位置屬性將不起做用。

1.3 繼承關係

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > FittedBox
複製代碼

從繼承關係能夠看出,FittedBox控件是一個基礎控件。函數

1.4 示例代碼

new Container(
  color: Colors.amberAccent,
  width: 300.0,
  height: 300.0,
  child: new FittedBox(
    fit: BoxFit.contain,
    alignment: Alignment.topLeft,
    child: new Container(
      color: Colors.red,
      child: new Text("FittedBox"),
    ),
  ),
)
複製代碼

寫了一個很簡單的例子,加入Container是爲了加顏色顯示兩個區域,讀者能夠試着修改fit以及alignment查看其不一樣的效果。佈局

1.5 源碼解析

const FittedBox({
Key key,
this.fit: BoxFit.contain,
this.alignment: Alignment.center,
Widget child,
})
複製代碼

1.5.1 屬性解析

fit:縮放的方式,默認的屬性是BoxFit.contain,child在FittedBox範圍內,儘量的大,可是不超出其尺寸。這裏注意一點,contain是保持着child寬高比的大前提下,儘量的填滿,通常狀況下,寬度或者高度達到最大值時,就會中止縮放。性能

BoxFit佈局表現

alignment:對齊方式,默認的屬性是Alignment.center,居中顯示child。學習

1.5.2 源碼

構造函數以下:ui

@override
RenderFittedBox createRenderObject(BuildContext context) {
return new RenderFittedBox(
  fit: fit,
  alignment: alignment,
  textDirection: Directionality.of(context),
);
}
複製代碼

FittedBox具體實現是由RenderFittedBox進行的。不知道讀者有沒有發現,目前的一些基礎控件,繼承自RenderObjectWidget的,widget自己都只是存儲了一些配置信息,真正的繪製渲染,則是由內部的createRenderObject所調用的RenderObject去實現的。

RenderFittedBox具體的佈局代碼以下:

if (child != null) {
  child.layout(const BoxConstraints(), parentUsesSize: true);
  // 若是child不爲null,則按照child的尺寸比率縮放child的尺寸
  size = constraints.constrainSizeAndAttemptToPreserveAspectRatio(child.size);
  _clearPaintData();
} else {
  // 若是child爲null,則按照最小尺寸進行佈局
  size = constraints.smallest;
}
複製代碼

1.6 使用場景

FittedBox在目前的項目中還未用到過。對於須要縮放調整位置處理的,通常都是圖片。筆者通常都是使用Container中的decoration屬性去實現相應的效果。對於其餘控件須要縮放以及調整位置的,目前尚未遇到使用場景,你們只須要知道有這麼一個控件,能夠實現這個功能便可。

2. AspectRatio

A widget that attempts to size the child to a specific aspect ratio.

2.1 簡介

AspectRatio的做用是調整child到設置的寬高比,這種控件在其餘移動端平臺上通常都不會提供,Flutter之因此提供,我想最大的緣由,可能就是自定義起來特別麻煩吧。

2.2 佈局行爲

AspectRatio的佈局行爲分爲兩種狀況:

  • AspectRatio首先會在佈局限制條件容許的範圍內儘量的擴展,widget的高度是由寬度和比率決定的,相似於BoxFit中的contain,按照固定比率去儘可能佔滿區域。
  • 若是在知足全部限制條件事後沒法找到一個可行的尺寸,AspectRatio最終將會去優先適應佈局限制條件,而忽略所設置的比率。

2.3 繼承關係

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > AspectRatio
複製代碼

從繼承關係看,AspectRatio是基礎的佈局控件。

2.4 示例代碼

new Container(
  height: 200.0,
  child: new AspectRatio(
    aspectRatio: 1.5,
    child: new Container(
      color: Colors.red,
    ),
  ),
);
複製代碼

示例代碼是定義了一個高度爲200的區域,內部AspectRatio比率設置爲1.5,最終AspectRatio的是寬300高200的一個區域。

2.5 源碼解析

構造函數以下:

const AspectRatio({
Key key,
@required this.aspectRatio,
Widget child
}) 
複製代碼

構造函數只包含了一個aspectRatio屬性,其中aspectRatio不能爲null。

2.5.1 屬性解析

aspectRatio:aspectRatio是寬高比,最終可能不會根據這個值去佈局,具體則要看綜合因素,外層是否容許按照這種比率進行佈局,只是一個參考值。

2.5.2 源碼

@override
  RenderAspectRatio createRenderObject(BuildContext context) => new RenderAspectRatio(aspectRatio: aspectRatio);
複製代碼

通過前面一些控件的解析,我想你們對這種構造應該不會再陌生了,繪製都是交由RenderObject去完成的,這裏則是由RenderAspectRatio去完成具體的繪製工做。

RenderAspectRatio的構造函數中會對aspectRatio作一些檢測(assert)

  • aspectRatio不能爲null;
  • aspectRatio必須大於0;
  • aspectRatio必須是有限的。

接下來咱們來看一下RenderAspectRatio的具體尺寸計算函數:

  • 若是限制條件爲isTight,則返回最小的尺寸(constraints.smallest);
if (constraints.isTight)
  return constraints.smallest;
複製代碼
  • 若是寬度爲有限的值,則將高度設置爲width / _aspectRatio。 若是寬度爲無限,則將高度設爲最大高度,寬度設爲height * _aspectRatio;
if (width.isFinite) {
  height = width / _aspectRatio;
} else {
  height = constraints.maxHeight;
  width = height * _aspectRatio;
}
複製代碼
  • 接下來則是在限制範圍內調整寬高,整體思想則是寬度優先,大於最大值則設爲最大值,小於最小值,則設爲最小值。

若是寬度大於最大寬度,則將其設爲最大寬度,高度設爲width / _aspectRatio;

if (width > constraints.maxWidth) {
  width = constraints.maxWidth;
  height = width / _aspectRatio;
}
複製代碼

若是高度大於最大高度,則將其設爲最大高度,寬度設爲height * _aspectRatio;

if (height > constraints.maxHeight) {
  height = constraints.maxHeight;
  width = height * _aspectRatio;
}
複製代碼

若是寬度小於最小寬度,則將其設爲最小寬度,高度設爲width / _aspectRatio;

if (width < constraints.minWidth) {
  width = constraints.minWidth;
  height = width / _aspectRatio;
}
複製代碼

若是高度小於最小高度,則將其設爲最小高度,寬度設爲height * _aspectRatio。

if (height < constraints.minHeight) {
  height = constraints.minHeight;
  width = height * _aspectRatio;
}
複製代碼

2.6 使用場景

AspectRatio適用於須要固定寬高比的情景下。筆者最近使用這個控件的場景是相機,相機的預覽尺寸都是固定的幾個值,所以不能隨意去設置相機的顯示區域,必須按照比率進行顯示,不然會出現拉伸的狀況。除此以外,卻是用的很少。

3. ConstrainedBox

A widget that imposes additional constraints on its child.

3.1 簡介

這個控件的做用是添加額外的限制條件(constraints)到child上,自己挺簡單的,能夠被一些控件替換使用。Flutter的佈局控件體系,梳理着發現確實有點亂,感受整體思想是缺啥就造啥,哈哈。

3.2 佈局行爲

ConstrainedBox的佈局行爲很是簡單,取決於設置的限制條件,而關於父子節點的限制因素生效優先級,能夠查看以前的文章,在這裏就不作具體敘述了。

3.3 繼承關係

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > ConstrainedBox
複製代碼

ConstrainedBox也是一個基礎的佈局控件。

3.4 示例代碼

new ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 100.0,
    minHeight: 100.0,
    maxWidth: 150.0,
    maxHeight: 150.0,
  ),
  child: new Container(
    width: 200.0,
    height: 200.0,
    color: Colors.red,
  ),
);
複製代碼

例子也挺簡單的,在一個寬高200.0的Container上添加一個約束最大最小寬高的ConstrainedBox,實際的顯示中,則是一個寬高爲150.0的區域。

3.5 源碼解析

構造函數以下:

ConstrainedBox({
Key key,
@required this.constraints,
Widget child
})
複製代碼

包含了一個constraints屬性,且不能爲null。

3.5.1 屬性解析

constraints:添加到child上的額外限制條件,其類型爲BoxConstraints。BoxConstraints的做用是幹啥的呢?其實很簡單,就是限制各類最大最小寬高。說到這裏插一句,double.infinity在widget佈局的時候是合法的,也就說,例如想最大的擴展寬度,能夠將寬度值設爲double.infinity。

3.5.2 源碼

@override
RenderConstrainedBox createRenderObject(BuildContext context) {
return new RenderConstrainedBox(additionalConstraints: constraints);
}
複製代碼

RenderConstrainedBox實現其繪製。其具體的佈局表現分兩種狀況:

  • 若是child不爲null,則將限制條件加在child上;
  • 若是child爲null,則會盡量的縮小尺寸。

具體代碼以下:

@override
void performLayout() {
if (child != null) {
  child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
  size = child.size;
} else {
  size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
}
}
複製代碼

3.6 使用場景

須要添加額外的約束條件可使用此控件,例如設置最小寬高,儘量的佔用區域等等。筆者在實際開發中使用的倒不是不少,倒不是說這個控件很差使用,而是好多約束因素是綜合的,例如須要額外的設置margin、padding屬性能,所以單獨再套個這個就顯得很繁瑣了。

3.7 關於UnconstrainedBox

這個控件不作詳細介紹了,它跟ConstrainedBox相反,是不添加任何約束條件到child上,讓child按照其原始的尺寸渲染。

很神奇是吧,我也以爲,其實它的做用就是給child一個儘量大的空間,不加約束的讓其顯示。用處我暫時木有想到。只能說Flutter生產Widget很隨性。

4. 後話

筆者建的一個Flutter學習相關的項目,Github地址,裏面包含了筆者寫的關於Flutter學習相關的一些文章,會按期更新,也會上傳一些學習demo,歡迎你們關注。

5. 參考

  1. FittedBox class
  2. BoxFit enum
  3. AspectRatio class
  4. ConstrainedBox class
  5. BoxConstraints class
  6. UnconstrainedBox class
相關文章
相關標籤/搜索