菜鳥學(xué)習(xí)安卓--第一個(gè)自寫安卓app

最近基本上把郭霖大神的《第一行代碼-第二版》看完了谆焊,也跟著敲了幾個(gè)app的例子,但是總覺(jué)得跟著大神寫浦夷,自己提高有限辖试,還是需要自己親自實(shí)現(xiàn)才帶感。在網(wǎng)上找了個(gè)簡(jiǎn)易計(jì)算器的效果圖劈狐,自己就寫完了罐孝。算是自己親自寫的第一個(gè)安卓app吧。不說(shuō)先看最終的效果圖:


最終效果圖

首先分析下這個(gè)app幾個(gè)大致功能點(diǎn)吧:

  • 界面部分肥缔。上面兩個(gè)地方一個(gè)用于展示輸入的表達(dá)式莲兢,一個(gè)用于顯示計(jì)算的表達(dá)式結(jié)果,用TextView正好就可以了续膳。下面的鍵盤輸入部分改艇,每個(gè)字符(或者字符串)正好用一個(gè)Button表示,而且可以看到上面4列都是那種1/4屏幕寬坟岔,正好用Android里面的layout_weight權(quán)重的方式來(lái)布局谒兄。最后一列也是,只是一個(gè)是3/4社付,一個(gè)是1/4承疲。因?yàn)橐獙㈡I盤部分沉底,所以整個(gè)界面使用RelativeLayout鸥咖,然后RelativeLayout里面套兩個(gè)LinearLayout纪隙,一個(gè)放上面兩個(gè)展示用的TextView,一個(gè)放下面的鍵盤扛或。
  • 表達(dá)式處理绵咱。主要分為表達(dá)式有效性驗(yàn)證以及表達(dá)式求值。表達(dá)式有效驗(yàn)證包括當(dāng)前輸入是否合法,比如前面字符已經(jīng)是一個(gè)操作符悲伶,這個(gè)時(shí)候就不能輸入操作數(shù)或者"."號(hào)等艾恼。還有就是當(dāng)輸入"="進(jìn)行求值前需要整個(gè)表達(dá)式做一次驗(yàn)證(主要是最后一個(gè)字符不能是等號(hào),操作符)麸锉。表達(dá)式求值主要是四則混合運(yùn)算時(shí)候钠绍,計(jì)算表達(dá)式有優(yōu)先級(jí)的問(wèn)題。由于沒(méi)有括號(hào)運(yùn)算符可以花沉,相對(duì)而言簡(jiǎn)單很多(沒(méi)有括號(hào)的各種嵌套柳爽,優(yōu)先級(jí)只用考慮加減乘除的優(yōu)先級(jí)就可以了)。所以對(duì)于表達(dá)式遇到一個(gè)乘法或者除法碱屁,只需要把這個(gè)乘除法計(jì)算的結(jié)果然后替換掉它們?cè)仍诒磉_(dá)式中的位置即可磷脯,一直不停的迭代直至計(jì)算出最終結(jié)果為止。 BB了這么多娩脾,要show my code了赵誓。
    布局,直接新建一個(gè)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個(gè)Layout碰声,一個(gè)放展示信息的兩個(gè)TextView诡蜓,一個(gè)用于放鍵盤,鍵盤的每列又是一個(gè)LinearLayout胰挑。
按道理計(jì)算器的界面應(yīng)該和計(jì)算邏輯分開了蔓罚,這里偷懶了下,就把計(jì)算器界面和計(jì)算邏輯放在一起了(最開始第一版是所有的布局洽腺,計(jì)算邏輯全寫在activity里面:))。然后新建一個(gè)計(jì)算器視圖類CalculatorLayout與布局文件calculator_view.xml對(duì)應(yīng)覆旱。

然后再activity_main.xml布局文件里引入這個(gè)計(jì)算器視圖就可以了蘸朋。碼如下:

