前言
计算器是每个手机自带的必备功能,生活中常用来计算比较大型复杂的数据,今天我准备自己写一个计算器APP出来,以巩固Android学习。
分析与设计
对于整个计算器软件的编写,我们分为两大部分,第一部分是UI设计,另一部分是逻辑实现。
UI设计
通常程序猿不需要懂太多的UI设计,但是简单的UI设计我们还是需要学会。计算器我们见得多了,最常见的就是顶部一个方形的显示屏,接着下面就是10多个按键,这些按键大多是圆形的,有了这些常识,我们倒不妨画个图。
这就是整个计算器大概的样子。
确定布局
那么接下来就是真正实现UI界面的时候了。
个人觉得现在constraintLayout(约束布局)最好用,因为动动手指就能实时为你展现UI界面,特别直观与方便,因此根标签就是constraintlayout了。
整个布局是这样的
确定视图种类和数目
显示屏就是一个TextView,而其余的按键都是Button,总的来说就是需要16个Button和3个icon图标以及一个TextView,为每个Button和icon以及TextView添加监听事件,实现相应的逻辑即可。
为了能改变Button的颜色以及样式,我们需要在两个 theme
中将 style 中的 parent 改为
<style name="Theme.xxx" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge"></style>
在 res -> drawable
文件夹中新建一个xml文件,添加如下代码,即可实现圆形Button
1 2 3 4 5 6 7 8 9 10 11 12
| <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <corners android:radius="25dp" /> <!-- 设置圆角弧度 --> <solid android:color="#fafafa" /> <!-- 设置背景颜色 --> <size android:width="40dp" android:height="40dp" /> <!-- 设置大小 --> <stroke android:width="0dp" android:color="#fff" /> <!-- 设置描边大小与颜色 --> </shape>
|
设置渐变色
同样也在 res -> drawable
文件夹中新建xml文件,添加如下代码
1 2 3 4 5 6 7 8
| <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <gradient android:startColor="#74b981" <!-- 起始色彩 --> android:endColor="#a1d47e" <!-- 结束色彩 --> android:angle="45" /> </shape>
|
更改字体样式
首先下载一个字体压缩包,将 xxx.ttf
解压到 res -> font
(如果没有font文件夹就创建一个),然后在 activity_main.xml
中使用即可,如
1 2 3
| <Button ... android:fontFamily="@font/myfont"/>
|
引入iconfont图标
在iconfont官网下载对应的图标并加到项目中
进入iconfont官网,选择合适的图标添加至项目,然后下载压缩包到本地,最后将整个解压后的文件加到项目中的 app -> src -> main -> assets
中(如果没有assets文件就新建一个)
编写一个帮助类
在将 iconfont
添加至项目之后,我们需要编写一个帮助类才能使图标正常在屏幕上显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.example.myapp;
import android.content.Context; import android.graphics.Typeface; import android.view.View; import android.view.ViewGroup; import android.widget.TextView;
public class FontManager { //根据地址,找到iconfont中的iconfont.ttf文件 public static final String ROOT = "iconfonts/",ICONFONTS = ROOT + "iconfont.ttf";
public static Typeface getTypeface(Context context, String font) { return Typeface.createFromAsset(context.getAssets(), font); }
//图标一般都是包含在一个ViewGroup中,比如constraintlayout //我们可以写一个方法,遍历指定xml parent 并且递归的覆盖每个TextView的字体 public static void markAsIconContainer(View v, Typeface typeface) { if (v instanceof ViewGroup) { ViewGroup vg = (ViewGroup) v; for (int i = 0; i < vg.getChildCount(); i++) { View child = vg.getChildAt(i); markAsIconContainer(child, typeface); } } else if (v instanceof TextView) { ((TextView) v).setTypeface(typeface); } } }
|
在xml文件中添加字段
在 values -> icons.xml
(如果没有就创建一个),将对应的iconfont图标的Unicode编码用字段名储存起来,以便在 activity_main.xml
中引用。
1 2 3 4 5
| <resources> <string name="return0"></string> <string name="calender"></string> <string name="record"></string> </resources>
|
而 activity_main.xml
应该是这样子的
1 2 3
| <TextView // ... android:text="@string/calender" />
|
使用help类显示图标
所有准备工作都做完了,接下来就是调用help类了,在 MainActivity
中写入如下代码
1 2 3
| //helper帮助类, 加载iconfonts的必要操作 Typeface iconFont = FontManager.getTypeface(getApplicationContext(), FontManager.ICONFONTS); FontManager.markAsIconContainer(findViewById(R.id.icons_container), iconFont);
|
接下来就能看到正常的图标了
逻辑部分实现
我们先总体分析一下,对于这个计算器,有很多重复的功能,比如有11个数字键的功能都极其相似,因此我们可以专门写一个方法进行封装;而其余的4个运算键也是类似的功能,我们封装一个方法用于实现 + - x ÷
功能,最后就是归零键与 =
键,只需要单独写两个方法就好了。因此我们首先写这5个方法(其中还包括显示小数点功能的方法),分别是 bindNormalClickEvent
, bindSpecialClickEvent
, bindCalClickEvent
, bindACClickEvent
, bindGetResultEvent
。
bindNormalClickEvent方法
为了能使绑定这个动作的 btn
完成相应数字(符号)在屏幕上的显示,我们设置 num
来保存这个相应的数字(符号)。
1 2 3 4 5 6 7 8
| //input是stringBuilder类型,保存着从键盘上输入的各个数字(符号) public void bindNormalClickEvent(Button btn, String num) { btn.setOnClickListener(view -> { input.append(num); mTextView.setText(input); frequency++; //记录数字(符号)的个数 }); }
|
bindSpecialClickEvent方法
这个方法和 bindNormalClickEvent
方法差不多,只是记录是否有小数点,方便后面操作转换成 double
类型的数据
1 2 3 4 5 6 7 8 9 10 11 12
| public void bindSpecialClickEvent(Button btn, String num) { btn.setOnClickListener(view -> { //special 限定了一次计算中只能按一次 '.' 键,special初始为true if (special) { input.append(num); mTextView.setText(input); frequency++; special = false; isDecimal = true; //数据中是否包含小数点 } }); }
|
bindCalClickEvent
这个方法用来绑定 + - x ÷
的按钮的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public void bindCalClickEvent(Button btn, String num) { btn.setOnClickListener(view -> { //isOnlySymbol规定 运算按钮一次运算中只能点击一次 if (isOnlySymbol){ symbol = num; for (int i = 0; i < frequency; i++) { //isDecimal来判定运算数中是否有小数点 if (isDecimal && ((int) input.charAt(i) - 46) == 0) { whereDecimal = frequency - i - 1; break; } } //将stringBuilder中的字符串提取出来转化为 double 类型的数字 int temp = frequency; for (int i = 0; i < temp; i++) { if (isDecimal && ((int) input.charAt(i) - 46) == 0) { frequency++; continue; } firstNum += ((int) input.charAt(i) - 48) * Math.pow(10, frequency - 1 - i); } //whereDecimal 标记了小数点的位置,为转为成double型做准备 if (isDecimal) whereDecimal++; firstNum /= Math.pow(10, whereDecimal); hasSymbol = true; if (input.length() != 0) { input.delete(0, temp); input.append(num); mTextView.setText(input); //输入完第一个数将这些属性重置,以便第二个数的输入 frequency = 0; isDecimal = false; whereDecimal = 0; special = true; } //确保运算按钮一次运算中只能点击一次 isOnlySymbol = false; } });
}
|
bindACClickEvent方法
这是计算器的 AC
归零键功能的实现,原理是将stringBuilder中的字符清空,将所有属性全部初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void bindACClickEvent(Button btn) { btn.setOnClickListener(view -> { input.setLength(0); firstNum = 0; secondNum = 0; mTextView.setText(input); special = true; frequency = 0; isDecimal = false; hasSymbol = false; hasFirstNum = false; isOnlySymbol = true; whereDecimal = 0; }); }
|
bindGetResultEvent方法
这个方法又和 bindCalClickEvent 很相似,只是最后多了一段运算符判定的代码,分别对应着 + - x ÷
不同的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public void bindGetResultEvent(Button button) { button.setOnClickListener(view -> { if (hasSymbol) { double result; input.delete(0, 1); for (int i = 0; i < frequency; i++) { if (isDecimal && ((int) input.charAt(i) - 46) == 0) { whereDecimal = frequency - i - 1; break; } } int temp2 = frequency; for (int i = 0; i < temp2; i++) { if (isDecimal && ((int) input.charAt(i) - 46) == 0) { frequency++; continue; } secondNum += ((int) input.charAt(i) - 48) * Math.pow(10, frequency - 1 - i); } if (isDecimal) whereDecimal++; secondNum /= Math.pow(10, whereDecimal);
if (Objects.equals(symbol, "+")) { result = firstNum + secondNum; } else if (Objects.equals(symbol, "x")) { result = firstNum * secondNum; } else if (Objects.equals(symbol, "÷")) { result = firstNum / secondNum; } else { result = firstNum - secondNum; } input.setLength(0); input.append(result); mTextView.setText(input); hasSymbol = false; } }); }
|
其余特殊按键
1 2 3 4 5 6 7 8 9 10 11 12
| //这是结束当前activity按钮 public void toLastPage(View view) { TextView textView = (TextView) view; textView.setOnClickListener(view1 -> finish()); }
//这是对于某些功能还未开放的提示按钮 public void onTipping(View view, Context context) { view.setOnClickListener(view1 -> Toast.makeText(context, "功能暂未开放", Toast.LENGTH_SHORT).show()); }
|
结果展示
源代码以及素材在 github 上:https://github.com/cofbro/Calculator-java-and-kotlin-