FlutterUnit & TolyUI | 布局游乐场

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 类承载各个属性数据:

dart 复制代码
class FlexAttr {
  final Axis direction;
  final MainAxisAlignment mainAxisAlignment;
  final CrossAxisAlignment crossAxisAlignment;
  final MainAxisSize mainAxisSize;
  final TextDirection textDirection;
  final VerticalDirection verticalDirection;
  final TextBaseline textBaseline;

另外,需要若干个色块,它们有各自的宽度和高度,通过 DisplayItem 类承载相关数据:

dart 复制代码
class DisplayItem {
  final double width;
  final double height;
  final Color color;

这样 Flex Playground 中的状态数据就呼之欲出了:

  • [1] . DisplayItem 列表数据,记录色块信息。
  • [2]. _selectIndex 选中的色块索引,用于删除色块。
  • [3]. FlexAttr 数据,决定 Flex 组件的展示效果。
dart 复制代码
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 回调,交由使用者处理,更新激活索引。
dart 复制代码
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 本身并不参与状态数据的维护逻辑。只负责基于数据构建界面,以及数据变化时的回调通知:

dart 复制代码
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 组件统一处理标题、布局等效果:

dart 复制代码
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]),
          )
        ],
      ),
    );
  }
}

4. Flex Playground 功能实现 - 数据逻辑层

FlexOpTool 提供了交互过程中数据变化的时机,使用者需要在对应的时机来维护状态数据的正确性。

如下所示,_onAddBox 方法,会在 DisplayItem 列表中增加一个色块,触发 setState 重新构建;_deleteSelectIndex 方法将移除对应索引的色块,并重新构建更新界面:

dart 复制代码
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 数据即可;重置回调时,将三个状态数据设为初始值:

dart 复制代码
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 的加持下,支持更多的好玩和使用的功能,敬请期待~

相关推荐
へんしんへんしん5 小时前
MySQL主从复制与读写分离
android·github
mrathena8 小时前
Windows 11 安装 安卓子系统 (WSA)
android·windows
啊猪是的读来过倒10 小时前
Vue 全局状态管理新宠:Pinia实战指南
前端·vue.js·flutter·pinia·全局状态管理
DaSunWarman10 小时前
自动翻译 android/res/values/strings.xml
android·xml·机器翻译·strings.xml
LuckyRich111 小时前
【MySQL】mysql访问
android·数据库·mysql·adb
江畔何年初見月11 小时前
餐饮点餐系统SQL
android·sql·信息可视化
nbplus_00714 小时前
Swagger php注解常用语法梳理
android·开发语言·php·个人开发·laravel·swagger
jiet_h14 小时前
深入理解 “androidx.databinding.DataBindingUtil“ 细节和使用
android·kotlin·androidx
jiet_h15 小时前
深入解析 androidx.databinding.Bindable 注解
android·kotlin·androidx
JoyceMill15 小时前
Android 蓝牙开发全面指南
android