Flutter/佈局:徹底搞懂 Align 是怎麼對齊的

deepfunc發表於2022-03-30

我們在開發中會經常使用 Align 元件來定位子元件的位置。通常使用的是幾個常用的相對位置,比如:alignment: Alignment.bottomRight,右下角這種。最近看官方文件關於 alignment: Alignment(x, y) 在 Align 中的計算解釋好像有點不對,再結合相關的原始碼,總算是弄清楚了。然後發現網上的教程解釋大多有錯誤,下面就來分享下我對 Align 對齊原理的理解。

前置知識:Alignment

Alignment 的工作方式是先在一個矩形中建立一個虛擬座標系,這個虛擬座標系的原點是矩形的中點,然後 (x, y) 是在這個虛擬座標系中的定位點,並且規定了 (x, y) 的數值代表的是相對值,比如 Alignment(0.0, 0.0) 是中點,Alignment(1.0, 1.0) 是右下角,如圖所示:

(x, y) 中的 x 這個相對值的單位是矩形寬度的一半,比如 2.0 就相當於整個矩形寬度了;y 也是類似的,y 的單位是矩形高度的一半。

再通過一個公式轉化為以矩形左上角為原點的座標系中的定位點。這個轉換公式如下:(x * w/2 + w/2, y * h/2 + h/2),其中 w 是矩形的寬度,h 是矩形的高度。結合下面的示意圖來理解一下計算過程:

綠色框的是這個矩形,黑色座標軸是虛擬座標系,藍色座標軸是以矩形左上角為原點的目標座標系。以虛擬座標軸上橫座標 x 為例,轉換後的目標點橫座標 x' = 矩形寬度的一半 加上 x * 矩形寬度的一半(虛擬座標軸中 x 的單位是矩形寬度的一半)。

比如,Alignment(2.0, 0.0) 這個點轉換後橫座標的值是 1.5 倍的矩形寬度,可不是兩倍的矩形寬度喔。

Align 是怎麼工作的

在 Align 的官方文件中有這樣一句話:

How it works

The alignment property describes a point in the child's coordinate system and a different point in the coordinate system of this widget. The Align widget positions the child such that both points are lined up on top of each other.

意思是說,先利用 alignment 屬性的描述求出在子元件座標系的一個點和在 Align 元件座標系中的另一個點;然後 Align 將子元件放置在使這兩個點重合的位置上。

錯誤的解釋

從以上描述可以看出,最後的放置座標是跟 Align 的大小也是有關係的,因為過程中要用 alignment 來計算 Align 中的一個點。但我從官方文件的示例說明中只看到計算跟子元件相關:

並且我在網上看到的一些教程的解釋,也是錯誤的,比如這個:

要證明這個錯誤很簡單,將 Align 放置在一個 Container 中,然後把 alignment 設定為 Alignment(1.0, 0.0),再隨便放置一個子元件,比如這樣:

Container(
  height: 120,
  width: 200,
  color: Colors.green.withOpacity(0.6),
  child: Align(
    alignment: Alignment(1.0, 0.0),
    child: Container(
      width: 60,
      height: 60,
      color: Colors.red,
    ),
  ),
),

你會發現不管怎麼改變外面 Container 的寬度,裡面紅色矩形的右邊始終是和外面 Container 的右邊是重合的。如果定位的座標只與子元件的寬度相關,這是如何做到的呢?

我的理解

官方文件中 how it works 已經給出了計算的原理,但是沒有給出具體的公式。我看了下相關原始碼證實了 how it works 的描述是沒有問題的,下面我用一個簡單的例子來說明下計算的過程,並給出正確的計算公式。

上圖中綠色矩形是 Align 的大小,紅色是子元件,我還是用 Alignment(1.0, 0.0) 來說明。Alignment(1.0, 0.0) 所描述的點的橫座標在綠色矩形和紅色矩形中都在右邊框那裡,如果 Align 按照這個位置來定位子元件的話,紅色矩形應該在紅色虛線框那裡。但根據 how it works 的描述,Align 放置子元件時會使兩個點重合,也就是紅色矩形右邊框的點與綠色矩形右邊框的點重合。這個時候我們只需要從紅色虛線框的位置移動到實線框的位置,也就是:Align 中定位點的橫座標 減去 子元件中定位點的橫座標:

alignWidth / 2 + x * (alignWidth / 2) - childWidth / 2 - x * (childWidth / 2)

對於座標的 y 值也可以用類似的過程來描述,所以最終定位點的計算公式應該是:

var x = (alignWidth - childWidth) / 2 + x * ((alignWidth - childWidth) / 2);
var y = (alignHeight - childHeight) / 2 + x * ((parentHeight - childHeight) / 2);

最後,如果 alignment 是 FractionalOffset 也可以用類似的過程來描述定位。FractionalOffset 與 Alignment 的不同在於虛擬座標系中 FractionalOffset 的原點在矩形左上角,並且 FractionalOffset(x, y) 中 x 的單位是矩形的整個寬度,不是一半。在 Align 中定位依然遵循兩個 FractionalOffset 的點要保持重合。

相關文章