最近收到很多小伙伴反馈,想基于扩展的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的分布式计算器的更多相关文章

  1. 周一干货~手把手教你安装 Visual Studio 安卓模拟器

    干货~手把手教你安装 Visual Studio 安卓模拟器 转 http://mini.eastday.com/mobile/171107134734194.html# 今天软妹为大家带来一篇来自M ...

  2. 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

    本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...

  3. 手把手教你写基于C++ Winsock的图片下载的网络爬虫

    手把手教你写基于C++ Winsock的图片下载的网络爬虫 先来说一下主要的技术点: 1. 输入起始网址,使用ssacnf函数解析出主机号和路径(仅处理http协议网址) 2. 使用socket套接字 ...

  4. 庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境

    庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境 一.介绍 说起微服务架构来,有一个环节是少不了的,那就是CI/CD持续集成的环境.当然,搭建CI/CD环境的工具很多, ...

  5. 干货 | 手把手教你搭建一套OpenStack云平台

    1 前言 今天我们为一位朋友搭建一套OpenStack云平台. 我们使用Kolla部署stein版本的OpenStack云平台. kolla是用于自动化部署OpenStack的一个项目,它基于dock ...

  6. 手把手教你学会 基于JWT的单点登录

      最近我们组要给负责的一个管理系统 A 集成另外一个系统 B,为了让用户使用更加便捷,避免多个系统重复登录,希望能够达到这样的效果--用户只需登录一次就能够在这两个系统中进行操作.很明显这就是单点登 ...

  7. Delphi - 手把手教你基于D7+Access常用管理系统架构的设计与实现 (更新中)

    前言 从事软件开发工作好多年了,学的越深入越觉得自己无知,所以还是要对知识保持敬畏之心,活到老,学到老! 健身和代码一样都不能少,身体是革命的本钱,特别是我们这种高危工种,所以小伙伴们运动起来!有没有 ...

  8. 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)

    1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理!   我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...

  9. 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)

    1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理!   我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...

随机推荐

  1. [译] 沙箱中的间谍 - 可行的 JavaScript 高速缓存区攻击

    原文 The Spy in the Sandbox – Practical Cache Attacks in Javascript 相关论文可在 https://github.com/wyvernno ...

  2. H5扇形

    使用H5 canvas绘制的可交互扇形 requestAnimationFrame() 现有动画实现方式的不足 setTimeout和setInterval都不十分精确.为它们传入的第二个参数,实际上 ...

  3. 用css动态实现圆环百分比分配——初探css3动画

    最近的小程序项目有个设计图要求做一个圆环,两种颜色分配,分别代表可用金额和冻结金额.要是就直接这么显示,感觉好像挺没水平??于是我决定做个动态! 在mdn把新特性gradients(渐变).trans ...

  4. sql server学习总结一

    一,数据库的三级模式结构 1.    模式 模式又称逻辑模式或者概念模式,是数据库中全体数据的逻辑结构和特征的描述,一个数据库只有一个模式,模式处于三级结构的中间层. 2.    外模式 外模式又称用 ...

  5. Python Turtle库绘制蟒蛇

    使用Python Turtle库来绘制蟒蛇 import turtle引入了海龟绘图体系 使用setup函数,设定了一个宽650像素和高350像素的窗体,其位置左上角坐标是200,200 说明位置在距 ...

  6. js知识梳理6:关于函数的要点梳理(2)(作用域链和闭包)

    写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者.有发现什么问题的,欢迎留言指出 ...

  7. 深入理解Kafka核心设计及原理(二):生产者

    转载请注明出处: 2.1Kafka生产者客户端架构 2.2 Kafka 进行消息生产发送代码示例及ProducerRecord对象 kafka进行消息生产发送代码示例: public class Ka ...

  8. Fabric2.2中的Raft共识模块源码分析

    引言 Hyperledger Fabric是当前比较流行的一种联盟链系统,它隶属于Linux基金会在2015年创建的超级账本项目且是这个项目最重要的一个子项目.目前,与Hyperledger的另外几个 ...

  9. 论文阅读 Continuous-Time Dynamic Network Embeddings

    1 Continuous-Time Dynamic Network Embeddings Abstract ​ 描述一种将时间信息纳入网络嵌入的通用框架,该框架提出了从CTDG中学习时间相关嵌入 Co ...

  10. 从.net开发做到云原生运维(八)——DevOps实践

    1. DevOps的一些介绍 DevOps(Development和Operations的组合词)是一组过程.方法与系统的统称,用于促进开发(应用程序/软件工程).技术运营和质量保障(QA)部门之间的 ...