满满干货!手把手教你实现基于eTS的分布式计算器
最近收到很多小伙伴反馈,想基于扩展的TS语言(eTS)进行HarmonyOS应用开发,但是不知道代码该从何处写起,从0到1的过程让新手们抓狂。
本期我们将带来“分布式计算器”的开发,帮助大家了解声明式开发范式的UI描述、组件化机制、UI状态管理、渲染控制语法等核心机制和功能。下面我们直接进入正题。
一、整体介绍
分布式计算器可以进行简单的数值计算,并支持远程拉起另一个计算器FA,实现两个FA进行协同计算。

如图1所示,分布式计算器界面主要由“键盘”、“显示”及“标题栏”三个模块组成。其中,“键盘”与“显示”模块负责响应用户点击并控制运算表达式及运算结果的显示,实现了基础的计算功能。“菜单栏”模块为计算器顶部的菜单栏,是分布式计算功能的入口。
那么,如何实现分布式计算器各模块的功能?下面我们将从组件化、声明式描述和状态管理三个维度来解析分布式计算器的实现。
图1 计算器界面
1. 组件化
ArkUI开发框架定义了一些具有特殊含义的组件管理装饰器,如图2所示:
图2 组件管理装饰器
根据声明式UI的组件化思想,我们可以将通过组件管理装饰器将计算器界面上的各个模块组件化为一个个独立的UI单元。
2. 声明式描述
通过ArkUI开发框架提供的一系列基础组件,如Column、Text、Divider、Button等,以声明方式进行组合和扩展来对各个模块进行描述,包括参数构造配置、属性配置、事件配置以及子组件配置等,并通过基础的数据绑定和事件处理机制实现各个模块的逻辑交互。
3. 状态管理
ArkUI开发框架定义了一些具有特殊含义的状态管理装饰器,如图3所示:
图3 状态管理装饰器
通过状态管理装饰器装饰组件拥有的状态属性,当装饰的变量更改时,组件会重新渲染更新UI界面。
以上就是实现分布式计算器的核心原理,下面我们将为大家带来分布式计算器的基础计算功能与分布式功能的具体实现。
二、基础计算功能的实现
上文中提到,分布式计算器的基础计算功能由键盘模块及显示模块实现。
1. 键盘模块
键盘模块响应了用户的点击,并实现了计算器的基本功能。下面我们将介绍键盘布局以及键盘功能的实现。
(1) 键盘布局
计算器界面上的键盘,其实是一张张图片按照 4*5格式排列,如图4所示:
图4 键盘模块
首先,我们需要将所有图片保存至项目的media文件夹下,并初始化为ImageList,代码如下:
export function obtainImgVertical(): Array<Array<ImageList>> {
let list =
[
[
{ img: $r('app.media.ic_cal_seven'), value: '7' },
{ img: $r('app.media.ic_cal_eight'), value: '8' },
{ img: $r('app.media.ic_cal_nine'), value: '9' }
],
[
{ img: $r('app.media.ic_cal_four'), value: '4' },
{ img: $r('app.media.ic_cal_five'), value: '5' },
{ img: $r('app.media.ic_cal_six'), value: '6' }
],
]
return list
}
export function obtainImgV(): Array<ImageList> {
let list =
[
{ img: $r('app.media.ic_cal_delete'), value: '' },
{ img: $r('app.media.ic_cal_minus'), value: '-' },
{ img: $r('app.media.ic_cal_plus'), value: '+' },
{ img: $r('app.media.ic_cal_equal'), value: '=' }
]
return list
}
然后,我们需要对键盘模块进行组件化操作。这里我们通过@Component装饰器让键盘模块成为一个独立的组件。
最后,使用ArkUI开发框架提供的内置组件及属性方法进行声明性描述。这里我们使用了Grid组件进行布局,并通过ForEach组件来迭代图片数组实现循环渲染,同时还为每张图片添加了ClickButton事件方法。代码如下:
@Component
export struct ButtonComponent {
private isLand: boolean
private onInputValue: (result) => void
build() {
Row() {
Grid() {
ForEach(obtainImgV(), (item, index) => {
GridItem() {
Image(item.Img)
.margin({ top: 5 })
.onClick(() => {
this.onInputValue(item.value)
})
}
.rowStart(index)
.rowEnd(index === 3 ? index + 1 : index)
.columnStart(3)
.columnEnd(3)
})
ForEach(obtainImgVertical(), (item) => {
ForEach(item, (item) => {
GridItem() {
Image(item.Img)
.margin({ top: 5 })
.onClick(() => {
this.onInputValue(item.value)
})
}
})
})
}
}
}
}
(2) 功能实现
按键功能包含了“归零”、“清除”、“计算”三个功能。
① 当用户点击“C”按钮后,运算表达式与运算结果“归零”,代码如下:
onInputValue = (value) => {
if (value === 'C') { // 当用户点击C按钮,表达式和运算结果归0
this.expression = ''
this.result = ''
return
}
// 输入数字,表达式直接拼接,计算运算结果
this.expression += value
this.result = JSON.stringify(MATH.evaluate(this.expression))
}
② 当用户点击“X”按钮后,删除运算表达式的最后一个字符。代码如下:
onInputValue = (value) => {
if (value === '') { // 当用户点击删除按钮,表达式删除上一次的输入,重新运算表达式
this.expression = this.expression.substr(0, this.expression.length - 1)
this.result = JSON.stringify(MATH.evaluate(this.expression))
return
}
if (this.isOperator(value)) { // 当用户输入的是运算符
// 判断表达式最后一个字符是运算符则覆盖上一个运算符,否则表达式直接拼接
if (this.isOperator(this.expression.substr(this.expression.length - 1, this.expression.length))) {
this.expression = this.expression.substr(0, this.expression.length - 1)
this.expression += value
} else {
this.expression += value
}
return
}
// 输入数字,表达式直接拼接,计算运算结果
this.expression += value
this.result = JSON.stringify(MATH.evaluate(this.expression))
}
③ 当用户点击“=”按钮后,将调用JavaScript的math.js库对表达式进行计算。代码如下:
import { create, all } from 'mathjs'
onInputValue = (value) => {
if (value === '=') { // 当用户点击=按钮
this.result = ''
// 判断表达式最后一个字符是运算符,运算结果需要去掉最后一个运算符运算,否则直接运算
if (this.isOperator(this.expression.substr(this.expression.length - 1, this.expression.length))) {
this.expression = JSON.stringify(MATH.evaluate(this.expression.substr(0, this.expression.length - 1)))
} else {
this.expression = JSON.stringify(MATH.evaluate(this.expression))
}
return
}
// 输入数字,表达式直接拼接,计算运算结果
this.expression += value
this.result = JSON.stringify(MATH.evaluate(this.expression))
}
注:计算功能的实现依赖于JavaScript的math.js库,使用前需通过npm install mathjs--save命令下载并安装math.js库。
2. 显示模块
显示模块实现了“键入的运算表达式”与“运算结果”的显示,本质上是Text文本,如图5所示:
图5 显示模块
首先我们通过@Component装饰器使该模块具有组件化能力,然后在build方法里描述UI结构,最后使用@Link状态装饰器管理组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。代码如下:
@Component
export struct InPutComponent {
private isLand: boolean
@Link result: string
@Link expression: string
build() {
Stack({ alignContent: this.isLand ? Alignment.BottomStart : Alignment.TopEnd }) {
Column() {
//运算表达式文本框
Scroll() {
Text(this.expression)
.maxLines(1)
.opacity(0.9)
.fontWeight(400)
.textAlign(TextAlign.Start)
.fontSize(this.isLand ? 50 : 35)
}
.width('90%')
.scrollable(ScrollDirection.Horizontal)
.align(this.isLand ? Alignment.Start : Alignment.End)
//运算结果文本框
Scroll() {
Text(this.result)
.maxLines(1)
.opacity(0.38)
.textAlign(TextAlign.Start)
.fontSize(this.isLand ? 45 : 30)
.margin(this.isLand ? { bottom: 64 } : {})
}
}
}
}
}
至此,一个初具计算功能的计算器就实现了。下面我们将实现计算器的分布式功能。
三、分布式功能的实现
计算器的分布式功能以菜单栏模块为入口,并基于分布式设备管理与分布式数据管理技术实现。
1. 菜单栏模块
“菜单栏”模块为计算器顶部菜单栏,是计算器分布式功能的入口。
图6 菜单栏模块
如图6所示,当用户点击图标 时,执行terminate()方法,退出计算器应用。当用户点击 按钮时,执行showDialog()方法,界面上弹出的分布式设备列表弹窗,选择设备后将获取分布式数据管理的权限,最后实现远端设备的拉起。代码如下:
@Component
export struct TitleBar {
build() {
Row() {
Image($r("app.media.ic_back"))
.height('60%')
.margin({ left: 32 })
.width(this.isLand ? '5%' : '8%')
.objectFit(ImageFit.Contain)
//执行terminate()方法,退出计算器应用
.onClick(() => {
app.terminate()
})
Blank().layoutWeight(1)
Image($r("app.media.ic_hop"))
.height('60%')
.margin({ right: 32 })
.width(this.isLand ? '5%' : '8%')
.objectFit(ImageFit.Contain)
//执行showDialog()方法,界面上弹出的分布式设备列表弹窗
.onClick(() => {
this.showDiainfo()
})
}
.width('100%')
.height(this.isLand ? '10%' : '8%')
.constraintSize({ minHeight: 50 })
.alignItems(VerticalAlign.Center)
}
}
2. 分布式设备管理
在分布式计算器应用中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。
(1) 分布式设备搜索
通过SUBSCRIBE_ID搜索分布式组网内的远端设备,代码如下:
startDeviceDiscovery() {
SUBSCRIBE_ID = Math.floor(65536 * Math.random())
let info = {
subscribeId: SUBSCRIBE_ID,
mode: 0xAA,
medium: 2,
freq: 2,
isSameAccount: false,
isWakeRemote: true,
capability: 0
}
Logger.info(TAG, `startDeviceDiscovery ${SUBSCRIBE_ID}`)
this.deviceManager.startDeviceDiscovery(info)
}
(2) 分布式设备列表弹窗
分布式设备列表弹窗实现了远端设备的选择,如图7所示,用户可以根据设备名称选择相应的设备进行协同计算。
图7 分布式设备列表弹窗
这里我们使用@CustomDialog装饰器来装饰分布式设备列表弹窗,代码如下:
@CustomDialog
export struct DeviceDialog {
build() {
Column() {
List() {
ForEach(this.deviceList, (item, index) => {
ListItem() {
Row() {
Text(item.deviceName)
.fontSize(21)
.width('90%')
.fontColor(Color.Black)
Image(index === this.selectedIndex ? $r('app.media.checked') : $r('app.media.uncheck'))
.width('8%')
.objectFit(ImageFit.Contain)
}
.height(55)
.margin({ top: 17 })
.onClick(() => {
if (index === this.selectedIndex) {
return
}
this.selectedIndex = index
this.onSelectedIndexChange(this.selectedIndex)
})
}
}, item => item.deviceName)
}
}
}
}
(3) 远端设备拉起
通过startAbility(deviceId)方法拉起远端设备的FA,代码如下:
startAbility(deviceId) {
featureAbility.startAbility({
want: {
bundleName: 'ohos.samples.DistributeCalc',
abilityName: 'ohos.samples.DistributeCalc.MainAbility',
deviceId: deviceId,
parameters: {
isFA: 'FA'
}
}
}).then((data) => {
this.startAbilityCallBack(DATA_CHANGE)
})
}
3. 分布式数据管理
分布式数据管理用于实现协同计算时数据在多端设备之间的相互同步。我们需要创建一个分布式数据库来保存协同计算时数据,并通过分布式数据通信进行同步。
(1) 管理分布式数据库
创建一个KVManager对象实例,用于管理分布式数据库对象。代码如下:
async createKvStore(callback) {
//创建一个KVManager对象实例
this.kvManager = await distributedData.createKVManager(config)
let options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: 1,
securityLevel: 1,
}
// 通过指定Options和storeId,创建并获取KVStore数据库,并通过Promise方式返回,此方法为异步方法。
this.kvStore = await this.kvManager.getKVStore(STORE_ID, options)
callback()
}
(2) 订阅分布式数据变化
通过订阅分布式数据库所有(本地及远端)数据变化实现数据协同,代码如下:
kvStoreModel.setOnMessageReceivedListener(DATA_CHANGE, (value) => {
if (this.isDistributed) {
if (value.search(EXIT) != -1) {
Logger.info(TAG, `EXIT ${EXIT}`)
featureAbility.terminateSelf((error) => {
Logger.error(TAG, `terminateSelf finished, error= ${error}`)
});
} else {
this.expression = value
if (this.isOperator(this.expression.substr(this.expression.length - 1, this.expression.length))) {
this.result = JSON.stringify(MATH.evaluate(this.expression.substr(0, this.expression.length - 1)))
} else {
this.result = JSON.stringify(MATH.evaluate(this.expression))
}
}
}
})
至此,具有分布式能力的计算器就实现了。期待广大开发者能基于TS扩展的声明式开发范式开发出更多有趣的应用。
点击链接,可获取分布式计算器完整代码:https://gitee.com/openharmony/app_samples/tree/master/Preset/DistributeCalc

