Qt 布局系統(tǒng)介紹
布局系統(tǒng)
作為一名 iOS 開發(fā)人員, 見證著 iOS 布局系統(tǒng)的不斷完善, 從絕對布局, Autoresizing 到 Autolayout. 使得開發(fā)人員的工作效率越來越高, 項目界面的可讀性和易維護性越來越強. 如今 IDE 中的可視化界面工具已經(jīng)非常強大, 許多網(wǎng)友"戲稱" iOS 開發(fā)者為"UI 拖拽師", 可見, iOS 開發(fā)中界面布局系統(tǒng)的高效. 所以, 優(yōu)秀的布局系統(tǒng)的使命在于讓開發(fā)者花更少的時間來完成更易維護的界面.
同樣的, 在 Qt 中, 系統(tǒng)提供了強大的排版機制來為窗口中的視圖進行布局排版, 經(jīng)過了對 Qt 布局一個初步的探索, 不得不對 Qt 布局系統(tǒng)的簡潔高效而又功能強大表示贊嘆.
布局系統(tǒng)的功能
在 Qt 中, 布局系統(tǒng)可以完成
- 定位子控件
- 得知窗體默認大小
- 得知窗體最小大小
- 窗體大小變化時進行布局排版
- 內(nèi)容改變(字體大小文本等, 隱藏或顯示, 移除)時進行布局排版
布局系統(tǒng)的結(jié)構(gòu)
Qt 提供了 QLayout 類及其子類來為界面進行排版布局. 結(jié)構(gòu)如下圖:
QLayout 是布局系統(tǒng)中的抽象基類, 繼承自 QObject 和 QLayoutItem, 其中四個子類分別為
- QBoxLayout(箱式布局)
- QFormLayout(表單布局)
- QGridLayout(網(wǎng)格布局)
- QStackedLayout(棧布局)
在真實使用場景中, 往往需要通過多種布局的相結(jié)合來完成界面的設(shè)計, 接下來將分別介紹四中布局.
QBoxLayout 箱式布局
箱式布局提供了兩個子類分別處理水平(QHBoxLayout)和垂直(QVBoxLayout)兩個方向的排版, 可以使視圖排成一行或者一列來顯示. 簡單說, 就是可以讓控件進行排排站, 比如在我們的 AlphaBox 中, 頂部的頭像, 姓名, 和刷新按鈕排成了一排, 這就是水平箱式布局:
你以為我要講一下這個東西如何實現(xiàn)? NO, 我偏偏要以垂直箱式布局為例, 用一個最簡單的例子來介紹箱式布局的使用, 首先創(chuàng)建一個基于 QWidget 的界面, 添加我們需要使用的頭文件:
#include <QVBoxLayout>
#include <QPushButton>
并在構(gòu)造函數(shù)中添加如下代碼
// 添加兩個按鈕
QPushButton *okBtn = new QPushButton;
okBtn ->setText(tr("我在上面, 我最牛"));
QPushButton *celBtn = new QPushButton;
celBtn->setText(tr("我在下面, 我不服"));
// 創(chuàng)建一個垂直箱式布局, 將兩個按鈕扔進去
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(okBtn);
layout->addWidget(celBtn);
// 設(shè)置界面的布局為垂直箱式布局
setLayout(layout);
運行看一下效果, 什么? 這就可以運行了? 坐標呢? 尺寸呢? 是的, 沒看錯...點擊運行:
兩個按鈕已經(jīng)一上一下, 乖乖的在垂直方向自己站好了位置, 就是這么強大, 就是這么省心.
QFormLayout 表單布局
強大的 AlphaBox 是很外向的, 可以很輕松的將你的資料分享給其他用戶, 當我們分享的時候, 會有這樣一個界面:
看到這個界面, 聰明的你可能會說, 這很簡單啊, 好幾個水平箱式布局就可以實現(xiàn), 可是, 更聰明的 Qt 提供了更高效的方式幫助你完成這樣一個界面, 那就是 QFormLayout.
在我所學習 Qt 所使用的書籍中, 將 QFormLayout 翻譯為窗體布局, 我個人認為, 將其翻譯為表單布局更為貼切, 因為 QFormLayout 的強大之處正是可以使用最快的速度完成一個用戶輸入的表單界面的搭建.
那么, 讓我們揭開 AlphaBox 的神秘面紗, 看看這樣一個界面是怎么實現(xiàn)的.
首先, 拖拽一個 Form Layout 到 Widget 中.
雙擊之后即可為表單增加一行.
相信大家看到這張圖時, 就已經(jīng)能理解到表單布局是如何使用的, 提供了標簽作為用戶輸入內(nèi)容的指引, 提供字段類型作為用戶輸入的控件, 作為 iOS 開發(fā)者, 深知這樣一個界面的搭建所需要的繁雜的工作量. 當我第一次打開這個界面時, 被這樣創(chuàng)建界面的方式所驚呆了.
- 按照圖中, 創(chuàng)建表單的第一行, 共享給哪個用戶的輸入框, 可以為輸入框填寫占位文字.
- 雙擊 Form Layout 創(chuàng)建字段類型為 QComboBox (多選框)的一行. 填寫允許的權(quán)限內(nèi)容.
- 設(shè)置整個 Widget 布局為垂直箱式布局
- 在 Form Layout 下拖拽過去一個 Horizontal Layout(水平箱式布局)
- 在箱式布局中添加 Horizontal Spacer (水平占位) 后拖拽兩個 Push Button 完成界面布局
快不快? 快不快! 快不快!!!
同樣的, 如果是使用純代碼表單布局的話可以使用addRow()
的方法來添加一行.
QGridLayout 網(wǎng)格布局
事實上, 強大的 AlphaBox 是這樣的, 我們可以共享給多個用戶, 而且, 下方會有一個列表, 展示共享的用戶以及權(quán)限列表. 這時, 表單布局就沒辦法滿足我們, 只好另求新歡 QGridLayout - 網(wǎng)格布局.
網(wǎng)格布局顧名思義, 可以將界面分割成行列來進行布局管理, 在每個單元格中來擺放控件. 所以 AlphaBox 分享的界面使用了一個 兩行三列 的網(wǎng)格布局來實現(xiàn)的.
當然, 更更復雜的界面, 用 Qt 布局的效率也是非常高的, 我做了一個外鏈分享的布局 Demo, 可以將內(nèi)部資料生成一個下載鏈接共享給任何人去下載.
這個界面中, 我在Tab之內(nèi)使用了網(wǎng)格布局, 布局如圖:
從圖中可以看出, 網(wǎng)格布局像是在操作一個 Excel 一樣簡單, 布局單元格, 合并單元格, 等等.
在這個界面中, 更靈活的使用了 QLayout 的屬性來完成了界面布局排版.
同樣的, 在代碼中, 可以使用如下等的 Api 來為網(wǎng)格視圖添加一個從幾行幾列開始占據(jù)幾行幾列的控件:
void addWidget(QWidget *, int row, int column, int rowSpan, int columnSpan)
QStackedLayout 棧布局
如在 AlphaBox 中, 我們可以通過云端文件瀏覽器直接查看和操作云端文件, 在加載的過程中, 會有一個轉(zhuǎn)菊花的界面.
加載失敗時的錯誤提示:
以及加載成功時:
通常應用的界面會根據(jù)不同的狀態(tài)有不同的內(nèi)容, 這時就可以使用 QStackedLayout 棧布局, 棧布局提供了一個頁面的棧, 每個頁面有完全獨立的界面布局. 可以非常清晰的對不同狀態(tài)下的界面進行布局管理.
在 Qt 的可視化布局工具中, 通過 Stacked Widget 來完成界面的棧布局
通過右鍵來進行頁面的插入移除和排序等操作.
布局相關(guān)屬性
控件大小
對于控件大小, 最重要的兩個屬性是 sizeHint
和 minimumSizeHint
, 這是 QWidget 的屬性, 是只讀屬性. 其中, sizeHint
屬性為控件的建議大小, 對于不同的控件, 有不同的建議大小, 同理 minimumSizeHint
為建議的最小大小. 知道了這兩個屬性才可以理解布局中控件的大小是如何控制的. 如果手動設(shè)置了最小尺寸的話(minimumSize
), minimumSizeHint
是會被忽略的.
大小策略
大小策略屬性 sizePolicy
也是 QWidget 類的屬性, 這個屬性在水平和垂直兩個方向分別起作用, 控制著控件大小變化的策略.
在可視化工具中可以直觀的看到幾種大小策略, 以垂直為例如圖:
-
QSizePolicy::Fixed
只能使用 sizeHint 的大小, 任何操作都不會改變控件大小 -
QSizePolicy::Minimum
sizeHint 為最小大小, 控件可以被拉伸 -
QSizePolicy::Maximum
sizeHint 為最大大小, 控件可以被壓縮 -
QSizePolicy::Preferred
sizeHint 為建議大小, 控件既可以被壓縮也可以被拉伸 -
QSizePolicy::MinimumExpanding
sizeHint 為最小大小, 不能被壓縮, 被拉伸的優(yōu)先級更高 -
QSizePolicy::Expanding
sizeHint 為建議大小, 可以被壓縮, 被拉伸的優(yōu)先級更高 -
QSizePolicy::Ignored
sizeHint 的值將會被忽略
在網(wǎng)上或者書中, 關(guān)于這些策略的說明會有很多, 可是如果不是真的自己嘗試一下, 很難很好的理解在復雜布局的情況下, 大小策略是如何控制布局的, 尤其是 MinimumExpanding
, Expanding
, Ignored
這三種.
對于優(yōu)先級的概念大家肯定不會陌生, 這里我認為優(yōu)先級來解釋這些策略是更清晰易懂的.
關(guān)于拉伸 Expanding
, MinimumExpanding
優(yōu)先級相同, 同時要比 Preferred
和 Ignored
拉伸優(yōu)先級更高, Preferred
和 Ignored
相同.
換句話說, 如果兩個控件在一個水平箱式布局中管理, 其中一個水平大小策略為 Preferred
另一個為 Expanding
或者 MinimumExpanding
如果水平拉伸窗體, 則 Preferred
的控件大小不會改變, Expanding
或者是 MinimumExpanding
會被拉伸.
同理, 如果兩個控件水平大小策略一個為Expanding
, 一個是MinimumExpanding
, 這時拉伸窗體, 則兩個控件均會拉伸.
多說一句, 如果兩個控件都為 Fixed
無法拉伸時, 控件間的間隙會被拉伸.
關(guān)于壓縮 如果達到了minimumSizeHint
是不會被繼續(xù)壓縮了, 但是Ignored
是會忽略 sizeHint
和 minimumSizeHint
的屬性的, 所以是會繼續(xù)被壓縮的.
伸縮性
在 QLayout 中提供了一個和控件大小策略相關(guān)的屬性, layoutStretch
布局伸縮性, 這個值是一個比例, 在可視化工具中可以更直觀的看到這個值的設(shè)置, 如果在布局中有三個控件, 則是三個控件的占比, 用逗號分隔, 如: 1, 1, 1
.
伸縮性就好理解一些了, 但是要注意的是, 只有會被壓縮或者拉伸的控件才會受該屬性值影響(如 Fixed
是不會受該屬性影響)
還有一點非常重要的是設(shè)置了伸縮性的比值(如果都為0, 則表示不設(shè)置) 剛剛提到的大小策略的優(yōu)先級將會被忽略, 還是剛剛的例子, 如果兩個控件在一個水平箱式布局中管理, 其中一個水平大小策略為 Preferred
另一個為 Expanding
, 設(shè)置水平箱式布局的 layoutStretch
為 2, 1
則拉伸時, 并不會像剛剛所說, 只有 Expanding
的控件會被拉伸, 而是都會被拉伸, 按照一個 2 : 1 的拉伸比例拉伸.
窗體大小約束策略
最后想介紹一下 QLayout 的 layoutSizeConstraint
屬性, 用來約束窗體大小, 只影響窗體, 所以該屬性只對最頂級的 QLayout 起作用.
關(guān)于這幾個屬性同樣的, 簡單的介紹網(wǎng)上和書上會有很多, 如果不嘗試一下, 淺顯的字面意思無法理解這幾個屬性的作用. 根據(jù)我的嘗試, 總結(jié)如下:
-
QLayout::SetDefaultConstraint
窗體最小值被設(shè)置為 minimumSize 值無法再縮小, 如果 QLayout 內(nèi)控件有更大的minimumSize, 則會取更大的minimumSize. -
QLayout::SetNoConstraint
窗體沒有約束策略 -
QLayout::SetFixedSize
,窗體大小被設(shè)定為 sizeHint 的大小,無法改變 -
QLayout::SetMinimumSize
窗體最小為 minimumSize 無法再縮小, 如果 QLayout 內(nèi)控件有更小的minimumSize, 則會取更小的minimumSize., 總結(jié)就是的話, 和 Default 不同的地方就是盡可能的小. -
QLayout::SetMaxmumSize
同理, 窗體最大值為 maxmumSize , 無法再放大 -
QLayout::SetMinAndMaxSize
窗體最小為 minimumSize 無法再縮小, 窗體最大值為 maxmumSize , 無法再放大
其他屬性
關(guān)于表單布局和網(wǎng)格布局還有其他的屬性約束單元格的一些策略, 如 layoutFieldGrowthPolicy
控件的變化方式策略等等有興趣可以查看官方文檔, 更多的屬性間隙, 間隔, 對其方式等等都比較好理解了, 在此也不贅述了.
官方文檔
參考
<< Qt Creator 快速入門>> 第三版, 霍亞飛著