在Android上实现汉字笔顺动画效果——HanZiWriter

news2024/11/17 23:41:18

序,万般皆是命,半点不由人。

Hanzi Writer 是 javascript 免费开源库,根据汉字书写时按照笔画顺序的特征,可以播放正确笔画顺序的描边动画和练习测试。支持简体字和繁体字。可以让全球用户能够通过手绘模仿的方式来学习和练习书写汉字。

特点
  • 丰富性: 包含9000+个常用汉字的笔画数据,覆盖广泛。
  • 准确性: 笔画轨迹详细且精准,模拟真实书写体验。
  • 开放源代码: 全面免费,可自由使用和扩展,鼓励社区参与和贡献。
  • 易用性: 提供简洁的JSON格式数据,易于集成到各类开发环境中。
  • 跨平台兼容: 数据基于SVG标准,能在多种设备和浏览器上无缝运行。
  • 体积小:Hanzi Writer 库仅仅 30 kb (压缩后仅 9kb!), 所以添加它不会使网页体积增大。
  • 用处广泛:Hanzi Writer 使用 javascript, HTML5 and SVG, 所以它几乎能嵌入到任何平台应用, iOS 应用、安卓应用、桌面端应用和网页。

在Android中使用,我使用的是加载WebView。

写一个html的引用地址,在定义几个和我们安卓交互的方法。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hanzi Writer</title>
    <script src="file:///android_asset/js/hanzi-writer3.js"></script>
    <script src="file:///android_asset/js/polyfill.min.js"></script>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: rgba(0, 0, 0, 0);
        }
        #target {
            width: 300px;
            height: 300px;
        }


    </style>
</head>
<body>
<div id="target"></div>
<script>
    let writer;
    function clearCharacter() {
        // 如果已存在 writer 实例,则删除
        if (writer) {
            writer.cancelQuiz(); // 取消正在进行的测试(如果有)
            writer.setCharacter(''); // 将汉字设置为空
            document.getElementById('target').innerHTML = ''; // 清空目标区域
            writer = null; // 将 writer 实例置为 null
        }
    }

    function loadCharacter(character,wide,height) {
        clearCharacter()
        writer = HanziWriter.create('target', character, {
            width: 300,
            height: 300,
            showOutline: true,
            showCharacter: false,
            drawingColor:'#168F16',
            drawingWidth:30,
        });
    }

    function startQuiz() {
        if (writer) {
           writer.quiz({
              onMistake: function(strokeData) {
                AndroidInterface.onMistake(JSON.stringify(strokeData));
              },
              onCorrectStroke: function(strokeData) {
                AndroidInterface.onCorrectStroke(JSON.stringify(strokeData));
              },
              onComplete: function(summaryData) {
                AndroidInterface.onComplete(JSON.stringify(summaryData));
              }
           });
        }
    }

    function getSVG() {
         if (writer) {
            const path = writer.getPath();
            const svg = path.toSVG();
            AndroidInterface.showSVG(svg);
        }
    }

    function init() {
        loadCharacter('');
    }

    window.onload = init;

     // 新增函数,用于从Android接收汉字
    function receiveCharacterFromAndroid(character) {
        loadCharacter(character);
    }


</script>
</body>
</html>

在Android中调用

        webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        webView.loadUrl( "file:///android_asset/hanziwriter1.html")
        webView.postDelayed({
            Log.i("AAAA","加载汉字----")
            loadCharacterFromAndroid("慕")
            webView.evaluateJavascript(
                "startQuiz()", null
            )
        }, 500)

