MLKitHandwriting 拓展
把用户在画布上写的字识别成文字。基于 Google ML Kit 数字墨水识别(Digital Ink Recognition), 支持 300 多种语言 / 20 多种文字系统,另外还能识别表情涂鸦和基本几何图形。 识别过程完全在设备本地完成,不上传笔迹。
为什么选它:
- 只要一个「开始识别」块。语言包没下载会自动先下载再识别 —— 不用自己串「下载模型 → 等下载完成事件 → 初始化 → 识别」这一长串。
- 笔迹自动采集。在设计器里给
书写画布属性选一块画布就行,用户照常在画布上画线,笔迹同时被记录,画布本身的绘制功能完全不受影响。不用自己在「拖动」事件里一个点一个点地喂坐标。 - 自动带上书写区域。画布尺寸会自动作为”书写区域”信息告诉识别器,这对准确率有明显帮助。
- 支持候选字。
候选数量设成 5,就能拿到多个候选做候选字列表(像输入法那样)。
重要限制:语言包需要联网从 Google 服务器下载(一种语言几 MB,下载后永久离线可用)。 国内网络访问 Google 服务器可能失败,此时触发
ModelDownloadFailed事件。 请在应用里对用户交代清楚,或引导用户在能访问 Google 服务的网络下完成首次下载。 识别引擎本身(约 13MB,4 个 CPU 架构)已打包在.aix里,不需要下载。
- .aix 拓展下载:
cn.fun123.MLKit.Handwriting.aix
快速上手
一、写字识别(最小可用)
设计器摆放:
- 一个画布(Canvas),宽度「充满」、高度 300 像素,
画笔粗细设 5 左右(笔画太细会影响识别)。 - 一个
MLKitHandwriting,把书写画布(DrawingCanvas) 属性选成刚才那个画布。 - 两个按钮(识别、清空)、一个标签显示结果。
when 按钮_识别.Click() {
Handwriting1.Recognize()
}
when 按钮_清除.Click() {
Handwriting1.Clear()
}
when Handwriting1.GotText(text, candidatesJson) {
标签1.Text = text
}
when Handwriting1.RecognizeFailed(errorMessage) {
标签1.Text = errorMessage
}
就这样 —— 语言默认是 zh-Hani(中文,简繁都认),第一次点「识别」会自动下载中文语言包。
二、写完自动识别,识别完自动清空(连续手写输入)
用「计时器」(Clock)做一个”停笔 800 毫秒就识别”的效果,体验接近手写输入法。
global penIdle = 0
when 画布1.Dragged(startX, startY, prevX, prevY, currentX, currentY, draggedAnySprite) {
penIdle = 0
}
when 计时器1.Timer() {
penIdle = penIdle + 1
if penIdle == 4 {
if Handwriting1.StrokeCount > 0 {
Handwriting1.Recognize()
}
}
}
when Handwriting1.GotText(text, candidatesJson) {
文本输入框_结果.Text = join(文本输入框_结果.Text, text)
Handwriting1.Clear()
penIdle = 0
}
计时器间隔设 200 毫秒,
penIdle == 4就是停笔约 800 毫秒。
三、做候选字列表(像输入法)
把 候选数量 设成 5,从 candidatesJson 里取候选。
when Screen1.Initialize() {
Handwriting1.MaxResultCount = 5
}
when Handwriting1.GotText(text, candidatesJson) {
标签1.Text = text
标签_原始结果.Text = candidatesJson
}
candidatesJson 的形状是(用「JSON 文本解码」块解析):
[{"text":"你","score":0.93},{"text":"尔","score":0.41},{"text":"仦","score":0.12}]
四、利用上文提升准确率
已经输入了一些内容时,把它设进 上文内容,识别器会据此判断。
例如上文是「今天天」,接着写的字更可能被认成「气」而不是形近字。
when Handwriting1.GotText(text, candidatesJson) {
文本输入框_结果.Text = join(文本输入框_结果.Text, text)
Handwriting1.PreContext = 文本输入框_结果.Text
Handwriting1.Clear()
}
五、应用启动时提前下载语言包
让用户第一次识别不用等,并且能明确看到网络问题。
when Screen1.Initialize() {
Handwriting1.CheckModel()
}
when Handwriting1.GotModelState(isDownloaded) {
if isDownloaded {
标签1.Text = "语言包已就绪"
} else {
标签1.Text = "正在下载语言包,请稍候…"
Handwriting1.DownloadModel()
}
}
when Handwriting1.ModelDownloaded() {
标签1.Text = "语言包下载完成,可以开始书写了"
}
when Handwriting1.ModelDownloadFailed(errorMessage) {
标签1.Text = errorMessage
}
六、识别几何图形 / 表情涂鸦
把 语言标签 换掉即可,其余代码不用改:
when 按钮_形状.Click() {
Handwriting1.LanguageTag = "shapes"
Handwriting1.Clear()
标签1.Text = "请画一个圆形、三角形或方形"
}
when 按钮_表情.Click() {
Handwriting1.LanguageTag = "emoji"
Handwriting1.Clear()
标签1.Text = "请画一个笑脸、爱心之类"
}
换语言后需要下载对应的语言包(
shapes、emoji的包都很小)。
属性
- DrawingCanvas
- 用户书写用的画布组件。在设计器里选好即可。
选定之后用户在画布上画线,笔迹会被自动记录,画布自己的绘制功能不受影响(还是照常画出线条)。
建议画布
画笔粗细设 4~6,太细的笔画会影响识别率。 - LanguageTag
- 识别语言。可以填标准语言标签,也可以填简称。常用值:
值 含义 zh-Hani中文(默认,简体繁体都能认) zh-Hani-TW/zh-Hani-HK繁体(台湾 / 香港) en-US英文 ja日文 ko韩文 emoji表情涂鸦 shapes基本几何图形 autodraw涂鸦联想(画得像什么就认成什么) 完整语言列表见 ML Kit 数字墨水支持语言。 改了语言需要重新下载对应语言包。
- MaxResultCount
- 返回几个候选结果,默认 1(最像的那个)。
设成 5 之类的值可以在
GotText事件的candidatesJson里拿到多个候选,用来做候选字列表。 - PreContext
- 上文内容。填上用户已经输入的前一段文字,识别器会据此提升准确率。只在块里设置(不是设计器属性)。
- StrokeCount
- 只读。当前已经采集到几笔笔迹。可以用它判断用户有没有写东西(
> 0才值得识别)。 - Busy
- 只读。是否正在识别或下载模型。
事件
- GotText(text,candidatesJson)
- 识别成功。
text— 最像的那个结果,直接用这个就行。candidatesJson— 全部候选结果的 JSON 数组(受候选数量属性控制), 形如[{"text":"你","score":0.93}]。score是置信度,部分语言不提供。
- RecognizeFailed(errorMessage)
- 识别失败。也包括这些”用法不对”的情况:没设
书写画布、还没写就点识别、上一次还没处理完。 错误文本可以直接显示给用户。 - ModelDownloaded()
- 语言包下载完成。自动下载和手动
DownloadModel都会触发。 - ModelDownloadFailed(errorMessage)
- 语言包下载失败。最常见的原因是网络无法访问 Google 服务器。
- GotModelState(isDownloaded)
CheckModel的结果。- GotDeleteResult(deleted,errorMessage)
DeleteModel的结果。
方法
- Recognize()
- 识别当前笔迹。语言包没下载会自动先下载再识别(下载成功会先触发
ModelDownloaded)。 结果通过GotText返回。 - Clear()
- 清空已采集的笔迹,同时清空画布上的画面,方便重新写。
- DownloadModel()
- 手动下载当前语言的语言包。
一般不需要单独调用(
Recognize会自动下载),但可以用它在应用启动时提前下载好。 - CheckModel()
- 查询当前语言的语言包是否已下载。结果通过
GotModelState返回。 - DeleteModel()
- 删除当前语言的语言包,释放存储空间。结果通过
GotDeleteResult返回。
运行环境:AI 伴侣联机需要 Android 11 及以上
本拓展的识别模型放在依赖库自带的 assets 里,AI 伴侣联机调试要把它挂进
AssetManager,用的是 Android 11(API 30)才有的 ResourcesLoader —— 这是系统提供的
唯一公开办法,更低版本没有等价 API。所以:
| 运行方式 | Android 6 ~ 10 | Android 11 及以上 |
|---|---|---|
| 打包 APK | ✅ 正常 | ✅ 正常 |
| AI 伴侣联机 | ❌ 模型加载不了 | ✅ 正常 |
打包 APK 之所以不受限制,是因为模型在编译时就被并进了 APK 的 assets,装到手机上 就是 App 自己的资源,与系统版本无关。伴侣的 APK 是提前编好装在手机上的,运行时没法 往自己已安装的包里加文件,只能靠系统的运行时挂载 API。
低版本设备上想验证功能,直接打包 APK 测即可;要用伴侣边改边调,请换 Android 11+ 的设备。
常见问题
点「识别」提示”请先给书写画布属性选一个画布组件”。
设计器里选中 MLKitHandwriting,在属性面板把 书写画布 选成你的画布。
点「识别」一直触发 ModelDownloadFailed。
语言包连不上 Google 服务器。这是网络问题,不是拓展的问题。
可以引导用户切换到能访问 Google 服务的网络完成一次下载,之后就永久离线可用了。
识别率不高。 按影响大小依次排查:
- 画笔太细 —— 把画布
画笔粗细调到 4~6。 - 写得太小 —— 让用户占满画布书写;识别器是按整块书写区域理解的。
- 一次写太多字 —— 一次识别一到几个字最准,写满一行再识别会明显下降。
- 没用上文 —— 连续输入时把已输入内容设进
上文内容。 - 语言不对 —— 写英文却用
zh-Hani,或反过来。
画布上的线还在,但识别说”还没有笔迹”。
Clear 是同时清笔迹和画面的;如果你用画布自己的 清除画布 块,只清了画面、笔迹还在(反过来也一样会错位)。
统一用本拓展的 Clear 方法。
能识别整页手写笔记吗? 不适合。数字墨水识别是为”边写边认”设计的(几个字到一行),整页笔记应该用 OCR 类拓展对图片做识别。
笔迹会上传吗? 不会。识别在设备本地完成,只有语言包下载需要联网。
扫码添加客服咨询