搜索
复制
满满干货!手把手教你实现基于eTS的分布式计算器的更多相关文章
- 周一干货~手把手教你安装 Visual Studio 安卓模拟器
干货~手把手教你安装 Visual Studio 安卓模拟器 转 http://mini.eastday.com/mobile/171107134734194.html# 今天软妹为大家带来一篇来自M ...
- 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接
本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...
- 手把手教你写基于C++ Winsock的图片下载的网络爬虫
手把手教你写基于C++ Winsock的图片下载的网络爬虫 先来说一下主要的技术点: 1. 输入起始网址,使用ssacnf函数解析出主机号和路径(仅处理http协议网址) 2. 使用socket套接字 ...
- 庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境
庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境 一.介绍 说起微服务架构来,有一个环节是少不了的,那就是CI/CD持续集成的环境.当然,搭建CI/CD环境的工具很多, ...
- 干货 | 手把手教你搭建一套OpenStack云平台
1 前言 今天我们为一位朋友搭建一套OpenStack云平台. 我们使用Kolla部署stein版本的OpenStack云平台. kolla是用于自动化部署OpenStack的一个项目,它基于dock ...
- 手把手教你学会 基于JWT的单点登录
最近我们组要给负责的一个管理系统 A 集成另外一个系统 B,为了让用户使用更加便捷,避免多个系统重复登录,希望能够达到这样的效果--用户只需登录一次就能够在这两个系统中进行操作.很明显这就是单点登 ...
- Delphi - 手把手教你基于D7+Access常用管理系统架构的设计与实现 (更新中)
前言 从事软件开发工作好多年了,学的越深入越觉得自己无知,所以还是要对知识保持敬畏之心,活到老,学到老! 健身和代码一样都不能少,身体是革命的本钱,特别是我们这种高危工种,所以小伙伴们运动起来!有没有 ...
- 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
随机推荐
- windows本地搭建easy-mock环境
起因:由于easy-mock官网很不稳定,所以想搭建自己本地的mock环境 1.首先安装node.js 环境 (提供地址:https://nodejs.org/en/) 2.下载mongoDB 地址( ...
- java中异常(Exception)的定义,意义和用法。举例
1.异常(Exception)的定义,意义和用法 我们先给出一个例子,看看异常有什么用? 例:1.1- public class Test { public static void main(S ...
- Python中使用正则表达式获取两个字符中间部分
问题背景:当我们爬取网页信息时,对于一些标签的提取是没有意义的,所以需要提取标签中间的信息. 解决办法:用到了re包下的函数 方法1:用到了research()方法和group()方法 方法2:用到了 ...
- PAT 1048数字加密
本题要求实现一种数字加密方法.首先固定一个加密用正整数 A,对任一正整数 B,将其每 1 位数字与 A 的对应位置上的数字进行以下运算:对奇数位,对应位的数字相加后对 13 取余--这里用 J 代表 ...
- leetcode多线程题目
代码附上了力扣没显示出来的测试 按序打印 class Foo { private CountDownLatch latch = new CountDownLatch(1); private Count ...
- 小程序checkbox调整大小(checkbox样式修改)
.skyCheckbox{ transform: scale(0.7,0.7); -webkit-transform: scale(0.7,0.7); } <label class=" ...
- 安全市场迎来新挑战,FinClip助力车联网数据安全
随着汽车工业的发展与电子技术的进步,智能汽车迎来了前所未有的蓬勃发展,随着汽车电动化.网联化.智能化交融发展,车辆运行安全.数据安全和网络安全风险交织叠加,安全形势更加复杂严峻......
- RMI反序列化学习
RMI学习 1.RMI简介 RMI(Remote Method Invocation),远程方法调用方法,其实就是本地java虚拟机要调用其他java虚拟机的方法,两个虚拟机可以是运行在相同计算机上的 ...
- 6.Docker容器底层实现了解与安全机制
原文地址: 点击直达 0x00 底层实现 我们以 Docker 基础架构来探究Docke底层的核心技术,简单的包括: Linux 上的命名空间(Namespaces) 控制组(Control grou ...
- Machine Learning 学习笔记 03 最小二乘法、极大似然法、交叉熵
损失函数. 最小二乘法. 极大似然估计. 复习一下对数. 交叉熵. 信息量. 系统熵的定义. KL散度