Js回调。

    private class JavaScriptInterface(private val context: Context) {
        @JavascriptInterface
        fun onMistake(data: String) {
            Log.i("AAAAA","onMistake: $data")
            val jsonObject = JSONObject(data)
            val character = jsonObject.getString("character")
            val drawPath = jsonObject.getString("drawnPath") //使用 drawPath对比圈画区域是否重叠
            val jsonPath = JSONObject(drawPath)
            val pathString = jsonPath.getString("pathString")
            val strPoint = StringBuilder()
            jsonPath.getJSONArray("points").let {
                for (i in 0 until it.length()) {
                    val jsonObject = it.getJSONObject(i)
                    val x = jsonObject.getDouble("x")
                    val y = jsonObject.getDouble("y")
                    strPoint.append("$x,$y ")
                }
            }

            val totalMistakes = jsonObject.getInt("totalMistakes")
            val strokeNum = jsonObject.getInt("strokeNum") + totalMistakes
            val mistakesOnStroke = jsonObject.getInt("mistakesOnStroke")
            val strokesRemaining = jsonObject.getInt("strokesRemaining")
            uiThread {
                if (context is MainActivity) {
                    context.takeBitmap()
                    context.updateTextView("当前汉字:$character, 一共写了${strokeNum}笔, 错误笔顺${totalMistakes}笔, 剩余笔顺${strokesRemaining}笔")
                    context.drawSVGImage(pathString, strPoint.toString())
                }
            }

        }

        @JavascriptInterface
        fun onCorrectStroke(data: String) {
            Log.i("AAAAA","-onCorrectStroke: $data")
            val jsonObject = JSONObject(data)
            val character = jsonObject.getString("character")
            val drawPath = jsonObject.getString("drawnPath")//使用 drawnPath对比圈画区域是否重叠
            val jsonPath = JSONObject(drawPath)
            val pathString = jsonPath.getString("pathString")
            val strPoint = StringBuilder()
            jsonPath.getJSONArray("points").let {
                for (i in 0 until it.length()) {
                    val jsonObject = it.getJSONObject(i)
                    val x = jsonObject.getDouble("x")
                    val y = jsonObject.getDouble("y")
                    strPoint.append("$x,$y,")
                }
            }

            val totalMistakes = jsonObject.getInt("totalMistakes")
            val strokeNum = jsonObject.getInt("strokeNum") + totalMistakes
            val mistakesOnStroke = jsonObject.getInt("mistakesOnStroke")
            val strokesRemaining = jsonObject.getInt("strokesRemaining")
            uiThread {
                if (context is MainActivity) {
                    context.takeBitmap()
                    context.updateTextView("当前汉字:$character, 一共写了${strokeNum}笔, 错误笔顺${totalMistakes}笔, 剩余笔顺${strokesRemaining}笔")
                    context.drawSVGImage(pathString, strPoint.toString())
                }
            }

        }

        @JavascriptInterface
        fun onComplete(data: String) {
            Log.i("AAAAA","onComplete: $data")
            val jsonObject = JSONObject(data)
            val character = jsonObject.getString("character")

            if (context is MainActivity) {
                context.updateTextView("当前汉字:$character, 笔顺完成")
            }
        }
    }

HanziWriter官方API

1.创建新HanziWriter实例需要传入目标 div(ID 或 DOM 引用)、要呈现的字符以及配置选项。下面是一个简单的示例。在 Javascript 中:

     function loadCharacter(character) {
            clearCharacter()
            writer = HanziWriter.create('target', character, {
                width: 300,
                height: 300,
                showOutline: true,
                showCharacter: false,
                drawingColor:'#168F80',
                drawingWidth:30,
            });
            writer.quiz({
                  onMistake: function(strokeData) {
                    AndroidInterface.onMistake(JSON.stringify(strokeData));
                  },
                  onCorrectStroke: function(strokeData) {
                    AndroidInterface.onCorrectStroke(JSON.stringify(strokeData));
                  },
                  onComplete: function(summaryData) {
                    AndroidInterface.onComplete(JSON.stringify(summaryData));
                  }
               });
        }

可以看到一下结果

2.创建 HanziWriter 实例后,您可以通过调用 animateCharacter()方法为其设置动画。

 function animateCharacter() {
            if (writer) {
               writer.animateCharacter({
               AndroidInterface.start());
               });
            }
        }

3.除了动画和测验的核心功能外,还有其他几种方法可用于控制角色的渲染。

writer.setCharacter(newCharacter) 加载新角色并重新渲染。
writer.showCharacter() 如果角色当前处于隐藏状态,则显示该角色
writer.hideCharacter() 如果当前显示该角色,则隐藏该角色
writer.showOutline() 如果字符轮廓当前处于隐藏状态,则显示该轮廓
writer.hideOutline() 如果当前显示字符轮廓,则隐藏它
writer.updateColor(colorName, newValue) 更改任意颜色选项的值。例如:writer.updateColor('strokeColor', '#AA12CD')
writer.cancelQuiz() 立即取消当前正在进行的测验

4.options包含其他配置选项的对象。可用的完整选项包括:

showOutline:布尔值,默认为 true。控制在第一次渲染时是否显示或隐藏轮廓。
showCharacter:布尔值,默认为 true。控制在第一次渲染时是否显示或隐藏字符。
width:数字。画布的宽度(以 px 为单位)。
height:数字。画布的高度(以 px 为单位)。
padding:数字,默认值为 20。字符和画布边缘之间的填充(单位为 px)。
strokeAnimationSpeed:数字,默认为 1。绘制每个笔触的速度必须大于 0。增加此数字可加快绘制笔触的速度,减少此数字可减慢绘制笔触的速度。
strokeHighlightSpeed:数字,默认为 2。在测验中给出提示时突出显示每个笔画的速度必须大于 0。增加此数字可加快突出显示速度,减少此数字可减慢突出显示速度。
strokeFadeDuration:数字,默认为 400。调用和隐藏笔画之间的转换时间(以毫秒为单位writer.show())writer.hide()
delayBetweenStrokes:数字,默认为 1000。动画时每次描边之间的时间(以毫秒为单位)。
delayBetweenLoops:数字,默认为 2000。循环动画时,每个动画循环之间的时间(以毫秒为单位)。
strokeColor:十六进制字符串,默认为“#555”。绘制每条笔画的颜色。
radicalColor:十六进制字符串,默认为 null。如果存在部首数据,则在笔画中绘制部首的颜色。如果未设置,部首将以与其他笔画相同的颜色绘制。
highlightColor:十六进制字符串,默认为“#AAF”。测验中用于突出显示的颜色。
outlineColor:十六进制字符串,默认为“#DDD”。字符轮廓的颜色。
drawingColor:十六进制字符串,默认为 '#333'。用户在测验期间绘制的线条的颜色。
drawingWidth:数字,默认4。用户在答题时绘制的线条宽度,单位为px。
showHintAfterMisses:整数,默认为 3。在向用户提供笔画高亮提示之前未完成的次数。设置为 false 以禁用。创建测验时也可以设置此项。
markStrokeCorrectAfterMisses:整数,默认禁用。强制将笔画标记为正确之前的未命中次数。这也可以在创建测验时设置。
quizStartStrokeNum:整数,默认为 0。可将此设置为从第一个笔划以外的笔划开始测验。也可在创建测验时设置。
acceptBackwardsStrokes:布尔值,默认 false。允许在测验期间向后绘制笔画。这也可以在创建测验时设置。
highlightOnComplete:布尔值,默认为 true。控制测验在用户完成绘制整个角色时是否短暂突出显示该角色。这也可以在创建测验时设置。
highlightCompleteColor:十六进制字符串,默认为 null。在测验中突出显示完成的字符时使用的颜色。如果未设置,highlightColor则将改为使用。仅当 时才highlightOnComplete相关true。
charDataLoader:函数。自定义函数用于加载字符数据。有关用法的更多信息,请参阅“加载字符数据”部分。
onLoadCharDataSuccess:函数。角色数据加载成功时的回调。此函数使用已加载的数据进行调用。这可用于实现加载微调器。
onLoadCharDataError:函数。字符数据加载失败时的回调。无论失败原因是什么,都会从charDataLoader传递此函数。
renderer:字符串,默认为“svg”。将其设置为“canvas”以使用 2d 画布而不是 SVG 进行渲染。在某些设备上可能会有更好的性能。

附上我的githubdemo地址。

PS:HanziWriter地址

github地址

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1955838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

复杂系统的动态演化与自相似性探究——揭示系统内部的结构与行为模式

复杂系统的动态演化与自相似性探究——揭示系统内部的结构与行为模式 动态演化与自相似性的核心思想 想象一下&#xff0c;你正在观察一棵树的生长。随着时间的推移&#xff0c;树会不断长高&#xff0c;长出新的叶子和枝条。这就是动态演化。同时&#xff0c;你会发现树的每一…

甄选范文“论企业集成架构设计及应用”软考高级论文,系统架构设计师论文

论文真题 论企业集成架构设计及应用企业集成架构(Enterprise Integration Arhitecture,EIA) 是企业集成平台的核心,也是解决企业信息孤岛问题的关键。企业集成架构设计包括了企业信息、业务过程、应用系统集成架构的设计。实现企业集成的技术多种多样,早期的集成方式是通过…

LexLIP——图片搜索中的多模态稀疏化召回方法

LexLIP——图片搜索中的多模态稀疏化召回方法 FesianXu 20240728 at WeChat Search Team 前言 最近笔者在回顾&笔记一些老论文&#xff0c;准备整理下之前看的一篇论文LexLIP&#xff0c;其很适合在真实的图片搜索业务场景中落地&#xff0c;希望笔记能给读者带来启发。如…

业务记录:处理动态表头的CSV/EXCEL文件

业务描述&#xff1a;传入一个动态表头的CSV文件&#xff0c;解析CSV&#xff0c;并保存入库。 CSV文件的表头是不确定的&#xff0c;即顺序和字段个数不确定&#xff0c;以及表头是中文字段。 例如&#xff1a; 为了顺利解析CSV文件&#xff0c;前端需要传入对应的字段名和顺…

Qwen-VL全文翻译(from GPT-4o)

目录 Abstract1 Introduction2 Methodology2.1 Model Architecture2.2 Inputs and Outputs 3 Training3.1 Pre-training3.2 Multi-task Pre-training3.3 Supervised Fine-tuning 4 Evaluation4.1 Image Caption and General Visual Question Answering4.2 Text-oriented Visual…

01 Go Web基础_20240728 课程笔记

概述 如果您没有Golang的基础&#xff0c;应该学习如下前置课程。 基础不好的同学每节课的代码最好配合视频进行阅读和学习&#xff0c;如果基础比较扎实&#xff0c;则阅读本教程巩固一下相关知识点即可&#xff0c;遇到不会的知识点再看视频。 视频课程 最近发现越来越多…

【算法专题】双指针算法之18. 四数之和(力扣)

欢迎来到 CILMY23的博客 &#x1f3c6;本篇主题为&#xff1a;双指针算法之18. 四数之和&#xff08;力扣&#xff09; &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算…

网络安全威胁情报是什么,它对代工生产(OEM)意味着什么?

随着汽车数字环境的不断变化&#xff0c;网络安全基础设施及其面临的威胁也日趋复杂。 为了更好地识别、理解并最终预防这些风险&#xff0c;网络安全威胁情报&#xff08;CTI&#xff09;的管理应是一个综合多方面的过程。 以下是CTI对OEM的意义&#xff0c;以及如何利用网络…

代码随想录算法训练营第40天|LeetCode 198.打家劫舍、213.打家劫舍II、337.打家劫舍III

1. LeetCode 198.打家劫舍 题目链接&#xff1a;https://leetcode.cn/problems/house-robber/ 文章链接&#xff1a;https://programmercarl.com/0198.打家劫舍.html#算法公开课 视频链接&#xff1a;https://www.bilibili.com/video/BV1Te411N7SX 思路&#xff1a; 递推公式&a…

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

