草庐IT

满满干货!手把手教你实现基于eTS的分布式计算器

HarmonyOS开发者社区 2023-03-28 原文

最近收到很多小伙伴反馈,想基于扩展的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. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  3. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  4. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  5. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  6. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  7. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  8. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  9. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  10. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

随机推荐