Android 计算器

Android开发系列



前言

在Android开发中,常用的两种开发方式有声明式(View)和组合式(Compose)。本文使用compose开发一款Android 计算器。最终结果如下所示

Android 计算器 演示


项目创建

创建项目
后面的自行项目名称即可

默认的启动页面是 Kotlin。但是Kotlin是可以与Java混合使用的。后面按照Java开发就行。

界面设计

  1. 首先根据计算器操作数,需要将各按键罗列出来,
    利用Kotlin的 数据类创建数据(也可以用Java创建),
    按键我包含了按键符号、图标、颜色
data class Operators(var symbol: String, var icon: ImageVector? =null, var color: Color = Color.Black)

val operators = arrayListOf(
    arrayListOf(Operators("0"), Operators("1"), Operators("2"), Operators("3")),
    arrayListOf(Operators("4"), Operators("5"), Operators("6"), Operators("7")),
    arrayListOf(Operators("8"), Operators("9"), Operators("«", icon = Icons.Default.ArrowBack), Operators("=")),
    arrayListOf(Operators("("), Operators(")"), Operators("C"), Operators("%", color = Color(0xFF2196F3))),
    arrayListOf(Operators("+", color = Color(0xFF03A9F4)), Operators("-", color = Color(0xFF03A9F4)), Operators("*", color = Color(0xFF03A9F4)), Operators("/", color = Color(0xFF03A9F4))),
)

定义历史表达式字符串和当前表达式字符串。这种定义就等于Java中的setter与getter

//    历史表达式
var historyExpression by mutableStateOf("")
//    当前表达式
var expression by mutableStateOf("请输入表达式")
  1. 去掉初始的Greeting这个Compose,新建一个Compose开始进行界面布局。
@Composable
fun CalculatorUi() {}

分为历史记录栏、当前表达式栏、操作符栏
首先创建一个盒子,并获取当前屏幕的长宽。里面放一个列布局盒子。

BoxWithConstraints {
    val screenWidth = maxWidth
    val screenHeight = maxHeight
    Column(
    modifier = Modifier
        .fillMaxSize()
		) {}
    }

在列布局盒子中,放我们的表达式和操作符

     // 历史表达式
     Text(
         text = historyExpression,
         modifier = Modifier
             .fillMaxWidth()
             .height(screenHeight / (2*2))
             .wrapContentSize(Alignment.BottomEnd),
         fontSize = 30.sp
     )

     // 当前表达式
     Text(
         text = expression,
         modifier = Modifier
             .fillMaxWidth()
             .height(screenHeight / (2*2))
             .wrapContentSize(Alignment.BottomEnd),
         fontSize = 30.sp
     )
     // 操作符
     for (i in operators) {
         Row {
             for (j in i)
                 Surface(
                     modifier = Modifier
                         .width(screenWidth / 4)
                         .height(screenHeight / (2 * operators.size))
                         .padding(5.dp)
                         .clickable {
                             expression = computer(expression, j.symbol)
                         },
                     color = Gray23,
                     shape = RoundedCornerShape(10.dp)

                 ) {
                     if (j.icon == null)
                         Text(
                             text = j.symbol,
                             modifier = Modifier
                                 .wrapContentSize(Alignment.Center),
                             textAlign = TextAlign.Center,
                             style = TextStyle(
                                 fontWeight = FontWeight.Bold,
                                 fontSize = 20.sp,
                                 color = j.color
                             )
                         )
                     else
                         Icon(
                             imageVector = j.icon!!,
                             contentDescription = null,
                             modifier = Modifier
                                .wrapContentSize(Alignment.Center)
                         )
                 }
         }
     }

提出点击事件

//    历史表达式
var historyExpression by mutableStateOf("")
//    当前表达式
var expression by mutableStateOf("请输入表达式")
/**
 * @param text 表达式
 * @param j 运算符
 */
fun computer (text: String, j: String): String {
    if (text == "请输入表达式" || text == "表达式错误") {
        return if (j != "«" && j != "C" && j!= "=") j
        else "请输入表达式"
    }
    return if (j == "«") {
        if (text.isEmpty()) "请输入表达式"
        else text.substring(0, text.length - 1)
    } else if (j == "=") {
        historyExpression = text
        calculate("$text#")
    } else if (j == "C") {
        "请输入表达式"
    } else {
        text+j
    }
}

计算原理

前缀、中缀、后缀表达式

  1. 前缀表达式:运算符在运算对象之前,如:+a*b
  2. 中缀表达式:运算符在运算对象之间,如:a+b*c
  3. 后缀表达式:运算符在运算对象之后,如:a*b+c

相互转换

eg. 中缀表达式 a+b*c-(d+e)
前缀表达式:
    先将表达式按优先级括起来 ((a+(b*c))-(d+e))
    再将符号提在括号前面 -(+(a*(bc))+(de) --> -+a*bc+de
    转回去则是遇到一个符号添加括号
后缀表达式:
    同理,只不过需要将符号提在后面
    ((a(bc)*+)(de)+-) --> abc*+de+-

优先级表

栈顶\当前+-*/()#(结束)
+>><<<>>
->><<<>>
*>>>><>>
/>>>><>>
(<<<<<=错误
)>>>>错误>>
#(开始)<<<<<错误=

基本思想

算数表达式 以“#…#”的格式

从左到右一次读入

  1. 数字 -> 入操作数栈

  2. 字符(默认把开头的‘#’入了操作符栈)

    eg: 当前栈顶为’#‘, 当前字符为’+‘, 根据表得’<',则表示 当前字符 > 当前栈顶

    • 当前字符 > 栈顶字符 -> 入操作符栈

    • 当前字符 < 栈顶字符 -> 数字栈出两个,操作符栈出一个,后出数字 操作符 先出数字;继续用当前字符比对字符栈栈顶字符

    • 当前字符 = 栈顶字符 -> 出操作符栈

    • 当前字符 ‘’ 栈顶字符 -> 表达式错误

  3. 对于非个位数或带小数点的数,可以在读到操作符时才作为一个数,进行入数字栈操作

eg. 2+3*5

  1. 数字栈:2; 字符栈:#
  2. 数字栈:2;字符栈:#,+
  3. 数字栈:2,3;字符栈:#,+
  4. 数字栈:2,3;字符栈:#,+,*
  5. 数字栈:2,3,5; 字符栈:#,+,*
  6. 数字栈:2,15;字符栈:#,+ ----> 数字栈:17;字符栈:# ----> 数字栈:17;字符栈:

注意最后字符必须是空的,否则表达式错误

优先级表的关系由来

  1. 配对(=)
  • 像’(‘、’)'或‘开始’、‘结束’这种必须成对存在的,当‘)’遇到‘(’或 ‘结束’遇到‘开始’这种就是 ‘=’

    1. 表达式错误
  • 上述配对的,若类似于当前字符为’结束’,当前栈顶为’(',这种,未能完成配对,则错误

  • 同理当前为’(‘,栈顶为’开始’,也未完成配置对,则错误

  • 其实按理来说’)‘压根没有入栈的机会,’('那一行应该都为错误

  1. ‘>’

    • 主要看运算符,比自己优先级小或相等的 就是 ‘>’
  2. ‘<’

    • 同理,比自己优先级大

自己添加优先级表

eg. 添加上 ‘%’, ‘%‘与’*’、'/'同一优先级

因此

栈顶\当前+-*/%()#(结束)
+>><<<<>>
->><<<<>>
*>>>>><>>
/>>>>><>>
%>>>>><>>
(<<<<<<=错误
)错误错误错误错误错误错误>>
#(开始)<<<<<<错误=

代码实现

public class Calculate {

    private static String Opr = "+-*%/()#";
    private static HashMap<Character, Integer> OprPriority = new HashMap<Character, Integer>();
    // '+', '-', '*', '/', '%', '(', ')', '#',
    private static Character[][] OprRelation = {
            {'>', '>', '<', '<', '<', '<', '>', '>'}, //'+'
            {'>', '>', '<', '<', '<', '<', '>', '>'}, //'-'
            {'>', '>', '>', '>', '>', '<', '>', '>'}, //'*'
            {'>', '>', '>', '>', '>', '<', '>', '>'}, //'/'
            {'>', '>', '>', '>', '>', '<', '>', '>'}, //'%'
            {'<', '<', '<', '<', '<', '<', '=', ' '}, //'('
            {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, //')'
            {'<', '<', '<', '<', '<', '<', ' ', '='}  //'#'
    };

    /**
     * 存放操作数的栈
     */
    static Stack<Double> values = new Stack<>();
    /**
     *  存放操作符的栈
     */
    static Stack<Character> ops = new Stack<>();

    /**
     * 这个是栈的第二个数 opera 栈顶的数
     */
    private static void count(Double num_2, Double num_1, char opr) {
        switch (opr) {
            case '+':
                values.push(num_1 + num_2);
                break;
            case '-':
                values.push(num_1 - num_2);
                break;
            case '*':
                values.push(num_1 * num_2);
                break;
            case '/':
                values.push(num_1 / num_2);
                break;
            case '%':
                values.push(num_1 % num_2);
                break;
        }
    }

    private static String operate(char ch) {
        // 判断当前操作符与栈顶操作符的关系,并操作
        char oprRelation = OprRelation[OprPriority.get(ops.peek())][OprPriority.get(ch)];
        if (oprRelation == '<') {
            // 栈顶操作符优先级低于当前操作符,当前操作符进栈
            ops.push(ch);
            return null;
        } else if (oprRelation == '>') {
            // 栈顶操作符优先级高于当前操作符,则弹出栈顶操作符,数组栈中去两个数字,并将当前操作符压入栈顶
            count(values.pop(), values.pop(), ops.pop());
            operate(ch);
        } else if (oprRelation == '=') {
            ops.pop();
            return null;
        } else if (oprRelation == ' ') {
            return "表达式错误";
        }
        return null;
    }

    public static String calculate(String calculator) {
        // 操作符栈里需初始化一个'#'
        ops.push('#');
        // 初始化
        for (int i = 0; i < Opr.length(); i++) {
            OprPriority.put(Opr.charAt(i), i);
        }
        // 这里需要考虑非个位数,数字只考虑0-9,不考虑'*'可省略的情况
        String num = "";
        for (int i=0;i<calculator.length();i++)
        {
            // 判断当前字符是否在Opr
            if (Opr.contains(String.valueOf(calculator.charAt(i))))
            {
                // 数字进栈
                if (!num.equals("")) {
                    values.push(Double.parseDouble(num));
                    num = "";
                }
                String res = operate(calculator.charAt(i));
                if (res != null) return res;
            } else {
                num += calculator.charAt(i);
            }
        }

        if (ops.isEmpty())
            return values.peek().toString();
        else return "表达式错误";
    }
}

点击震动与音效

在点击时,手机的震动或者音效能带来更好的体验。

  1. 震动
//初始化振动器
vibrator = getSystemService(Service.VIBRATOR_SERVICE) as Vibrator?
// 在点击事件中添加
vibrator?.vibrate(longArrayOf(100, 200), -1)
  1. 音效
// 初始化播放器
soundPool = SoundPool.Builder().build()
// 初始化资源ID
soundID = soundPool!!.load(this, R.raw.duang, 1)
// 在点击事件中添加 (这里不能把加载弄在 .play中,音频资源加载一次就够了,且需要时间)
 soundPool?.play(
     soundID, 1F, 1F, 0, 0, 1.0F
 );

哎~~这是第一次在CSDN上发布,有些玩意不会弄。有问题私信。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值