FlutterUnit 基于 TolyUI 大大简化了界面构建的代码复杂程度,因此之前想要实现的一些小功能,就可以轻松支持。布局游乐场是通过交互的方式来 直观体验 组件的布局特性,从而更易学和掌握。目前 FlutterUnit 已在 知识集录模块新增了 布局宝库, 大家可以更新代码查看 ~
对于新手朋友,对有些较复杂的布局组件很难把握其特性。为此 FlutterUnit 设立了 布局宝库 模块,来帮助开发者更容易理解 Flutter 中的布局特性以及核心的布局组件。其中的 Playground 指的是可交互操作的组件展示面板,如下的 Flex Playground 将淋漓尽致地展示 Flex 组件布局特性。
Playground 实现过程中,依赖了很多 TolyUI 中的组件:
目前已经完成了 Flex、Wrap、Stack 三个多字布局的 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;
左侧的布局效果展示区封装为如下的 FlexDisplay 组件,依赖三个状态数据,通过 Flex 组件来构建展示内容。其中,
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,
});
@override
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,
});
@override
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]),
)
],
),
);
}
}
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 的加持下,支持更多的好玩和使用的功能,敬请期待~