form_core

一个功能强大的 Flutter 表单问卷渲染库,提供 XLSForm 标准的完整实现,支持复杂的表单逻辑、多种题型和灵活的布局模式。

核心优势

  • 🎯 标准兼容:完整支持 XLSForm 标准,兼容 ODK、KoBoToolbox 等主流数据采集工具
  • 🚀 开箱即用:提供丰富的内置题型和样式,无需额外开发即可快速构建问卷
  • 🎨 灵活布局:支持列表页和单项页两种呈现模式,适配不同的使用场景
  • 🔄 动态逻辑:支持跳题逻辑(relevant)、自动计算(calculate)、约束验证(constraint)
  • 🎭 高度定制:支持自定义题目样式、布局、验证规则等,满足个性化需求

支持的题型

基础输入题型

  • text - 文本输入题:支持单行文本输入
  • integer - 整数输入题:限制只能输入整数
  • double / decimal - 小数输入题:支持小数点输入

选择题型

  • select_one - 单选题:从多个选项中选择一个
    • 支持图片选项(图片可配置在选项的左、右、上、下位置)
    • 支持随机选项(appearance="random")
  • select_multiple - 多选题:从多个选项中选择多个
    • 支持图片选项
    • 支持最小/最大选择数量限制

媒体题型

  • image - 图片题:支持多种绘制模式
    • 绘图模式(appearance="draw"):自由绘制
    • 书写模式(appearance="write"):手写文字
    • 签名模式(appearance="signature"):电子签名
  • video - 视频题:支持视频录制和上传

高级题型

  • range - 范围选择题:滑动条选择数值范围
  • note - 提示说明题:显示说明文字,支持动态内容
  • calculate - 计算题:根据其他题目的答案自动计算结果
  • begin_group - 问题组:将多个问题组织在一起
    • 支持一次性显示所有子问题(appearance="all")
    • 支持逐个显示子问题(appearance="one_by_one")
  • repeat - 重复组:支持重复填写一组问题(如多个家庭成员信息)

特殊题型

  • scriptui - 脚本驱动题:通过脚本动态控制题目显示和交互
  • timer - 计时器题:记录答题时间
  • voice - 语音识别题:支持语音输入(规划中)
  • dateTime - 日期时间题:日期时间选择器(规划中)

功能特性

表达式引擎

  • ✅ 支持 XLSForm 表达式评估(relevant、calculation、constraint)
  • ✅ 支持从 JSON 对象或 Map 动态构建评估上下文
  • ✅ 支持 ${variable} 变量引用语法
  • ✅ 支持 . 当前节点引用(用于 constraint 表达式)
  • ✅ 自动处理数据类型转换(Boolean、String、Number)
  • ✅ 完整的错误处理和异常捕获

布局模式

  • 列表页模式(listPage):适合问卷预览和详情查看,所有题目在一个滚动列表中显示
  • 单项页模式(itemPage):适合移动端数据采集,每次显示一个题目,支持左右滑动切换
    • 支持上下布局(topToBottom):题目描述和答题区域上下排列
    • 支持左右布局(leftToRight):题目描述和答题区域左右排列

显示模式

  • 回答模式(answer):默认模式,遵循 relevant 表达式控制题目显示
  • 预览模式(prePage):预览问卷结构,控制 display 的 UI 呈现
  • 展开模式(expand):显示所有题目,忽略 relevant 表达式

交互特性

  • ✅ 支持必答题验证
  • ✅ 支持跳题逻辑(根据前面题目的答案动态显示/隐藏后续题目)
  • ✅ 支持答题进度跟踪
  • ✅ 支持欢迎页和报告页自定义
  • ✅ 支持答题倒计时
  • ✅ 支持答题完成回调
  • ✅ 支持编辑模式和只读模式切换
  • ✅ 支持文本输入防抖(避免频繁触发回调)

使用方法

基本用法

import 'package:form_core/native/expression_channel.dart';