相机镜头移动特效视频转场模板Pr工程文件

Pr转场模板&#xff0c;相机镜头移动特效视频转场Pr工程文件 逼真的相机移动&#xff1a;具有一系列动态相机移动功能&#xff0c;包括平移、倾斜、缩放和旋转&#xff0c;为您的过渡添加逼真和引人入胜的视觉元素。 无缝集成&#xff1a;以.prproj文件形式提供&#xff0c;便…

操作系统——进程深度理解

目录 一、操作系统 1、概念 2、操作系统的结构 3、操作系统的理解 二、进程 一、操作系统 1、概念 操作系统是一个软件&#xff0c;一款进行软硬件资源管理的软件 电脑开机的时间&#xff0c;就是把操作系统加载到内存并运行的过程。 对操作系统广义的认识&#xff1a;…

PowerBI 度量值不被切片器筛选

我们有这样一张表&#xff1a; 我们用一个切片器绑定奖金表[奖金]&#xff0c;就可以用表格来联动显示数据: 现在用户有个新的需求&#xff0c;当单选某个奖金时&#xff0c;需要统计小于这个奖金数的人数。 我用了一个度量值来统计&#xff1a; 度量值 VAR selected_bonus…

全国区块链职业技能大赛样题第9套后端源码

后端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746050 前端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746216 智能合约+数据库表设计:https://blog.csdn.net/Qhx20040819/article/details/140746646 项目预览 登录 用户管理

vue3 快速入门 (五) : Flex布局

1. 如何变成Flex布局 变成Flex容器&#xff0c;只需在容器布局的节点的CSS中&#xff0c;增加display : flex .mylayout {/* 省略了其他代码 */display: flex; }2. flex direction : 方向 row : 以行排列 row-reverse &#xff1a; 以行反向排列 column &#xff1a;以列排列…

向日葵RCE复现(CNVD-2022-10270/CNVD-2022-03672)

一、环境 1.1 网上下载低版本的向日葵<2022 二、开始复现 2.1 在目标主机上打开旧版向日葵 2.2 首先打开nmap扫描向日葵主机端口 2.3 在浏览器中访问ip端口号cgi-bin/rpc?actionverify-haras &#xff08;端口号&#xff1a;每一个都尝试&#xff0c;直到获取到session值…

AJAX之原理

目录 XMLHttpRequest基本使用查询参数数据提交&#xff08;注册账号&#xff09; Promise基本语法Promise三种状态Promise链式调用 async函数和await XMLHttpRequest 基本使用 XMLHttpRequest对象用于与服务器交互 与axios关系&#xff1a;axios内部采用XMLHttpRequest与服务…

Elasticsearch:跨集群使用 ES|QL

警告&#xff1a;ES|QL 的跨集群搜索目前处于技术预览阶段&#xff0c;可能会在未来版本中更改或删除。Elastic 将努力解决任何问题&#xff0c;但技术预览中的功能不受官方 GA 功能的支持 SLA 约束。 使用 ES|QL&#xff0c;你可以跨多个集群执行单个查询。 前提&#xff1a; …

每天一个设计模式之命令模式(第二天)

交互模式中的命令模式&#xff0c;在开始记录之前&#xff0c;要讲些自己的感受&#xff0c;真真切切的感受到了悟性的瓶颈&#xff01;一共十页书&#xff0c;需要自己细细琢磨品味&#xff0c;至少三四遍才大概了解了他们间的逻辑&#xff0c;我需要调整下自己的学习思路&…

Lumos学习王佩丰Excel第七讲:认识公式与函数

一、认识Excel公式 1、运算符 运算符 作用 算术运算符 加 - 减 * 乘 / 除 % 百分比&#xff0c;相当于除以100 & 连接符&#xff0c;文本数字均可连接 ^ 乘方 比较运算符 等于 > 大于 < 小于 ≤ 小于等于 ≥ 大于等于 例1&#xff1…