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

相关推荐
DU_YULIN15 分钟前
Camera HAL ThreadManager概述
android·camera hal
淡淡的香烟1 小时前
Android中的MQTT通信原理解析
android·物联网
淡淡的香烟1 小时前
Android Camera开发详解
android·音视频
mmsx2 小时前
基于 Android 的校园信息管理系统源码
android·java·okhttp
xcyxiner3 小时前
flutter wsl2安装记录(使用宿主机虚拟机)
前端·flutter
YF02113 小时前
如何进入Android工程模式
android
Kevin Coding4 小时前
JsonConvert:适用于 Android、鸿蒙与 Flutter 的 JSON 转 Model 插件
android·flutter·harmonyos
xcLeigh5 小时前
Go入门:基本数据类型全览与选择指南
android·服务器·golang·教程·go热门
alexhilton6 小时前
规范驱动开发:让AI生成符合预期的KMP代码
android·kotlin·android jetpack
码农coding6 小时前
android12 ActivityManagerService开机启动分析
android