<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里加載我們的計(jì)算器視圖。下面具體看看表達(dá)式的處理扣唱。
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藕坯,主要是為了統(tǒng)一給他們添加響應(yīng)事件
        * */
        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;
        }

        /*
        在計(jì)算表達(dá)式值前,檢查表達(dá)式是否合法
        * */
        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;
        }

        /*
        檢查當(dāng)前輸入是否合法
        * */
        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)) {//如果最后一位數(shù)字
                                if (input.equals('.')) {//如果輸入的是'.'噪沙,那么最后一個(gè)操作數(shù)中不能包含'.'
                                        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);
                }
        }

        /*
        獲取表達(dá)式的最后一個(gè)操作數(shù)
        * */
        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);
        }

        /*
        獲取表達(dá)式第一個(gè)操作數(shù)炼彪,返回值是操作數(shù)的索引
        * */
        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();
        }

        /*
        計(jì)算表達(dá)式的值
        * */
        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);//拿到最長(zhǎng)的乘除法表達(dá)式
                                                temp = firstNum + ( "" + operator1 + String.valueOf(calMultiOrDivForExpression(temp2.substring(0, max)))) + temp2.substring(max);
                                        }
                                }
                        }
                }
                return result;
        }

        /*
        獲取最長(zhǎng)乘除法表達(dá)式的索引值
        * */
        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();
        }

        /*
        計(jì)算乘除法表達(dá)式的值,遞歸計(jì)算
        * */
        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));
        }

        /*
        判斷表達(dá)式是否是數(shù)字
        * */
        private boolean isExpressionDigit(String expression) {
                try {
                        Double.valueOf(expression);
                        return true;
                } catch (NumberFormatException e) {
                        return false;
                }
        }
}

整個(gè)CalculatorLayout還是比較簡(jiǎn)單的正歼。在CalculatorLayout的構(gòu)造函數(shù)里首先加載布局辐马,然后拿到各個(gè)控件彤路,比如兩個(gè)TextView(因?yàn)楹芏嗟胤蕉家獙?duì)展示信息進(jìn)行處理扬卷,所以把他們做成了私有屬性)。然后是為各個(gè)Button設(shè)置點(diǎn)擊回調(diào),用Lamada表達(dá)式(lamada表達(dá)式需要JDK1.8級(jí)以上才支持棺滞,需要在模塊的gralde腳本里加上compileOptions { sourceCompatibility org.gradle.api.JavaVersion.VERSION18 targetCompatibility org.gradle.api.JavaVersion.VERSION18}, 以及 jackOptions { enabled true } 這樣的配置)統(tǒng)一處理他們的回調(diào)方法患久,里面根據(jù)各個(gè)Button的text判斷怎么處理元咙。具體計(jì)算表達(dá)式上面代碼里都有相應(yīng)的注釋,大家有興趣的可以copy去測(cè)試下湃密,如果有問(wèn)題歡迎隨時(shí)反饋給我诅挑。最后我想說(shuō)的是,大神們有沒(méi)有好的Android項(xiàng)目可以練手啊泛源,循序漸進(jìn)的對(duì)新手友好的拔妥,跪謝(不得不說(shuō)還是專門的Markdown寫作軟件排版更美觀:))。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俩由,一起剝皮案震驚了整個(gè)濱河市毒嫡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幻梯,老刑警劉巖兜畸,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異碘梢,居然都是意外死亡咬摇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門煞躬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肛鹏,“玉大人,你說(shuō)我怎么就攤上這事恩沛≡谌牛” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵雷客,是天一觀的道長(zhǎng)芒珠。 經(jīng)常有香客問(wèn)我,道長(zhǎng)搅裙,這世上最難降的妖魔是什么皱卓? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮部逮,結(jié)果婚禮上娜汁,老公的妹妹穿的比我還像新娘。我一直安慰自己兄朋,他們只是感情好掐禁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般穆桂。 火紅的嫁衣襯著肌膚如雪宫盔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天享完,我揣著相機(jī)與錄音灼芭,去河邊找鬼。 笑死般又,一個(gè)胖子當(dāng)著我的面吹牛彼绷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茴迁,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼寄悯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了堕义?” 一聲冷哼從身側(cè)響起猜旬,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倦卖,沒(méi)想到半個(gè)月后洒擦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怕膛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年熟嫩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褐捻。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掸茅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柠逞,到底是詐尸還是另有隱情昧狮,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布板壮,位于F島的核電站逗鸣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏个束。R本人自食惡果不足惜慕购,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一聊疲、第九天 我趴在偏房一處隱蔽的房頂上張望茬底。 院中可真熱鬧,春花似錦获洲、人聲如沸阱表。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)最爬。三九已至涉馁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爱致,已是汗流浹背烤送。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留糠悯,地道東北人帮坚。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像互艾,于是被迫代替她去往敵國(guó)和親试和。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容