FlutterUnit 基于 TolyUI 大大簡化了界面構建的代碼復雜程度,因此之前想要實現的一些小功能,就可以輕松支持。布局游樂場是通過交互的方式來 直觀體驗 組件的布局特性,從而更易學和掌握。目前 FlutterUnit 已在 知識集錄模塊新增了 布局寶庫, 大家可以更新代碼查看 ~
1. 什么是布局游樂場
對于新手朋友,對有些較復雜的布局組件很難把握其特性。為此 FlutterUnit 設立了 布局寶庫 模塊,來幫助開發者更容易理解 Flutter 中的布局特性以及核心的布局組件。其中的 Playground 指的是可交互操作的組件展示面板,如下的 Flex Playground 將淋漓盡致地展示 Flex 組件布局特性。
Playground 實現過程中,依賴了很多 TolyUI 中的組件:
- 對于枚舉類型的參數,通過 TolySelect 組件處理選擇事件。
- 操作的圖標按鈕使用 TolyAction 組件。
- 布局寶庫的側欄導航,使用 TolyRailMenuTree 組件。
目前已經完成了 Flex、Wrap、Stack 三個多字布局的 Playground 。可以在操作面板中增加/刪除,指定寬高的色塊。來更好的體驗組件的布局效果。
2. Flex Playground 功能實現 - 數據層
下面以 Flex Playground 來介紹一下功能實現。一個 Playground 主要包括兩個區域:左側的組件 效果展示區 和右側的 操作面板區。其中右側的交互操作會影響左側的效果展示:
我們知道 Flutter 中,數據決定界面的表現,首先應該確定 Flex Playground 中依賴了哪些數據。當前功能是通過交互修改 Flex 組件的屬性,所以 Flex 組件的屬性內容是一份需要維護的狀態。通過下面的 FlexAttr 類承載各個屬性數據:
class FlexAttr {final Axis direction;final MainAxisAlignment mainAxisAlignment;final CrossAxisAlignment crossAxisAlignment;final MainAxisSize mainAxisSize;final TextDirection textDirection;final VerticalDirection verticalDirection;final TextBaseline textBaseline;
另外,需要若干個色塊,它們有各自的寬度和高度,通過 DisplayItem 類承載相關數據:
class DisplayItem {final double width;final double height;final Color color;
這樣 Flex Playground 中的狀態數據就呼之欲出了:
[1]
. DisplayItem 列表數據,記錄色塊信息。[2]
._selectIndex
選中的色塊索引,用于刪除色塊。[3]
. FlexAttr 數據,決定 Flex 組件的展示效果。
class _FlexPlaygroundState extends State<FlexPlayground> {List<DisplayItem> _data = [];late FlexAttr _attr;int _selectIndex = -1;
3. Flex Playground 功能實現 - 視圖層
左側的布局效果展示區封裝為如下的 FlexDisplay 組件,依賴三個狀態數據,通過 Flex 組件來構建展示內容。其中,
- Flex 構造函數中的各個屬性由 FlexAttr 數據提供;
children
組件由List<DisplayItem>
數據映射得到;- 色塊的選中事件,通過
onSelectChanged
回調,交由使用者處理,更新激活索引。
class FlexDisplay extends StatelessWidget {final List<DisplayItem> items;final FlexAttr attr;final int selectIndex;final ValueChanged<int> onSelectChanged;const FlexDisplay({super.key,required this.items,required this.attr,required this.selectIndex,required this.onSelectChanged,});Widget build(BuildContext context) {return Flex(direction: attr.direction,mainAxisAlignment: attr.mainAxisAlignment,crossAxisAlignment: attr.crossAxisAlignment,mainAxisSize: attr.mainAxisSize,textDirection: attr.textDirection,verticalDirection: attr.verticalDirection,textBaseline: TextBaseline.alphabetic,children: items.asMap().keys.map((int index) {bool active = selectIndex == index;return GestureDetector(onTap: () => onSelectChanged(index),child: DisplayPlayItem(item: items[index], selected: active),);}).toList(),);}
}
右側的操作面板需要支持交互操作,通過觸發事件來更新狀態數據,然后更新界面,即可實現我們期望的功能。Flex 組件的屬性中有很多枚舉值,這比較適合使用下拉選擇來處理。Flutter 中自帶的選擇器在桌面端的體驗效果并不是很好,于是 TolyUI 中提供了 TolySelect 組件,便于構建類似的選擇功能:
右側的面板通過 FlexOpTool 組件展示,其中添加、刪除、重置的按鈕事件,分別通過回調交由使用者處理。另外屬性操作時數據的變化,也會通過 ValueChanged
通知外界進行數據處理。也就是說 FlexOpTool 本身并不參與狀態數據的維護邏輯。只負責基于數據構建界面,以及數據變化時的回調通知:
class FlexOpTool extends StatefulWidget {final ValueChanged<Size> onAddBox;final VoidCallback onDelete;final VoidCallback onReset;final FlexAttr attr;final ValueChanged<FlexAttr> onAttrChange;
這里著重介紹一下選擇器的使用, TolySelect 主要通過 String 列表 data
和激活索引 selectIndex
決定菜單項羽和激活項;通過 cellStyle
可以簡單定制菜單項的展示效果。由于這里選擇器的使用場合非常多,所以封裝了 ItemSelector
組件統一處理標題、布局等效果:
typedef NameCalc<T> = String Function(T data);class ItemSelector<T> extends StatelessWidget {final int selectIndex;final List<T> data;final NameCalc<T> calcFun;final ValueChanged<T> onSelect;final String label;final String subTitle;const ItemSelector({super.key,required this.selectIndex,required this.data,required this.calcFun,required this.onSelect,required this.subTitle,required this.label,});Widget build(BuildContext context) {TextStyle labelStyle = const TextStyle(color: Color(0xff61666d), fontSize: 12);DropMenuCellStyle lightStyle = const DropMenuCellStyle(padding: EdgeInsets.symmetric(horizontal: 4,vertical: 1),borderRadius: BorderRadius.all(Radius.circular(6)),foregroundColor: Color(0xff1f1f1f),backgroundColor: Colors.transparent,disableColor: Color(0xffbfbfbf),hoverBackgroundColor: Color(0xfff5f5f5),hoverForegroundColor: Color(0xff1f1f1f),textStyle: TextStyle(fontSize: 11));return Padding(padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6),child: Row(children: [Column(crossAxisAlignment: CrossAxisAlignment.start,children: [Text(label, style: labelStyle),Text(subTitle, style: labelStyle.copyWith(fontSize: 8)),],),const Spacer(),TolySelect(fontSize: 11,cellStyle: lightStyle,data: data.map((e) => calcFun(e)).toList(),selectIndex: selectIndex,iconSize: 16,height: 25,width: 110,maxHeight: 200,onSelected: (int index) => onSelect(data[index]),)],),);}
}
4. Flex Playground 功能實現 - 數據邏輯層
FlexOpTool 提供了交互過程中數據變化的時機,使用者需要在對應的時機來維護狀態數據的正確性。
如下所示,_onAddBox
方法,會在 DisplayItem
列表中增加一個色塊,觸發 setState 重新構建;_deleteSelectIndex
方法將移除對應索引的色塊,并重新構建更新界面:
void _onAddBox(Size size) {int index = _data.length + 1;Color color = kColors[index % kColors.length];_data.add(DisplayItem(width: size.width, height: size.height, color: color));setState(() {});
}void _deleteSelectIndex() {if (_selectIndex < 0 || _selectIndex >= _data.length) {$message.warning(message: '請先選擇刪除的色塊!');return;}_data.removeAt(_selectIndex);_selectIndex = -1;setState(() {});
}
當屬性數據變化,通過 _onAttrChange
方法更新 _attr
數據即可;重置回調時,將三個狀態數據設為初始值:
void _onAttrChange(FlexAttr attr) {setState(() {_attr = attr;});
}void _reset({bool init=false}){_attr = FlexAttr(direction: Axis.horizontal);_data = [DisplayItem(width: 20, height: 20, color: kColors[0]),DisplayItem(width: 10, height: 80, color: kColors[1]),DisplayItem(width: 40, height: 30, color: kColors[2]),DisplayItem(width: 60, height: 20, color: kColors[3]),];_selectIndex = -1;if(init) return;setState(() {});
}
這就是 Flex Playground 功能的核心實現過程,緊緊把握 狀態數據
、組件構建
、交互事件
和 數據維護
四個方面,就可以很輕松地完成任何功能需求。其他的 Playground 實現方式類似,就不一一介紹了,希望大家可以在 布局游樂場 中,通過交互的方式,學習更多知識。
以后,FlutterUnit 將在 TolyUI 的加持下,支持更多的好玩和使用的功能,敬請期待~