满满干货!手把手教你实现基于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进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
随机推荐
- HTML5打造原生应用——Ionic框架简介与Ionic Hello World
试了试用Ionic框架打造了两个应用,然后在Google Play上架了. 程序语言答人 教你设计物联网 更有意思的是这是在一周的业余时间内完成的三个应用中的两个,接着让我们看看这个框架如何实现高效地 ...
- 使用 Vuex + Vue.js 构建单页应用【新篇】
使用 Vuex + Vue.js 构建单页应用[新篇] 在去年的七月六号的时候,发布了一篇 使用 Vuex + Vue.js 构建单页应用 的文章,文章主要是介绍 vuex 的基本使用方法,发现对大部 ...
- java栈stack和堆heap的工作原理,用途及区别?举例说明
java堆和栈的区别[新手可忽略不影响继续学习] Java中内存分成两种:一种是栈stack,一种是堆heap.函数中的一些基本类型的变量(int, float)和对象的引用变量(reference) ...
- 微信jssdk分享(附代码)
老规矩---demo图: (注释:微信分享只支持右上角三个点触发) ======> 老规矩上菜: 1. 这边我封装了 share.js function allSharefun(param) ...
- 微信小程序拖动列表功能
WXML部分 1 <view class="index"> 2 3 <!-- 数据展示区 --> 4 <scroll-view 5 class=&qu ...
- drf中的请求与响应
请求与响应(3星) 请求:Request REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了Ht ...
- 2021-01-25 cf #697 Div3 C题(超时,换思路减少复杂度)
题目链接:https://codeforces.com/contest/1475/problem/C 题意要求:需组成的2对,男的序号不能重,女的序号不能重 比如这例 输入: 行1--测试个数 行1` ...
- VisualStudio安装步骤
1.下载vs2017,点击安装 2.选择asp.net选项进行安装,如果需要其他的功能,可以选上 3.更改安装路径,尽量把文件安装在c盘以外的盘上,因为c盘是系统盘,安装的东西越多电脑会越卡.注意:不 ...
- partTwo自动出题程序第三阶段
课堂测试3: 2.可定制(数量/打印方式):输入大的数量值,测试一下系统是否崩溃,反向查找系统是否优化的余地: 3.定制操作数的个数: 4.定制是否有乘除法 5.定制是否有括号(随机加入) 6 .定制 ...
- kubeadm 搭建 K8s
kubeadm 搭建 K8s 本篇主要记录一下 使用 kubeadm 搭建 k8s 详细过程 ,环境使用 VirtualBox 构建的3台虚拟机 1.环境准备 操作系统:Centos7 (CentOS ...