// 1. 评估通用表达式
final result = await ExpressionChannel.evaluate(
  expression: '${age} >= 18',
  context: {'age': 20},
);

// 2. 评估布尔表达式(用于 relevant 和 constraint)
final isVisible = await ExpressionChannel.isTrue(
  expression: '${age} >= 18',
  context: {'age': 20},
);

// 3. 评估字符串表达式(用于 calculation 返回字符串)
final grade = await ExpressionChannel.evalString(
  expression: "if(${score} > 90, '优秀', '不合格')",
  context: {'score': 95},
);

// 4. 评估数值表达式(用于 calculation 返回数字)
final sum = await ExpressionChannel.evalNumber(
  expression: '${a} + ${b}',
  context: {'a': 10, 'b': 20},
);

使用 Map 作为上下文

final context = {
  'name': '张三',
  'age': 25,
  'score': 85,
  'isStudent': true,
};

final result = await ExpressionChannel.evaluate(
  expression: '${age} >= 18 and ${isStudent} = true',
  context: context,
);

使用 currentValue(用于 constraint 表达式)

// constraint 表达式中可以使用 "." 引用当前字段的值
final isValid = await ExpressionChannel.isTrue(
  expression: '. >= 0 and . <= 100',
  context: {'score': 50},
  currentValue: 75, // "." 将引用这个值
);

复杂表达式示例

// 条件表达式
final result = await ExpressionChannel.evalString(
  expression: "if(${age} >= 18, '成年人', '未成年人')",
  context: {'age': 20},
);

// 数学运算
final total = await ExpressionChannel.evalNumber(
  expression: '${price} * ${quantity}',
  context: {'price': 10.5, 'quantity': 3},
);

// 逻辑运算
final canVote = await ExpressionChannel.isTrue(
  expression: '${age} >= 18 and ${citizenship} = true',
  context: {'age': 20, 'citizenship': true},
);

API 文档

ExpressionChannel

evaluate

评估通用表达式,返回动态类型结果。

static Future<dynamic> evaluate({
  required String expression,
  dynamic context,
  dynamic currentValue,
})

参数:

  • expression: ODK 表达式字符串,如 "${age} >= 18"
  • context: 评估上下文,可以是 Map<String, dynamic>String(JSON/XML)
  • currentValue: 当前字段的值(用于 constraint 表达式中的 "." 引用),可选

返回: Future<dynamic> - 评估结果,类型根据表达式返回(Boolean、String、Number 等)

isTrue

评估布尔表达式,返回布尔值。

static Future<bool> isTrue({
  required String expression,
  dynamic context,
  dynamic currentValue,
})

返回: Future<bool> - 布尔值结果

evalString

评估字符串表达式,返回字符串结果。

static Future<String?> evalString({
  required String expression,
  dynamic context,
  dynamic currentValue,
})

返回: Future<String?> - 字符串结果

evalNumber

评估数值表达式,返回数值结果。

static Future<num?> evalNumber({
  required String expression,
  dynamic context,
  dynamic currentValue,
})

返回: Future<num?> - 数值结果(Double)

支持的表达式语法

变量引用

  • ${variable} - 引用上下文中的变量,转换为 /data/variable
  • . - 引用当前字段的值(需要提供 currentValue 参数)

运算符

  • 算术运算符:+, -, *, /, %
  • 比较运算符:>, <, >=, <=, =, !=
  • 逻辑运算符:and, or, not

函数

  • if(condition, trueValue, falseValue) - 条件表达式
  • number(value) - 转换为数字(自动处理)

数据类型

  • 布尔值:true, false(自动转换为 1/0)
  • 字符串:使用单引号或双引号,如 'hello'"world"
  • 数字:整数或小数,如 10, 3.14

错误处理

所有方法都会抛出异常,建议使用 try-catch 处理:

try {
  final result = await ExpressionChannel.evaluate(
    expression: '${age} >= 18',
    context: {'age': 20},
  );
} catch (e) {
  print('表达式评估出错: $e');
}