관리 메뉴

Bbaktaeho

[Android] 계산기 앱 만들기 (안드로이드, 계산기 애플리케이션, 후위 표기법, Calculator, infix, postfix) 본문

개발 (Develop)/안드로이드 (Android)

[Android] 계산기 앱 만들기 (안드로이드, 계산기 애플리케이션, 후위 표기법, Calculator, infix, postfix)

Bbaktaeho 2020. 9. 14. 18:27
반응형

2020-09-14 수정 : (우리가 일상에서 이용하는 수식은 중위 표기법입니다)

들어가며


자바 언어를 검색을 통해서 익히다 보니 어떤 util이 더 좋은지 아직 감이 안 잡혔고 메서드를 만들어 사용하는 것도 많이 부족합니다.

때문에 자바 언어가 많이 미숙해서 코드가 좋지 못한 점 양해 부탁드립니다.

제가 구현한 코드는 답이 아닙니다. 참고로만 봐주시면 감사하겠습니다.

 

이번에 만들어볼 계산기는 윈도우 계산기입니다.

윈도우 계산기
안드로이드 계산기 (최대한 비슷하게 해봄ㅋㅋ)

간단한 연산만 할 수 있도록 구현하겠습니다.

 

참고로 계산기를 구현하는 것은 쉬운 일이 아닙니다. 저 역시 구현하면서 논리적인 생각, 자료구조를 떠올리며 많은 노력을 쏟았습니다.

프로젝트 생성


bbaktaeho-95.tistory.com/57

 

[Android] 자기소개 앱 만들기 (안드로이드 기초, Splash)

새로운 프로젝트 생성하는 방법은 아래 포스팅에서 확인해주세요. [개발(develop)/안드로이드(android)] - [Android] Click 이벤트로 Toast 생성하기 (setOnclickListener, makeToast) 들어가며 동작하는 기능 없..

bbaktaeho-95.tistory.com

위의 포스팅에서 프로젝트 생성과 Splash 구현까지 그대로 따라 하면 됩니다.

res/values/colors.xml


프로젝트에서 사용할 색상을 먼저 작성합니다.

<color name="colorBlack">#000000</color>
<color name="colorWhite">#FFFFFF</color>
<color name="colorMyGray">#E6E6E6</color>

검은색, 흰색, 회색을 추가했습니다.

res/values/styles.xml


화면에 보일 스타일을 따로 작성했습니다.

메인 파일에서 추가하지 않고 어떻게 해야 스타일을 적용시킬 수 있을까 찾아보다가 styles.xml 에서 원하는 스타일을 작성하면 layout들의 스타일, 테마까지 쉽게 적용시킬 수 있었습니다.

<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
	<item name="android:windowBackground">@color/colorWhite</item>
</style>

<style name="MainTheme" parent="Theme.AppCompat.Light.DarkActionBar">
	<item name="android:windowBackground">@color/colorMyGray</item>
</style>

Splash 화면, Main 화면에 적용시킬 스타일만 작성했습니다.

res/layout/activity_main.xml


이제 UI를 만들어보겠습니다.

가장 먼저 루트 레이아웃으로 LinearLayout을 선택했고 그 안에 RelativeLayout, TableLayout 으로 구현했습니다.

각 TableRow에 버튼을 균형적으로 배치할 수 있도록 TableLayout의 속성 값으로 android:stretchColumns="*" 옵션을 추가했습니다.

<TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1">

            <Button
                android:id="@+id/btn_mod"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:backgroundTint="#3EBAB4B4"
                android:onClick="buttonClick"
                android:text="%"
                android:textSize="18sp" />

            <Button
                android:id="@+id/button2"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:backgroundTint="#3EBAB4B4"
                android:clickable="false"
                android:enabled="false"
                android:text="CE"
                android:textSize="18sp" />

            <Button
                android:id="@+id/btn_clear"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:backgroundTint="#3EBAB4B4"
                android:onClick="clearClick"
                android:text="C"
                android:textSize="18sp" />

            <Button
                android:id="@+id/btn_delete"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:backgroundTint="#3EBAB4B4"
                android:text="Del"
                android:onClick="deleteClick"
                android:textSize="18sp" />
</TableRow>

하나의 TableRow에 들어있는 Button들입니다.

 

여기서 버튼에 onClick 속성이 있는데 자바, 코틀린 코드에서 리스너 메서드를 구현하지 않아도 각 뷰 컴포넌트들에게 직접적으로 메서드를 연결할 수 있었습니다.

이러한 방법을 추천하지 않는다고 하지만 단순히 어떤 버튼이 클릭되었는지만 아는 기능뿐이고 메인 코드가 줄어드는 장점이 있기 때문에 적용했습니다.

 

다음은 수식과 결과가 나오는 TextView입니다.

여기는 RelativeLayout을 이용해서 구현했습니다.

<RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="171dp">

        <TextView
            android:id="@+id/txt_expression"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_alignParentTop="true"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginTop="44dp"
            android:layout_marginRight="20dp"

            android:hint="expression"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/txt_result"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/txt_expression"
            android:layout_alignRight="@+id/txt_expression"

            android:layout_marginTop="25dp"
            android:hint="result"
            android:textColor="@color/colorBlack"
            android:textSize="36sp"
            android:textStyle="bold" />
</RelativeLayout>

MainActivity.java


앞서 말씀드렸던 onClick 속성을 적용하려면 View 타입의 매개변수가 있는 메서드를 선언해야 합니다.

public void buttonClick(View v) {...}

특히 public 제어자로 선언하지 않으면 사용할 수 없습니다. (엄청 삽질함)

자꾸 새로 만들라는 경고 메시지만 나옵니다.

 

다음은 MainActivity 클래스의 필드입니다.

private TextView txtExpression;
private TextView txtResult;
private List<Integer> checkList; // -1: 이콜, 0: 연산자, 1: 숫자, 2: . / 예외 발생을 막는 리스트
private Stack<String> operatorStack; // 연산자를 위한 스택
private List<String> infixList; // 중위 표기
private List<String> postfixList; // 후위 표기

TextView들은 수식과 계산 결과 UI입니다.

나머지 스택과 리스트는 계산, 예외 처리를 하기 위한 필드입니다.

 

다음으로 전체 MainActivity 클래스의 메서드들입니다.

예외나 추가적인 기능을 처리하느라 메서드가 많아지고 서로 연관성이 좀 생겼습니다.. 

전체 코드는 깃허브 링크로 공유하겠습니다. 포스팅에서 몇 가지 메서드들만 다뤄보겠습니다.

 

init()

    // 필드 초기화
    void init() {
        txtExpression = findViewById(R.id.txt_expression);
        txtResult = findViewById(R.id.txt_result);
        checkList = new ArrayList<>();
        operatorStack = new Stack<>();
        infixList = new ArrayList<>();
        postfixList = new ArrayList<>();

        ActionBar actionBar = getSupportActionBar();
        assert actionBar != null;
        actionBar.hide();
    }

필드들을 초기화하고 ActionBar를 숨김 처리합니다.

 

getWeight(String)

    // 연산자 가중치 (우선순위 *,/,%,+,-)
    int getWeight(String operator) {
        int weight = 0;
        switch (operator) {
            case "X":
            case "/":
                weight = 5;
                break;
            case "%":
                weight = 3;
                break;
            case "+":
            case "-":
                weight = 1;
                break;
        }
        return weight;
    }

연산자의 우선순위에 따라 가중치를 부여하는 메서드입니다.

후위 표기법으로 나타낼 때 가장 중요한 역할을 합니다.

 

infixToPostfix()

    // 전위 -> 후위
    void infixToPostfix() {
        for (String item : infixList) {
            // 피연산자
            if (isNumber(item)) postfixList.add(String.valueOf(Double.parseDouble(item)));
            // 연산자
            else {
                if (operatorStack.isEmpty()) operatorStack.push(item);
                else {
                    if (getWeight(operatorStack.peek()) >= getWeight(item)) postfixList.add(operatorStack.pop());
                    operatorStack.push(item);
                }
            }
        }
        while (!operatorStack.isEmpty()) postfixList.add(operatorStack.pop());
    }

중위 표기법을 후위 표기법으로 변환하는 메서드입니다.

미리 저장했던 infixList(중위 표기법)에서 operatorStack을 이용해 연산자 우선순위에 따라 postfixList(후위 표기법)에 추가합니다.

출처 : https://wayhome25.github.io/cs/2017/04/18/cs-22/ 설명이 한 번에 이해되서 가지고 왔습니다.

 

calculate()

    // 계산
    String calculate(String num1, String num2, String op) {
        double first = Double.parseDouble(num1);
        double second = Double.parseDouble(num2);
        double result = 0.0;
        try {
            switch (op) {
                case "X": result = first * second;break;
                case "/": result = first / second;break;
                case "%": result = first % second;break;
                case "+": result = first + second;break;
                case "-": result = first - second;break;
            }
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), "연산할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
        return String.valueOf(result);
    }

실질적인 계산을 하는 메서드입니다.

결과 값을 문자열로 변환하고 리턴해줍니다.

실행


동영상은 팝업창 또는 전체 화면으로 봐주세요.

GitHub


 

github.com/bbaktaeho/all-my-studies/tree/56999b73e76c0b3bbff89a3c5f22d3c0ef8937fe/android/Calculator/app/src/main/java/com/example/calculator

 

bbaktaeho/all-my-studies

👨‍🏫 배움은 끝이 없다고 합니다.. Contribute to bbaktaeho/all-my-studies development by creating an account on GitHub.

github.com

참고 자료


recipes4dev.tistory.com/126

 

안드로이드 렐러티브레이아웃. (Android RelativeLayout)

1. 안드로이드 RelativeLayout 클래스. [안드로이드 리니어레이아웃. (Android LinearLayout)]에서 설명한 LinearLayout 을 사용하여 UI 레이아웃을 구성하다보면, 원하는 배치 구조를 만들기가 쉽지 않은 경우��

recipes4dev.tistory.com

wayhome25.github.io/cs/2017/04/18/cs-22/

 

강의노트 21. 자료구조 - stack을 이용한 후위표기법 계산기 만들기 · 초보몽키의 개발공부로그

패스트캠퍼스 컴퓨터공학 입문 수업을 듣고 중요한 내용을 정리했습니다. 개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.

wayhome25.github.io

recipes4dev.tistory.com/55

 

버튼 클릭 이벤트를 처리하는 몇 가지 방법. (Android Button Click Event)

1. Button의 클릭 이벤트 처리 안드로이드에서 Button 클릭 이벤트를 처리하기 위해서는 리스너(listener)의 개념과 구현 방식을 이해하고 있어야 합니다. 만약 리스너에 대한 내용을 처음 접하신다면,

recipes4dev.tistory.com

 

반응형