最近收到很多小伙伴反馈,想基于扩展的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: booleanprivate onInputValue: (result) => voidbuild() {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按钮,表达式和运算结果归0this.expression = ''this.result = ''return}// 输入数字,表达式直接拼接,计算运算结果this.expression += valuethis.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 += valuethis.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 += valuethis.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: stringbuild() {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 = indexthis.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 = valueif (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扩展的声明式开发范式开发出更多有趣的应用。

点击链接,可获取分布式计算器完整代码:app_samples: We provide a series of app samples to help you quickly get familiar with the APIs and app development process of the OpenHarmony SDKs. | 为帮助开发者快速熟悉OpenHarmony SDK所提供的API和应用开发流程,我们提供了一系列的应用示例 - Gitee.com

手把手教你实现基于eTS的分布式计算器相关推荐

  1. 手把手教你搭建Hadoop生态系统伪分布式集群

    Hello,我是 Alex 007,一个热爱计算机编程和硬件设计的小白,为啥是007呢?因为叫 Alex 的人太多了,再加上每天007的生活,Alex 007就诞生了. 手把手教你搭建Hadoop生态 ...

  2. 手把手教你:基于TensorFlow的语音识别系统

    系列文章 第十章.手把手教你:基于Django的用户画像可视化系统 第九章.手把手教你:个人信贷违约预测模型 第八章.手把手教你:基于LSTM的股票预测系统 目录 系列文章 一.项目简介 二.语音数据 ...

  3. 手把手教你:基于LSTM的股票预测系统

    系列文章 第七章.手把手教你:基于深度残差网络(ResNet)的水果分类识别系统 第六章.手把手教你:人脸识别的视频打码 第五章.手把手教你:基于深度学习的滚动轴承故障诊断 目录 系列文章 一.项目简 ...

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

    转自即时通讯网:http://www.52im.net/ 本文原作者:"水晶虾饺",原文由"玉刚说"写作平台提供写作赞助,原文版权归"玉刚说" ...

  5. 手把手教你:基于深度残差网络(ResNet)的水果分类识别系统

    系列文章 手把手教你:图像识别的垃圾分类系统 手把手教你:人脸识别考勤系统 手把手教你:基于粒子群优化算法(PSO)优化卷积神经网络(CNN)的文本分类 目录 系列文章 一.项目简介 二.水果分类结果 ...

  6. 手把手教你:基于Django的用户画像可视化系统

    系列文章 第九章.手把手教你:个人信贷违约预测模型 第八章.手把手教你:基于LSTM的股票预测系统 第七章.手把手教你:基于深度残差网络(ResNet)的水果分类识别系统 目录 系列文章 一.项目简介 ...

  7. 【手把手教你】基于均线排列的价格动量策略回测

    01 引言 动量策略是通过利用市场波动从现有趋势的延续中获利.简单来说,高买高卖,反之亦然("低买高卖"是均值回归策略思想,与动量策略相反).价值投资通常基于均值的长期回归,而动量 ...

  8. stm32g4 下载算法_手把手教你制作基于IAR、STM32H7的下载算法

    本文由作者『Lucas』原创并授权发布,地址: https://blog.csdn.net/lin_duo/article/details/110754189下载算法对于大部分工程师来说,只需要会使用 ...

  9. 手把手教你实现基于LSTM的情感分析(LSTM-based Sentiment) Classification

    首先推荐一个Jupyter环境,是由Google提供的colab(https://colab.research.google.com/),有免费的GPU可以使用 第一次使用需要在实验环境中下载相关的p ...

  10. 手把手教你开发基于深度学习的人脸识别【考勤/签到】系统

    人脸识别介绍 平台环境需求 技术点 系统流程 细节设计 人脸检测 人脸关键点定位 人脸特征提取 模型的训练 模型的部署 MySQL数据库的使用 MFC工程的搭建 软件使用 人脸识别介绍 人脸识别技术是 ...

最新文章

  1. Tomcat 集群问题
  2. JAVAC 命令详解
  3. (转)Unity3d UnityEditor编辑器定制和开发插件
  4. 源码安装NASM,无root权限
  5. .NET core3.0 使用Jwt保护api
  6. icoding复习3
  7. 【渝粤教育】国家开放大学2019年春季 2773特种动物养殖 参考试题
  8. Windows Mobile 编程 (Win32) - 获取设备能力
  9. 世界级数学家加入华为,曾获菲尔兹奖 网友:终于知道数理化的重要了
  10. SpringCloud工作笔记045---SpringCloud分布式服务部署常用端口
  11. POSTMAN list参数传值
  12. oracle中ccuser,oracle数据库user profile设置方法
  13. treegrid修改css
  14. static_cast,reinterpret_cast,const_cast,dynamic_cast:
  15. 彻底安装oracle数据库,安装 Oracle 数据库软件
  16. 使用C++实现n阶行列式的计算
  17. java猜拳_java实现猜拳小游戏
  18. 动态路由协议之OSPF基本原理、区域划分及配置
  19. 实验11-1-7 藏头诗 (15分)
  20. oracle导入.dmp,oracle导入.dmp脚本

热门文章

  1. 短期工作经历到底要不要写到简历上?
  2. 手机qq邮箱html,手机QQ邮箱在哪里找
  3. win7文件服务器代码,win7搭建文件服务器
  4. UOJ #449. 【集训队作业2018】喂鸽子
  5. ubuntu14.04自定义系统默认xp字体
  6. seo人员必须知道的基础知识
  7. Unity3d发布WebGL打包AssetBundle的材质球丢失问题
  8. for循环小技巧,遍历数组的时候要使用恰当
  9. 分享个网站首页弹窗代码
  10. 有关苹果手机下载应用后提示不受信任的企业开发者解决方案: