【Android入门01】教你从0开发一个计算器软件——say No to “Hello world”,say Hi to Calculator

演示gif
本文会手把手教你如何从0搭建一个系统自带的计算器应用。

前言

计算器应用只是一个普通的系统原生应用,并不设计数据库、网络交互的内容,也不需要使用MVVM等设计模式和高级的Jetpack组件。只要搞清界面设计运算逻辑两部分,任何人都可以轻松的实现该应用。

界面设计

界面结构划分
可以很清楚的看到,主要界面主要分为运算过程显示区运算结果显示区用户交互区

界面基本搭建

1.使用两条水平guideline对界面进行划分,guideline1位于30%处,guideline1上方为运算过程显示区;guideline2位于38%处,guideline1与guideline2之间是运算结果显示区。
2.使用vector asset生成所需按键,不过这里的加减乘除和数字都可以直接用TextView进行代替,只有撤回按钮(undo)需要生成矢量图。
生成vector asset
3.如何生成四角圆形的按钮
3.1在drawable文件夹中生成一个drawable resource file(可绘制资源文件),内容如下。
自定义可绘制资源文件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><corners android:radius="10dp" /><size android:height="60dp"android:width="60dp" />
</shape>

corners用来定义圆角的弧度,这里可以随个人喜好定制;size用来定义控件的宽度和高度。

3.2 在控件中指定background属性为刚刚定义的可绘制资源文件。

android:background="@drawable/round_shape"

通过查看android:backgroud属性的源码可知,该属性的格式可以是具体的颜色,也可以是对可绘制资源文件的引用,所以在此处我们可以在drawable resource file中自行定义想要的控件格式并进行引用。

 <!-- A drawable to use as the background.  This can be either a referenceto a full drawable resource (such as a PNG image, 9-patch,XML state list description, etc), or a solid color such as "#ff000000"(black). --><attr name="background" format="reference|color" />

4.对控件进行约束
将横排的四个控件选中,点击右键 —— Chains —— Horizontal Chain Style —— spread,对控件添加横向约束。竖排的依此类推,也是创建Vertical Chain,4个横排和4个竖排都要执行此步骤。
添加约束
这里一共有三种模式,分别是spread,spread_inside和packed。这里我直接放上谷歌开发者文档中的介绍,不难看出我们的布局需要使用第一种,也就是spread这种样式。
三种chain style的区别
5.用到的颜色资源如下,浅灰色:#7D7D7D,深灰色:#636363,橙色:#E99D28。(这里我是仿照ios计算器的UI设计,使用mac自带的拾色器对mac原生计算器的颜色资源进行拾取)。
6.使用backgroundTint属性为控件设置背景颜色,textColor属性设置文字颜色。

android:background="@drawable/round_shape"
android:textColor="@color/white"
android:backgroundTint="#E99D28"

这里的background属性和backgroundTint属性较为容易混淆。通过阅读源码可知,backgroundTint属性主要是设置背景的颜色,只能指定color;而background属性可以理解为设置背景的样式,可选的有颜色和引用。
这里我们使用的是对可绘制资源文件的饮用。那么如果background属性指定为颜色又会发生什么呢?这里的颜色指的不是一个具体颜色,而是一个color的资源文件,可以设置控件和文字被选中时的颜色和未选中时的颜色。

 <!-- Tint to apply to the background. --><attr name="backgroundTint" format="color" /><!-- A drawable to use as the background.  This can be either a referenceto a full drawable resource (such as a PNG image, 9-patch,XML state list description, etc), or a solid color such as "#ff000000"(black). --><attr name="background" format="reference|color" />
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="#F5C713" android:state_checked="true" /><item android:color="#7C7B7E" android:state_checked="false" />
</selector>

7.添加两个textView放入过程显示区和结果显示区,并添加约束。
8.养成良好的编程习惯,对控件ID进行修改,注意这里的修改建议在design模式下,在右侧属性栏里进行修改,这样的修改才可以影响全局。如果只是在code模式中进行修改,就只是修改了当前控件的id值,其他引用到该控件的地方无法得到自动更新,需要自己手动去改。
添加id值
修改id值

运算逻辑

刚刚的界面设计部分只是开胃菜,如何实现四则运算及混合运算,才是这个应用的重难点。

计算机中的四则运算是如何实现的?

如果你对数据结构有一定了解,那么你一定知道在计算机中,计算机是把中缀表达式(eg: 1 + 2)转化为后缀表达式(eg:1 2 + )的形式,后缀表达式也称为逆波兰表达式(Reverse Polish Notation)
使用后缀表达式有以下几点优点:
1.去掉括号后表达无歧义,比如中缀表达式(1 + 2) * (3 + 4),转化为后缀表达式就是((1 2 +)(3 4 +)*),此时拆掉括号变为1 2 + 3 4 + *照样不会有任何歧义。如果是中缀表达式,计算机在遍历到括号部分还需要先对括号内内容进行计算,将会大大减慢计算机的处理速度。
2.方便使用栈(Stack)这种数据结构进行存储,遇到数字则入栈,遇到运算符则取出栈顶前2个数字进行计算,并压入栈中。
leetcode150 计算逆波兰表达式,这是一道关于栈和逆波兰表达式的经典算法题,可以加深对栈和后缀表达式的理解。

在本应用中,可以使用后缀表达式进行混合四则运算吗?

答案是可以。不过鉴于阅读本文的可能绝大部分都是新手,所以这里采用的是最暴力的【模拟】方法,对表达式进行遍历和求解。至于如何【将中缀表达式转化为后缀表达式】,文末会简单提及。

本应用中,该如何模拟四则运算呢?

本应用中混合四则运算的核心思路,是用2个数组,分别存储数字和操作符号,并用2个指针从头开始遍历。如下所示:
表达式:1 + 2 x 3 + 6 x 4 x 5 - 1
数字数组numList:1,3,4,6,4,5,1
运算符数组operatorList:+,x,+,x,-
一开始,数字指针指向1,运算符指针指向+。对运算符指针指向的运算符进行判断,如果当前运算符为高优先级运算符(乘号、除号、取余)则直接计算;若该运算符为低优先级运算符(加号、减号),则需分为两种情况讨论:1.这是最后一个运算符 2.接下来的运算符也是加减。如果满足上述两种情况,则取出数字数组中当前位置的下一位数字,并直接进行运算。
如果不满足上述2种情况,即【该运算符为低优先级运算符,且后面的运算符是高优先级运算符】,则数字数组当前位置的下一个位置开始用while循环进行遍历,直到下一个低优先级运算符为止。对应到本例中,即先完成2 x 3的计算,再将计算结果与1进行计算。

具体实现

用一个calculate函数封装运算逻辑

private fun calculate(param1: Float, operator: String, param2: Float): Float {var res = 0.0fwhen (operator) {"+" -> res = param1 + param2"—" -> res = param1 - param2"x" -> res = param1 * param2"÷" -> res = param1 / param2"%" -> res = param1 % param2}return res}

对点击数字按钮的事件进行封装,并在数字0 ——数字9的点击事件中调用此函数

private fun numberButtonClicked(view: View) {val textView = view as TextViewcurrentInputStringBuilder.append(textView.text)if (isNumStart) {numList.add(textView.text.toString())isNumStart = false} else {if (currentInputStringBuilder.contains('.')) {numList[numList.lastIndex] = currentInputStringBuilder.toString()} else {numList[numList.lastIndex] = currentInputStringBuilder.toString()}}showProcess()showResult()}

对点击运算符的事件进行封装,并在加减乘除取余符号的点击事件中调用此函数

 private fun operatorButtonClicked(view: View) {if (operatorList.size < numList.size) {val textView = view as TextViewoperatorList.add(textView.text.toString())currentInputStringBuilder.clear()isNumStart = trueshowProcess()}}

对运算过程进行及时更新

private fun showProcess() {val sb = StringBuilder()for ((i, num) in numList.withIndex()) {sb.append(num)if (i < operatorList.size) {sb.append(" ${operatorList[i]} ")}}process_textView.text = sb.toString()}

展示当前运算结果

