iOS流式布局的原理和代碼實現
最簡單的流式布局模型, 其實就是: 靠左, 靠右, 或者堆疊. 根據這個簡單的理論, 可以用兩個棧(Stack)數據結構, 一個表示靠左邊的控件列表, 另一個表示靠右邊的控件列表, 即可實現流式布局模型.
用偽代碼表示如下:
// 視圖控件 class View{ private FlowLayouter layouter;// 當控件發生 frame 改變后, 調用此方法標記為需要重新布局 void setNeedsLayout(){ View view = this; while(view){ view.markNeedsLayout(); // 當控件需要重新布局時, 一般地, 它的父節點也需要重新布局 view = view.parent; } } void layout(){ for(View child in this.children){ this.layouter.place(child); } }
}
// 流式布局管理器 class FlowLayouter{ private Stack leftViews; private Stack rightViews;
void place(View child){ Position pos; child.layout(); // 子節點先進行布局 while(!this.spaceFits(child)){ if(child.floatLeft){ View view = this.leftViews.pop(); pos = view.pos; // 當被移除的節點比其它節點更高時, 繼續移除 while(pos.y > this.leftViews.last.y){ View view = this.leftViews.pop(); pos = view.pos; } } if(child.floatRight){ View view = this.rightViews.pop(); pos = view.pos; // 當被移除的節點比其它節點更高時, 繼續移除 while(pos.y > this.rightViews.last.y){ View view = this.rightViews.pop(); pos = view.pos; } } } // place child here child.pos = pos; if(child.floatLeft){ this.leftViews.push(child.pos); } if(child.floatRight){ this.rightViews.push(child.pos); } }
}</pre>
這段代碼最重要的是兩點:
1. 當某個控件發生改變時, 它需要重新布局. 同時, 它的父節點, 以及父節點的父節點, 一直到節點樹的根節點, 都需要重新布局. 當然, 這是性能最差的方案, 優化的思路就是減少需要重新布局的節點的數量, 這需要發動每個人的聰明才智來想.
2. 用兩個 Stack 來分別表示靠左的和靠右節點列表. 如果當前的空白空間不足以放下一個控件, 那么, 嘗試從節點列表中移除一個節點, 這樣, 這個布局區域就空出來了一些空間. 當然, 這個空間應該往下移, 不能和被移除的節點所占據的空間重疊. 因為流式布局的基本原理就是不重疊(除非通過特殊設定, 如負數的偏移量).
有了這個簡單的流式布局模型, 你可以在所有最基本的絕對定位的 GUI 庫上面實現功能強大的流式布局, 例如, iOS 的 UIKit 不支持流式布局, 你可以根據上面的代碼擴展, 給 iOS 界面開發加上流式布局功能.
流式布局其實是非常有趣的一項功能, 它的模型很簡潔, 但功能強大且應用廣泛. GUI 界面的本質是樹, 樹是簡潔而優美的, 而流式布局使用的數據結構是 Stack, 又是一種非常基礎的數據結構.
說句題外話, 我已經實現了 iOS 系統上面的 UI 流式布局 -CocoaUI, 你可以試用下.
Related posts: