菜鸟学习安卓--第一个自写安卓app

最近基本上把郭霖大神的《第一行代码-第二版》看完了,也跟着敲了几个app的例子,但是总觉得跟着大神写,自己提高有限,还是需要自己亲自实现才带感。在网上找了个简易计算器的效果图,自己就写完了。算是自己亲自写的第一个安卓app吧。不说先看最终的效果图:


最终效果图

首先分析下这个app几个大致功能点吧:

  • 界面部分。上面两个地方一个用于展示输入的表达式,一个用于显示计算的表达式结果,用TextView正好就可以了。下面的键盘输入部分,每个字符(或者字符串)正好用一个Button表示,而且可以看到上面4列都是那种1/4屏幕宽,正好用Android里面的layout_weight权重的方式来布局。最后一列也是,只是一个是3/4,一个是1/4。因为要将键盘部分沉底,所以整个界面使用RelativeLayout,然后RelativeLayout里面套两个LinearLayout,一个放上面两个展示用的TextView,一个放下面的键盘。
  • 表达式处理。主要分为表达式有效性验证以及表达式求值。表达式有效验证包括当前输入是否合法,比如前面字符已经是一个操作符,这个时候就不能输入操作数或者"."号等。还有就是当输入"="进行求值前需要整个表达式做一次验证(主要是最后一个字符不能是等号,操作符)。表达式求值主要是四则混合运算时候,计算表达式有优先级的问题。由于没有括号运算符可以,相对而言简单很多(没有括号的各种嵌套,优先级只用考虑加减乘除的优先级就可以了)。所以对于表达式遇到一个乘法或者除法,只需要把这个乘除法计算的结果然后替换掉它们原先在表达式中的位置即可,一直不停的迭代直至计算出最终结果为止。 BB了这么多,要show my code了。
    布局,直接新建一个calculator_view.xml的布局文件,由于要让键盘沉底,最外层使用RelativeLayout,具体的码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:id="@+id/cal_reg_text"
            android:textColor="#FFF"
            android:textSize="20sp"
            android:gravity="center_vertical|right"
            android:layout_marginRight="5dp" />
        <View
            android:layout_width="match_parent"
            android:layout_height="2dp"
            android:layout_marginLeft="20dp"
            android:background="#6FFF">
        </View>
        <TextView
            android:id="@+id/cal_result"
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:textColor="#FFF"
            android:textSize="20sp"
            android:gravity="center_vertical|right"
            android:layout_marginRight="5dp"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="2dp"
            android:background="#6FFF">
        </View>
    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:layout_alignParentBottom="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">
            <Button
                android:id="@+id/cal_reset"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="C"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_del"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="Del"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_dot"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="."
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_add"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="+"
                android:textSize="20sp"
                android:textColor="#FFF"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">
            <Button
                android:id="@+id/cal_7"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="7"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_8"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="8"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_9"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="9"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_minus"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="-"
                android:textSize="20sp"
                android:textColor="#FFF"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">
            <Button
                android:id="@+id/cal_4"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="4"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_5"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="5"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_6"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="6"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_mul"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="*"
                android:textSize="20sp"
                android:textColor="#FFF"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">
            <Button
                android:id="@+id/cal_1"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="1"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_2"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="2"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_3"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="3"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_div"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="/"
                android:textSize="20sp"
                android:textColor="#FFF"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">
            <Button
                android:id="@+id/cal_0"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="3"
                android:text="0"
                android:textSize="20sp"
                android:textColor="#FFF"/>

            <Button
                android:id="@+id/cal_equal"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="match_parent"
                android:text="="
                android:textSize="20sp"
                android:textColor="#FFF"/></LinearLayout>
    </LinearLayout>
</RelativeLayout>

次层就2个Layout,一个放展示信息的两个TextView,一个用于放键盘,键盘的每列又是一个LinearLayout。
按道理计算器的界面应该和计算逻辑分开了,这里偷懒了下,就把计算器界面和计算逻辑放在一起了(最开始第一版是所有的布局,计算逻辑全写在activity里面:))。然后新建一个计算器视图类CalculatorLayout与布局文件calculator_view.xml对应。

然后再activity_main.xml布局文件里引入这个计算器视图就可以了。码如下:

<com.example.frank.calculator.CalculatorLayout
    android:id="@+id/calculator_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.example.frank.calculator.CalculatorLayout>

这样就完成了在activity里加载我们的计算器视图。下面具体看看表达式的处理。
CalculatorLayout的完整代码如下:

public class CalculatorLayout extends FrameLayout {
        private TextView expression;
        private TextView result;
        public CalculatorLayout(Context context, AttributeSet attrs) {
                super(context, attrs);
                LayoutInflater.from(context).inflate(R.layout.calculator_view, this);
                List<Button> buttons = getAllButtons(this);
                expression = (TextView)findViewById(R.id.cal_reg_text);
                result = (TextView)findViewById(R.id.cal_result);
                for (Button button :buttons) {
                        button.setOnClickListener((v) -> {
                                String text = (String)((Button)v).getText();
                                if (text.equals("Del")) {
                                        String tempText = (String) expression.getText();
                                        if (tempText.length() > 0) {
                                                expression.setText(tempText.substring(0, tempText.length() - 1));
                                        }
                                } else if (text.equals("C")) {
                                        expression.setText("");
                                        result.setText("");
                                } else if (text.equals("=")) {
                                        String expression = (String) this.expression.getText();
                                        if (isExpressionValidBeforeCal(expression)) {
                                                result.setText("" + calculateExpression(expression));
                                        }
                                } else {
                                        String tempText = (String) expression.getText();
                                        if (isValidForInput(text.charAt(0))) {
                                                tempText = tempText + text;
                                                expression.setText(tempText);
                                        }
                                }
                        });
                }

        }

        /*
        获取所有的Button,主要是为了统一给他们添加响应事件
        * */
        private List<Button> getAllButtons(View view) {
                List<Button> buttons = new ArrayList<>();
                if (view instanceof ViewGroup) {
                        ViewGroup vp= (ViewGroup)view;
                        for (int i = 0; i < vp.getChildCount(); i++) {
                                View temp = vp.getChildAt(i);
                                if (temp instanceof Button) {
                                        buttons.add((Button)temp);
                                }
                                buttons.addAll(getAllButtons(temp));
                        }
                }
                return buttons;
        }

        /*
        在计算表达式值前,检查表达式是否合法
        * */
        private boolean isExpressionValidBeforeCal(String expression) {
                if (expression.length() == 0) {
                        return false;
                }
                Character character = expression.charAt(expression.length() - 1);
                if (character.equals('+') || character.equals('-') || character.equals('*') || character.equals('/') || character.equals('.')) {
                        return false;
                }
                return true;
        }

        /*
        检查当前输入是否合法
        * */
        private boolean isValidForInput(Character input) {
                String tempText = (String) expression.getText();
                if (tempText.length() > 0) {
                        Character lastChar = tempText.charAt(tempText.length() - 1);
                        if (Character.isDigit(lastChar)) {//如果最后一位数字
                                if (input.equals('.')) {//如果输入的是'.',那么最后一个操作数中不能包含'.'
                                        String lastOperatorNumber = lastOperatorNumber(tempText);
                                        return !lastOperatorNumber.contains(".");
                                }
                                return true;
                        } else {
                                if (lastChar.equals('.')) {//如果最后一位不是'.'
                                        return Character.isDigit(input);
                                } else if (lastChar.equals('+') || lastChar.equals('-') || lastChar.equals('*') || lastChar.equals('/')) {
                                        return Character.isDigit(input);
                                }
                                return false;
                        }
                } else {
                        return Character.isDigit(input);
                }
        }

        /*
        获取表达式的最后一个操作数
        * */
        private String lastOperatorNumber(String expression) {
                if (expression.length() == 0)
                        return "";
                int start = 0;
                for (int i = expression.length() - 1; i >= 0; i--) {
                        Character character = expression.charAt(i);
                        if (character.equals('+') || character.equals('-') || character.equals('*') || character.equals('/')) {
                                start = i + 1;
                                break;
                        }
                }
                return expression.substring(start);
        }

        /*
        获取表达式第一个操作数,返回值是操作数的索引
        * */
        private int firstOperatorNumber(String expression) {
                if (expression.length() == 0) {
                        return -1;
                }
                for (int i = 1; i < expression.length(); i++) {
                        Character character = expression.charAt(i);
                        if (character.equals('+') || character.equals('-') || character.equals('*') || character.equals('/')) {
                                return i;
                        }
                }
                return expression.length();
        }

        /*
        计算表达式的值
        * */
        private double calculateExpression(String expression) {
                if (expression.length() == 0) {
                        return 0;
                }
                double result = 0;
                String temp = expression;
                while (temp.length() > 0) {
                        String temp2 = temp;
                        int firstIndex = firstOperatorNumber(temp2);
                        double firstNum = Double.valueOf(temp2.substring(0, firstIndex));
                        if (firstIndex == temp.length()) {
                                return firstNum;
                        }
                        Character operator1 = temp.charAt(firstIndex);
                        temp2 = temp2.substring(firstIndex + 1);
                        int secondIndex = firstOperatorNumber(temp2);
                        double secondNum = Double.valueOf(temp2.substring(0, secondIndex));
                        if (operator1.equals('*')) {
                                result = firstNum * secondNum;
                                if (secondIndex >= temp2.length())
                                        break;
                                temp = String.valueOf(result) + temp2.substring(secondIndex);
                        } else if (operator1.equals('/')) {
                                result = firstNum / secondNum;
                                if (secondIndex >= temp2.length())
                                        break;
                                temp = String.valueOf(result) + temp2.substring(secondIndex);
                        } else if (operator1.equals('+') || operator1.equals('-')) {
                                if (secondIndex == temp2.length()) {
                                        return operator1.equals('+') ? (firstNum + secondNum) : (firstNum - secondNum);
                                } else {
                                        Character operator2 = temp2.charAt(secondIndex);
                                        if (operator2.equals('+') || operator2.equals('-')) {
                                                double result2 = operator1.equals('+') ? (firstNum + secondNum) : (firstNum - secondNum);
                                                result += result2;
                                                temp = result2 + temp2.substring(secondIndex);
                                        } else {
                                                int max = maxMultiDivExpForExpression(temp2);//拿到最长的乘除法表达式
                                                temp = firstNum + ( "" + operator1 + String.valueOf(calMultiOrDivForExpression(temp2.substring(0, max)))) + temp2.substring(max);
                                        }
                                }
                        }
                }
                return result;
        }

        /*
        获取最长乘除法表达式的索引值
        * */
        private int maxMultiDivExpForExpression(String expression) {
                for (int i = 0; i < expression.length(); i++) {
                        Character character = expression.charAt(i);
                        if (character.equals('-') || character.equals('+')) {
                                return i;
                        }
                }
                return expression.length();
        }

        /*
        计算乘除法表达式的值,递归计算
        * */
        private double calMultiOrDivForExpression(String expression) {
                if (isExpressionDigit(expression)) {
                        return Double.valueOf(expression);
                }
                int firstIndex = firstOperatorNumber(expression);
                double firstNum = Double.valueOf(expression.substring(0, firstIndex));
                Character operator = expression.charAt(firstIndex);
                return operator.equals('*') ? firstNum * calculateExpression(expression.substring(firstIndex + 1)) : firstNum / calculateExpression(expression.substring(firstIndex + 1));
        }

        /*
        判断表达式是否是数字
        * */
        private boolean isExpressionDigit(String expression) {
                try {
                        Double.valueOf(expression);
                        return true;
                } catch (NumberFormatException e) {
                        return false;
                }
        }
}

整个CalculatorLayout还是比较简单的。在CalculatorLayout的构造函数里首先加载布局,然后拿到各个控件,比如两个TextView(因为很多地方都要对展示信息进行处理,所以把他们做成了私有属性)。然后是为各个Button设置点击回调,用Lamada表达式(lamada表达式需要JDK1.8级以上才支持,需要在模块的gralde脚本里加上compileOptions { sourceCompatibility org.gradle.api.JavaVersion.VERSION18 targetCompatibility org.gradle.api.JavaVersion.VERSION18}, 以及 jackOptions { enabled true } 这样的配置)统一处理他们的回调方法,里面根据各个Button的text判断怎么处理。具体计算表达式上面代码里都有相应的注释,大家有兴趣的可以copy去测试下,如果有问题欢迎随时反馈给我。最后我想说的是,大神们有没有好的Android项目可以练手啊,循序渐进的对新手友好的,跪谢(不得不说还是专门的Markdown写作软件排版更美观:))。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容