private fun showResult() {if (numList.size > 0) {var i = 0var param1 = numList[0].toFloat()var param2 = 0.0fif (operatorList.size > 0) {while (true) {val operator = operatorList[i]//若当前运算符为乘除,则直接运算if (operator == "x" || operator == "÷" || operator == "%") {if (i + 1 < numList.size) {param2 = numList[i + 1].toFloat()param1 = calculate(param1, operator, param2)}} else {//若当前运算符为加减,则先考虑 1.这是最后一个运算符 和 2.接下来的运算符也是加减 这两种情况if (i == operatorList.size - 1 || (operatorList[i + 1] == "+" || operatorList[i + 1] != "—")) {if (i + 1 < numList.size) {param2 = numList[i + 1].toFloat()param1 = calculate(param1, operator, param2)}} else {//后面还有运算符且运算符是乘除var j = i + 1var mParam1 = numList[j].toFloat()var mParam2 = 0.0fwhile (true) {if (j == operatorList.size) breakif (operatorList[j] == "x" || operatorList[j] == "÷" || operatorList[j] == "%") {if (j + 1 < numList.size) {mParam2 = numList[j + 1].toFloat()mParam1 = calculate(mParam1, operatorList[j], mParam2)}} else {break}j++}param2 = mParam1param1 = calculate(param1, operator, param2)i = j - 1}}if (++i == operatorList.size) {break}}}result_textView.text = String.format("%.2f", param1)} else {result_textView.text = "0"}}

代码的其他部分,如撤销按钮,取反按钮,清空按钮等的具体实现详见github。

一些坑

记得考虑前导0的情况

例如“0001”这种输入,显然是不合法的;这里应该对这种情况进行判断。
如何优雅的避免前导0:在输入时,我们并没有对前导0进行判断。如果要在输入时进行判断,显然要对数字0的数字进行单独的处理,例如判断当前输入的数字0是否是第一位,如果是第一位但是整个数字只有一位则不做处理【eg:数字0】,如果后面还有其他数字【eg:数字09】,则修改当前数字为9。这种判断方法显然是不够优雅的。
本文的实现方式是:不在输入时进行处理,但在输出时进行处理。我们使用的是一个StringBuilder来存储输入的数字,当我们调用toFloat()方法是,前导0会自动被去掉。(包括0000这种情况,也只会显示一个0)。这是kotlin为我们封装好的函数,底层调用了Java中的parseFloat()。现在知道Kotlin有多香了吧

判断多个符号连续输入

多个符号连续输入,例如【1 ++2】,显然是不合法的。你只需要判断operatorList的大小和numList的大小即可,只有operatorList.size < numList.size才执行点击事件。因为操作符数组的大小必然是小于等于数字数组的大小的。比如【1 + 2 + 】,【1】这种情况,一旦操作符数组大小超过数字数组大小,例如【1+2+3++】,这种情况就是不合法的。

其他bug,例如在没有数字或运算符的情况下按取反按钮,撤回按钮等

这种情况会报空指针异常,记得做边界处理

如何实现浮点数运算

这里我们的实现逻辑是。***当输入小数点时,对UI界面进行更新(过程显示区),但不对numList数组进行更新。***原因是如果对numList数组进行更新,此时numList中最后一个数就会变成形如【99.】这种情况,这显然不是一个合法的数字。如果此时我们按下下一个操作符,那这个不合法的数字就会一直被存储在数字数组中。正确做法是,当我们输入下一个数字时候,此时再将小数点和当前数字一同更新到numList数组中。
这里要注意,一次输入中最多只能包含一个小数点,并且小数点不能在数字的开头

为什么数字数组的类型是String而不是Float

理论上,数字数组的类型是Int还是买了String都可以,无非就是如果存储的是String类型,进行操作的时候要转化成具体数字在操作,如果存储的是Float类型,在进行操作时要转化成String再进行操作罢了。这里有一个被忽略的问题是,如果数字数组的类型是Float,那么当输入小数点后,在输入0时,例如【0.000】我们希望这个过程可以完整的展示在屏幕上(后面可能还会有其他数字输入,所以此时该输入是合法的),但由于StringBuilder会调用toFloat()方法,所以存储进数字数组时,这些后置的0是会被自动省略的。这个toFloat()方法在帮我们去除前导0的同时,也把我们想要的后置0给去掉了,也许这就是欲戴皇冠,必承其重吧。所以这里数字数组只能选用String进行存储。

题外话:如何将中缀表达式转化为后缀表达式

用一个StringBuilder储存数字,用一个栈储存运算符,若栈为空或当前运算符的优先级比栈顶元素更高,则将当前运算符入栈;若当前运算符和栈顶元素的优先级一样或较低,则弹出栈顶元素并拼接到StringBuilder中,再将该操作符入栈。
转化为后缀表达式后,再用栈进行操作即可。有兴趣的朋友可以实践一下,将本应用的计算逻辑替换为后缀表达式 + 栈,试着实现一下,此处不再过多赘述。
此处借用另一位博主的文章的图:中缀表达式转后缀表达式详解
在这里插入图片描述

写在最后

第一次认认真真写了一篇长文,希望可以帮助到有需要的人,还有考虑不周的地方请多包涵。
完整代码地址:CalculatorDemo
其他文章:Android入门02】教你从0实现一个手势解锁页面——Let‘s unveil the world

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1620146.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

php 经纬度范围计算器,经纬度距离角度计算器|经纬度距离角度计算器(geography)下载v2.1 免费版 - 欧普软件下载...

经纬度距离角度计算器是一款简单好用的计算器软件&#xff0c;可以根据开始点、参考点、结束点、距离输出、角度输出等参数计算出两点距离和三点夹角&#xff0c;计算速度快&#xff0c;准确率高&#xff0c;需要的朋友可以来本站下载&#xff01; 经纬度距离角度计算器安装教程…

android中倒计时计算器,死亡计算器生命倒计时下载-死亡计算器生命倒计时软件下载 v8.8.0_5577安卓网...

死亡计算器生命倒计时下载分享给大家。火爆全抖音的生命倒计时&#xff0c;年龄计算器、死亡计算器、倒计时生日提醒等功能。通过精确到小数点的数据来告知人们珍惜时间的重要性&#xff01; 【软件说明】 人生在世三万天&#xff0c;好好珍惜每一刻的时光。独特功能&#xff1…

Linux(多进程与多线程)

目录 1、进程与线程概念 1.1 进程 1.2 线程 1.3 进程与线程区别 2、多进程 2.1多进程概念 2.2 进程相关API 2.3 多进程编程 3、多线程 3.1 多线程概念 3.2 多线程相关API 3.3 多线程编程 1、进程与线程概念 1.1 进程 在计算机科学中&#xff0c;进程是正在执行中…

虚拟机搭建集群

基于3台虚拟机搭建集群架构图 1.服务器准备 使用VMware Workstation虚拟机创建虚拟服务器来搭建集群&#xff0c;所用软件及版本如下&#xff1a; VMware Workstation 12.0 CentOS-6.5-64bit 2.基础虚拟机安装 2.1新建虚拟机&#xff08;虚拟硬件&#xff09; 关键步骤&a…

【web集群简介-01】

文章目录 一、web服务基础二、集群简介 一、web服务基础 web服务器HTTP基本原理 web服务基础HTTP 1.URL / URI URL全称为Uniform Resource Location&#xff0c;统一资源定位符 URI全称为Uniform Resource Identifier&#xff0c;统一资源标识符 URN:统一资源名称 (Uniform Re…

什么是虚拟计算机集群

这个问题来自近期几位网友的私信&#xff0c;他们不约而同问到一个问题&#xff1a;什么是虚拟计算机集群&#xff1f;Laxcus分布式操作系统是如何做的&#xff1f;下面就正式回答一下这个问题。 在我们传统的认知里&#xff0c;或者大家平常比较多接触的&#xff0c;都…

Linux下的5款主流高可用集群软件介绍

Linux集群主要分成三大类:高可用集群(High Availability Cluster)、负载均衡集群(Load Balance Cluster)、科学计算集群(High Performance Computing Cluster)。 其中高可用集群具有保障应用程序持续提供服务的能力,可以将因软、硬件、人为造成的故障对业务的影响降低到最小…

高可用 - 01 闲聊高性能集群

文章目录 什么是集群集群的特点与功能1. 高可用性与可扩展性2. 负载均衡与错误恢复3. 心跳监测与漂移IP地址 集群的分类1. 高可用集群高可用的概念常见的HA集群双机冷备双机热备双机互备多机互备 高可用集群软件 2. 负载均衡集群3. 分布式计算集群 HA集群中的相关术语1&#xf…

常见集群(Cluster)软件和技术解析

集群就是通过软件将一组服务器作为一个整体向客户提供资源。这些单个的服务器就是集群的节点。当对外提供资源的节点故障后&#xff0c;集群中其余的节点能够将资源接管起来&#xff0c;继续对客户提供资源。 集群技术的核心就是资源访问控制。由于集群中所有节点都可以访问集群…

[Linux基础与服务管理——常用集群高可用软件 Keepalived]

1.Keepalived 简介 Keepalived是Linux下的一个免费的、轻量级的高可用解决方案。是一个由C语言编写的路由软件&#xff0c;主要目标是为Linux系统和基于Linux的基础架构提供简单而强大的负载平衡和高可用性设。Keepalived实现了一组检查器&#xff0c;以根据其健康状况动态地和…

集群分为几种,用的软件分别是什么?

集群分为几种&#xff0c;用的软件分别是什么? 补充&#xff1a;涉及的组件 1.1、apache 跨平台的网页服务器&#xff0c;主要使用它做静态资源服务器&#xff0c;也可以做代理服务器转发请求 1.2、ngnix 高性能的 HTTP和反向代理服务器&#xff0c;ngnix处理能力相当于apache…

轻量级集群管理软件-Ansible

ansible概述和运行机制 ansible概述 Ansible是一款为类Unix系统开发的自由开源的配置和自动化工具, 它用Python写成&#xff0c;类似于saltstack和Puppet&#xff0c;但是有一个不同和优点是我们不需要在节点中安装任何客户端 , 它使用SSH来和节点进行通信 Ansible基于 Pytho…

了解集群、集群的分类、常用的集群软硬件及选型介绍(内附详细图解)

Table of Contents 一、集群简介 二、集群的七大优点 三、集群的分类 四、常用的集群软硬件及选型介绍 一、集群简介 集群就是一组&#xff08;若干个&#xff09;相互独立的计算机&#xff0c;利用高速通信网络组成的一个较大的计算机服务系统&#xff0c;每个集群节点&…

数据I/O

I/O类型 区分同步或异步&#xff08;synchronous/asynchronous&#xff09;。 简单来说&#xff0c;同步是一种可靠的有序运行机制&#xff0c;当我们进行同步操作时&#xff0c;后续的任务是等待当前调用返回&#xff0c;才会进行下一步&#xff1b;而异步则相反&#xff0c;…

【赋权算法】Python实现熵权法

在开始之前&#xff0c;我们先说一下信息熵的概念。 当一件事情发生&#xff0c;如果是意料之中&#xff0c;那么这个事情就并不能拿来当做茶余饭后的谈资&#xff0c;我们可以说这个事情并没有什么信息和价值。而当一件不可能发生的事情发生的时候&#xff0c;我们可能就会觉…

【软件测试】大学毕业后顶着压力,巧合的开启了我人生的新篇章......

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 小李&#xff1a;…

中国矿业大学徐海学院最不常见的网络工程计算机毕业设计题目推荐50例

之前有矿业大学徐海学院的童鞋在后台找我们&#xff0c;最近要准备毕业设计了&#xff0c;不会选题&#xff0c;希望可以帮忙给一些毕业设计题目&#xff0c;我整整花了一周把之前做的答辩通过的毕业设计成品进行整理&#xff0c;并精选一些容易实现且不会刷下来的题目列举下。…

电路叠加定理的例题

来看第二题 不擅长画这种图形&#xff0c;见谅 注:叠加定理中&#xff0c;电压源当导线短路处理&#xff0c;电流源当开路处理。

计算机春季高考考什么时候开始报名,2021春季高考报名时间 什么时候报名

2021春季高考报名时间是什么时候&#xff0c;小编整理了相关信息&#xff0c;来看一下&#xff01; 2021春季高考报名时间 春考招生简章公布(2020年12月下旬)&#xff1b; 春考校测方案公布(2021年1月) 春考(2021年1月)&#xff1b; 春考出分(通常为考试后两周)&#xff1b; 春…

【运维】hadoop集群安装(一)多节点安装

文章目录 一.Purpose二. Prerequisites三. Installation1. 节点规划2. Configuring Hadoop in Non-Secure Mode3. 准备工作4. 配置core-site.xmlhdfs-site.xmlyarn-site.xmlmapred-site.xmlworkers 4. 分发配置、创建文件夹5. 格式化6. 操作进程6.1. hdfs启动停止 6.2. yarn启动…