1、前言

经过我们前面10章的学习,我们基本上可以开发出一个简单的APP了,为了巩固学习的内容,我们先开发一个计算器APP来连个手(文末有源代码),界面如下:

  • 包含基本的计算器运算功能

  • 支持一键清空,支持逐个删除数字

  • 支持将上次计算的过程保存下来,下次打开app继续;

  • 支持清理之前保存的记录

这里面我们会用到知识如下:

  1. 开发UI界面(基本控件的使用以及基本布局的使用);

  2. 监听用户点击事件,在输入栏中及时反馈(动态交互);

  3. 支持将过程持久化到本地,下次打开后自动恢复;

let's go!

2、创建项目 & 初始化

打开开发工具 DevEco-Studio,按照以下顺序创建项目,过程不再详述,看截图。

3、UI描述

我们的布局比较简单,分三个大块从上到下,分别是:

  1. 用户输入与计算结果显示部分;

  2. 核心数字键盘部分;

  3. 一个大的=按钮

示意图如下:

我们可以注意到,数字按钮的样式是一样的(蓝色),操作符按钮的样式也是一样的(绿色),三个功能按钮的样式也是一样的(红色),因此,我们可以使用鸿蒙的样式复用只是来减少重复代码量。

我们三部分复用的样式代码如下:

// 数字键盘的公共样式
@Extend(Button) function numberBtn(num: number, click: (num: number) => void) {
.type(ButtonType.Capsule)
.width(60)
.height(60)
.onClick(() => click(num))
}
// 运算符公共样式
@Extend(Button) function operatorBtn() {
.type(ButtonType.Capsule)
.backgroundColor(Color.Green)
.width(60)
.height(60)
} // 额外功能公共样式
@Extend(Button) function extraBtn() {
.type(ButtonType.Capsule)
.width(60)
.height(60)
.backgroundColor(Color.Red)
}

紧接着就是我们的UI描述部分代码,如下:

  build() {
Column() {
Column() {
Text(this.message)
.fontSize(20)
.width('100%')
.textAlign(TextAlign.End)
}
.padding(10)
.backgroundColor('gray')
.borderWidth(1)
.borderRadius(10)
.margin(10) Row() {
Button('7').numberBtn(7, this.onNumberClick)
Button('8').numberBtn(8, this.onNumberClick)
Button('9').numberBtn(9, this.onNumberClick)
Button('重置').extraBtn().onClick(this.onResetClick)
}
.justifyContent(FlexAlign.SpaceAround)
.numberRow() Row() {
Button('4').numberBtn(4, this.onNumberClick)
Button('5').numberBtn(5, this.onNumberClick)
Button('6').numberBtn(6, this.onNumberClick)
Button('删除').extraBtn().onClick(this.onDeleteClick) }
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('1').numberBtn(1, this.onNumberClick)
Button('2').numberBtn(2, this.onNumberClick)
Button('3').numberBtn(3, this.onNumberClick)
Button('清理').extraBtn().onClick(this.onClearClick)
}
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('%')
.type(ButtonType.Capsule)
.width(60)
.height(60)
Button('0').numberBtn(0, this.onNumberClick)
Button('.')
.type(ButtonType.Capsule)
.width(60)
.height(60)
.onClick(this.onDotClick)
Button('保存').operatorBtn().onClick(this.onSaveClick)
}
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('x')
.operatorBtn()
.onClick(() => this.onOperatorClick('x'))
Button('÷')
.operatorBtn()
.onClick(() => this.onOperatorClick('÷'))
Button('+')
.operatorBtn()
.onClick(() => this.onOperatorClick('+'))
Button('-')
.operatorBtn()
.onClick(() => this.onOperatorClick('-'))
}
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('=')
.width('100%')
.backgroundColor(Color.Pink)
.onClick(this.onCalcClick)
}
.margin(10)
}
.height('100%')
}

4、用户交互

紧接着,我们来实现用户的功能交互

  • 用户点击数字按钮时,监听器事件
  onNumberClick = (num: number) => {
if (this.message === '0') {
this.message = `${num}`;
} else {
this.message += `${num}`
}
}
  • 用户点击运算符按钮时的监听器事件
  onOperatorClick = (_opt: string) => {
this.message += _opt;
this.calcFinished = false;
}
  • 数据持久化和清空持久化数据
  onSaveClick = () => {
AppStorage.SetOrCreate('lastResult', this.message);
}
onClearClick = () => {
PersistentStorage.DeleteProp('lastResult')
this.message = '0';
}

其他的功能感兴趣可以在文末看详细的源代码。演示如下(包含了数据持久化,现场恢复的演示)

5、源代码

代码仓库地址:

https://gitee.com/lantingshuxu/harmony-calculator

速览核心的UI代码:

// 数字键盘的公共样式
@Extend(Button) function numberBtn(num: number, click: (num: number) => void) {
.type(ButtonType.Capsule)
.width(60)
.height(60)
.onClick(() => click(num))
}
// 运算符公共样式
@Extend(Button) function operatorBtn() {
.type(ButtonType.Capsule)
.backgroundColor(Color.Green)
.width(60)
.height(60)
}
// 额外功能公共样式
@Extend(Button) function extraBtn() {
.type(ButtonType.Capsule)
.width(60)
.height(60)
.backgroundColor(Color.Red)
} PersistentStorage.PersistProp('lastResult', '0'); type Operator = '+' | '-' | '*' | '/' | '(' | ')';
// 传入字符串,运算其结果(可以跳过)
function calculate(expression: string): number {
    //.. 这部分代码是计算用户输入结果d的,不重要,省略
} @Entry
@Component
struct Index {
@StorageProp('lastResult') message: string = '0'
calcFinished: boolean = true; @Styles numberRow() {
.width('100%')
.margin({ bottom: 10 })
} build() {
Column() {
Column() {
Text(this.message)
.fontSize(20)
.width('100%')
.textAlign(TextAlign.End)
}
.padding(10)
.backgroundColor('gray')
.borderWidth(1)
.borderRadius(10)
.margin(10) Row() {
Button('7').numberBtn(7, this.onNumberClick)
Button('8').numberBtn(8, this.onNumberClick)
Button('9').numberBtn(9, this.onNumberClick)
Button('重置').extraBtn().onClick(this.onResetClick)
}
.justifyContent(FlexAlign.SpaceAround)
.numberRow() Row() {
Button('4').numberBtn(4, this.onNumberClick)
Button('5').numberBtn(5, this.onNumberClick)
Button('6').numberBtn(6, this.onNumberClick)
Button('删除').extraBtn().onClick(this.onDeleteClick) }
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('1').numberBtn(1, this.onNumberClick)
Button('2').numberBtn(2, this.onNumberClick)
Button('3').numberBtn(3, this.onNumberClick)
Button('清理').extraBtn().onClick(this.onClearClick)
}
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('%')
.type(ButtonType.Capsule)
.width(60)
.height(60)
Button('0').numberBtn(0, this.onNumberClick)
Button('.')
.type(ButtonType.Capsule)
.width(60)
.height(60)
.onClick(this.onDotClick)
Button('保存').operatorBtn().onClick(this.onSaveClick)
}
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('x')
.operatorBtn()
.onClick(() => this.onOperatorClick('x'))
Button('÷')
.operatorBtn()
.onClick(() => this.onOperatorClick('÷'))
Button('+')
.operatorBtn()
.onClick(() => this.onOperatorClick('+'))
Button('-')
.operatorBtn()
.onClick(() => this.onOperatorClick('-'))
}
.numberRow()
.justifyContent(FlexAlign.SpaceAround) Row() {
Button('=')
.width('100%')
.backgroundColor(Color.Pink)
.onClick(this.onCalcClick)
}
.margin(10)
}
.height('100%')
} resetCheck() {
if (this.calcFinished) {
this.message = '0';
this.calcFinished = false;
}
} onNumberClick = (num: number) => {
this.resetCheck();
if (this.message === '0') {
this.message = `${num}`;
} else {
this.message += `${num}`
}
}
onOperatorClick = (_opt: string) => {
this.message += _opt;
this.calcFinished = false;
}
onDotClick = () => {
this.resetCheck();
this.message += '.';
}
onResetClick = () => {
this.message = '0';
this.calcFinished = true;
}
onDeleteClick = () => {
this.resetCheck();
if (this.message.length > 1) {
this.message = this.message.substr(0, this.message.length - 1);
} else {
this.message = '0'
}
}
onSaveClick = () => {
AppStorage.SetOrCreate('lastResult', this.message);
}
onClearClick = () => {
PersistentStorage.DeleteProp('lastResult')
this.message = '0';
}
onCalcClick = () => {
this.message = calculate(this.message.replace(/x/g, '*').replace(/÷/g, '/')) + '';
this.calcFinished = true;
}
}

6、结语

从代码我们可以看到,依旧存在很多相似的重复代码,我们还可以优化下吗?

答案是可以的,使用条件渲染。

请持续关注 "鸿蒙UI开发快速入门 —— part11"

鸿蒙UI开发快速入门 —— part11: 鸿蒙计算器开发实践的更多相关文章

  1. HealthKit开发快速入门教程之HealthKit开发概述简介

    HealthKit开发快速入门教程之HealthKit开发概述简介 2014年6月2日召开的年度开发者大会上,苹果发布了一款新的移动应用平台,可以收集和分析用户的健康数据.该移动应用平台被命名为“He ...

  2. Transform组件C#游戏开发快速入门

    Transform组件C#游戏开发快速入门大学霸 组件(Component)可以看作是一类属性的总称.而属性是指游戏对象上一切可设置.调节的选项,如图2-8所示.本文选自C#游戏开发快速入门大学霸   ...

  3. HealthKit开发快速入门教程之HealthKit数据的操作

    HealthKit开发快速入门教程之HealthKit数据的操作 数据的表示 在HealthKit中,数据是最核心的元素.通过分析数据,人们可以看到相关的健康信息.例如,通过统计步数数据,人们可以知道 ...

  4. HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID

    HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID HealthKit开发准备工作 在开发一款HealthKit应用程序时,首先需要讲解HealthKit中有哪些类,在i ...

  5. Apple Watch开发快速入门教程

     Apple Watch开发快速入门教程  试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...

  6. 游戏控制杆OUYA游戏开发快速入门教程

    游戏控制杆OUYA游戏开发快速入门教程 1.2.2  游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4  游戏控制杆各个角度的 ...

  7. webpack快速入门——实战技巧:开发和生产并行设置

    package.json中,devDependencies和dependencies是不同的 devDependencies:开发依赖 dependencies:生产依赖(线上) 1.安装生产环境的依 ...

  8. SpringBoot开发快速入门

    SpringBoot开发快速入门 目录 一.Spring Boot 入门 1.Spring Boot 简介 2.微服务 3.环境准备 1.maven设置: 2.IDEA设置 4.Spring Boot ...

  9. WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)

    概述 本文描述WPF的拖放功能(Drag and Drop). 拖放功能涉及到两个功能,一个就是拖,一个是放.拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放.假设界面上有两个控件,一个Tre ...

  10. Android开发快速入门(环境配置、Android Studio安装)

    Android是一种激动人心的开源移动平台,它像手机一样无处不在,得到了Google以及其他一些开放手机联盟成员(如三星.HTC.中国移动.Verizon和AT&T等)的支持,因而不能不加以学 ...

随机推荐

  1. 日干算命api接口_json数据_性格/爱情/事业/财运/健康运势免费接口

    ​ 该API接口基于传统的八字学原理,通过用户提供的日干信息,为用户提供性格.爱情.事业.财运和健康等多方面的运势分析和建议.以下是该接口的详细介绍: ‌一.功能概述‌ ‌性格分析‌:根据用户的日干信 ...

  2. qemu的使用

    一.QEMU的运行模式 直接摘抄自己<揭秘家用路由器0day漏洞挖掘技术>,网上查了一下也没有找到令人满意的QEMU的使用说明,就采用这本书上的介绍.如果后期能够找到比较满意的QEMU的使 ...

  3. 2022年6月中国数据库排行榜:TiDB卷土重来摘桂冠,达梦蛰伏五月夺探花

    排行榜风云又起,各产品墨坛论剑.2022年6月的 中国数据库流行度排行榜 再掀风云,6月排行榜共有231个数据库参与排名,两名新成员的加入,注入了新活力.本月排行榜用一句话可以概括为:TiDB卷土重来 ...

  4. iOS开发中UITextView使用小结

    最近项目开发中用到了多行文本框限制文字输入个数限制,之前的话在textViewDidChange方法中截取超出字数就可以.测试人员发现在拼音转汉字的过程中,YYTextView不会二次确认textVi ...

  5. 使用 Debugger 断点 如果打开了断点调试 就会跳转空白页面

    <!DOCTYPE html> <html> <header> <title>test</title> </header> &l ...

  6. kotlin集合——>集合概述、构造集合

    1.kotlin集合概述 在kotli.collections包中包含List(有序集合).Set(无序集合).Map(健值对)三种集合 2.val定义的集合,调用集合方法可以添加删除,但是如果重新创 ...

  7. 在 Kubernetes 中基于 StatefulSet 部署 MySQL(上)

    大家好,我是老 Z! 本文实现了 MySQL 数据库在基于 KubeSphere 部署的 K8s 集群上的安装部署,部署方式采用了图形化这种形式.下一篇文章将会涉及 GitOps 的基础操作,部署过程 ...

  8. 多元/多维高斯/正态分布概率密度函数推导 (Derivation of the Multivariate/Multidimensional Normal/Gaussian Density)

    各种维度正态分布公式: 一维正态分布 二维正态分布/多维正态分布 各向同性正态分布 注:即方差都是一样的,均值不一样,方差的值可以单独用标量表示. 多元/多维高斯/正态分布概率密度函数推导 (Deri ...

  9. numpy库(python)

    文章目录 1.numpy简介 2.安装numpy 3.ndarry : numpy库的心脏 3.1 创建数组 3.2数据类型 3.3dtype NumPy是用Python.进行科学计算,尤其是数据分析 ...

  10. DashText-快速开始

    快速开始 DashText,是向量检索服务DashVector推荐使用的稀疏向量编码器(Sparse Vector Encoder),DashText可通过BM25算法将原始文本转换为稀疏向量(Spa ...