-
pt.1~3)안드로이드 스튜디오 계산기 (프로젝트 생성부터 완성까지!)Java/Android Studio 2020. 9. 10. 18:00
Android Studio 를 이용해 계산기를 만들어보자!
1. 프로젝트 생성하기
새로운 프로젝트를 생성한다.
이번시간에는 텅 빈 액티비티를 선택한다.
Next를 누르면 프로젝트 이름과 경로를 설정하는 창이 나온다. 설정이 완료되면 [Finish]
실행하면 가장 기본적인 요소 2가지가 보인다.
activity_main.xml은 인터페이스를 구성하는 요소이고 MainActivity는 함수를 컨트롤하는 부분이다.
ASP.NET으로 비유하면 activity_main.xml 과 MainActivity.java는 각각 뷰와 컨트롤에 대응한다.
2. 에뮬레이터 실행하기
AVD Manager를 실행시킨다.
필자는 이미 에뮬레이터를 생성해서 Galaxy S7 API R이 보인다.
+Create Virtual Device를 눌러 가상머신을 생성하자
가상머신의 기종을 선택하자. 원하는 기종이 없을 경우 스펙을 직접 입력하거나 import할 수 있다.
시스템 이미지 설정
여기서 이미지란 일반적으로 뜻하는 사진이 아닌 OS를 구동하기 위한 패키지를 뜻한다.
기타 설정을 마치고 [Finish]
상단의 Run app 버튼을 누른다 (Shift + F10)
에뮬레이터가 실행되고 현재까지 만들어진 어플리케이션이 실행된다.
스마트폰 연결 없이도 테스트 실행을 해볼 수 있는 것이다.
또는 안드로이드 스마트폰을 연결해서 앱을 직접 실행할 수 있다.
그러기 위해서는 개발자 모드를 실행하고 USB연결을 해줘야 한다.
다음 글에 개발자 모드 설정법이 잘 나와있다.
요약하자면 [설정]-[디바이스 정보] - [소프트웨어 정보] - [빌드 번호] 를 여러번 클릭해준다.
그러면
위와 같이 맨 밑에 개발자 옵션이 생긴다.
들어가서 USB 디버깅을 ON 해주는 것도 잊지 말자.
이까지 하면 기본적인 개발 준비가 완료되었다.
계산기 프로그램 출처: dirrito.tistory.com/13
3. 컨트롤 추가하기
먼저 컨트롤을 추가해 보자
다시 activity_main.xml을 가보면 우측 상단에 3개의 아이콘이 보인다.
각각 코드-분할-디자인 으로 [코드]만 보거나 [코드랑 디자인] 둘다 보거나 [디자인]만 볼 수 있다.
위 사진은 디자인이다.
디자인에서는 컨트롤을 드래그 앤 드롭으로 쉽게 추가할 수 있어서 가시성이 좋다.
Palette의 컨트롤들을 화면 또는 Component tree에 드롭할 수 있다.
드래그 앱 드롭으로 LinearLayout(vertical)을 추가해 보았다.
위와 같이 Component Tree에 추가됨을 알 수 있다.
Component Tree에서 부모개체와 자식개체를 잘 정리해야 컨트롤이 꼬이는 걸 방지할 수 있다.
activity_main.xml 파일에 바로 반영됨을 알 수 있다.
xml 파일에 위 같은 형식으로 입력해도 컨트롤은 똑같이 추가된다.
레이아웃 설정없이 마구잡이로 컨트롤을 추가하면 어플 실행시 좌상단에 컨트롤들이 몰려있는 현상이 생길 수 있다.
다음과 같이 xml 파일을 작성해본다.
[activity_main.xml]
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1.5" android:orientation="vertical"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> <TextView android:id="@+id/first_textView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:text="" android:textSize="30dp" /> </ScrollView> <TextView android:id="@+id/second_textView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:layout_weight="2" android:text="" android:textSize="30dp" /> </LinearLayout> <GridLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:columnCount="4" android:orientation="horizontal" android:rowCount="5"> <Button android:id="@+id/clear" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="C" /> <Button android:id="@+id/bracket" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="( )" /> <Button android:id="@+id/percent" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="%" /> <Button android:id="@+id/div" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="÷" /> <Button android:id="@+id/num7" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="7" /> <Button android:id="@+id/num8" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="8" /> <Button android:id="@+id/num9" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="9" /> <Button android:id="@+id/mul" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="X" /> <Button android:id="@+id/num4" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="4" /> <Button android:id="@+id/num5" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="5" /> <Button android:id="@+id/num6" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="6" /> <Button android:id="@+id/sub" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="-" /> <Button android:id="@+id/num1" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="1" /> <Button android:id="@+id/num2" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="2" /> <Button android:id="@+id/num3" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="3" /> <Button android:id="@+id/add" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="+" /> <Button android:id="@+id/back" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="back" /> <Button android:id="@+id/num0" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="0" /> <Button android:id="@+id/dot" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="." /> <Button android:id="@+id/equal" android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="=" /> </GridLayout> </LinearLayout>
LinearLayout─LinearLayout─ScrollView─first_textView
└second_textView
└GridLayout─(버튼20개)
의 구조로 되어있다.
버튼은
<Button android:id="@+id/equal" "equal"로 아이디 설정 android:layout_rowWeight="1" android:layout_columnWeight="1" android:layout_margin="-5dp" android:text="=" /> "="로 텍스트 설정
id와 텍스트를 설정해준다. 이외에 layout_rowWeight, layout_columnWeight 은 열, 행에서 버튼간의 비율을 설정할 때
쓴다. margin은 여백을 나타낸다.
xml을 수정하고 디자인을 확인하면
위와같이 되어있다.
버튼의 text를 설정한대로 보인다.
다음은
1. MainActivity.java를 수정한다.
2. CalculateHelper 클래스를 추가한다.
MainActivity.java.를 다음과 같이 수정한다.
[MainActivity.java]
package com.example.dumb; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity { CalculateHelper calculateHelper; boolean isDot; boolean isBracket; boolean isPreview; TextView textView; TextView textView2; int size; String result; Button num0; Button num1; Button num2; Button num3; Button num4; Button num5; Button num6; Button num7; Button num8; Button num9; Button add; Button sub; Button mul; Button div; Button clear; Button bracket; Button percent; Button back; Button dot; Button equal; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); calculateHelper = new CalculateHelper(); size=0; setButton(); setTextView(); } private void setButton(){ num0 = findViewById(R.id.num0); num1 = findViewById(R.id.num1); num2 = findViewById(R.id.num2); num3 = findViewById(R.id.num3); num4 = findViewById(R.id.num4); num5 = findViewById(R.id.num5); num6 = findViewById(R.id.num6); num7 = findViewById(R.id.num7); num8 = findViewById(R.id.num8); num9 = findViewById(R.id.num9); add = findViewById(R.id.add); sub = findViewById(R.id.sub); mul = findViewById(R.id.mul); div = findViewById(R.id.div); clear = findViewById(R.id.clear); bracket = findViewById(R.id.bracket); percent = findViewById(R.id.percent); back = findViewById(R.id.back); dot = findViewById(R.id.dot); equal = findViewById(R.id.equal); num0.setOnClickListener(numClickListener); num1.setOnClickListener(numClickListener); num2.setOnClickListener(numClickListener); num3.setOnClickListener(numClickListener); num4.setOnClickListener(numClickListener); num5.setOnClickListener(numClickListener); num6.setOnClickListener(numClickListener); num7.setOnClickListener(numClickListener); num8.setOnClickListener(numClickListener); num9.setOnClickListener(numClickListener); add.setOnClickListener(markClickListener); sub.setOnClickListener(markClickListener); mul.setOnClickListener(markClickListener); div.setOnClickListener(markClickListener); clear.setOnClickListener(markClickListener); bracket.setOnClickListener(markClickListener); percent.setOnClickListener(markClickListener); back.setOnClickListener(markClickListener); dot.setOnClickListener(markClickListener); equal.setOnClickListener(markClickListener); } Button.OnClickListener numClickListener = new View.OnClickListener() { @Override public void onClick(View v) { switch(v.getId()){ case R.id.num0:textView.append("0");break; case R.id.num1:textView.append("1");break; case R.id.num2:textView.append("2");break; case R.id.num3:textView.append("3");break; case R.id.num4:textView.append("4");break; case R.id.num5:textView.append("5");break; case R.id.num6:textView.append("6");break; case R.id.num7:textView.append("7");break; case R.id.num8:textView.append("8");break; case R.id.num9:textView.append("9");break; } preview(); } }; Button.OnClickListener markClickListener = new View.OnClickListener(){ @Override public void onClick(View v){ switch (v.getId()) { case R.id.add: textView.append(" + "); isPreview = true; break; case R.id.sub: textView.append(" - "); isPreview = true; break; case R.id.mul: textView.append(" * "); isPreview = true; break; case R.id.div: textView.append(" / "); isPreview = true; break; case R.id.percent: textView.append(" % "); isPreview = true; break; case R.id.clear: textView.setText(""); textView2.setText(""); calculateHelper = new CalculateHelper(); isPreview = false; break; case R.id.bracket: if (!isBracket) { textView.append("( "); isBracket = true; } else { textView.append(" )"); isBracket = false; } isPreview = true; break; case R.id.back: size = textView.getText().length(); if (size != 0) textView.setText(textView.getText().toString().substring(0, size - 1)); if (size > 1) { if (calculateHelper.checkNumber(textView.getText().toString().substring(size - 2))) preview(); else { isPreview = false; textView2.setText(""); } } break; case R.id.dot: textView.append("."); isDot = true; break; case R.id.equal: result = textView.getText().toString(); double r = calculateHelper.process(result); if (!isDot) textView.setText(String.valueOf((int) r)); else textView.setText(String.valueOf(r)); textView2.setText(""); isDot = false; isPreview = false; break; } } }; private void preview(){ if(isPreview){ result = textView.getText().toString(); double r = calculateHelper.process(result); if(!isDot){ textView2.setText(String.valueOf((int)r)); }else{ textView2.setText(String.valueOf(r)); } } } private void setTextView(){ textView = findViewById(R.id.first_textView); textView2 = findViewById(R.id.second_textView); } }
여기서는 메인클래스 내에 CalculateHelper, TextView, Button을 선언한다.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); calculateHelper = new CalculateHelper(); size=0; setButton(); setTextView(); }
OnCreate 즉 실행되면 가장 먼저 위에 선언한 CalculateHelper, TextView, Button을 인스턴스화 한다.
Button.OnClickListener numClickListener = new View.OnClickListener() { @Override public void onClick(View v) { switch(v.getId()){ case R.id.num0:textView.append("0");break; case R.id.num1:textView.append("1");break; case R.id.num2:textView.append("2");break; case R.id.num3:textView.append("3");break; case R.id.num4:textView.append("4");break; case R.id.num5:textView.append("5");break; case R.id.num6:textView.append("6");break; case R.id.num7:textView.append("7");break; case R.id.num8:textView.append("8");break; case R.id.num9:textView.append("9");break; } preview(); } };
숫자 버튼에 OnClickListener를 연결해준다.
View에서 id를 받아서 각 버튼에 맞는 숫자를 textView에 더하도록 하는 함수다.
Button.OnClickListener markClickListener = new View.OnClickListener(){ @Override public void onClick(View v){ switch (v.getId()) { case R.id.add: textView.append(" + "); isPreview = true; break; case R.id.sub: textView.append(" - "); isPreview = true; break; case R.id.mul: textView.append(" * "); isPreview = true; break; case R.id.div: textView.append(" / "); isPreview = true; break; case R.id.percent: textView.append(" % "); isPreview = true; break; case R.id.clear: textView.setText(""); textView2.setText(""); calculateHelper = new CalculateHelper(); isPreview = false; break; case R.id.bracket: if (!isBracket) { textView.append("( "); isBracket = true; } else { textView.append(" )"); isBracket = false; } isPreview = true; break; case R.id.back: size = textView.getText().length(); if (size != 0) textView.setText(textView.getText().toString().substring(0, size - 1)); if (size > 1) { if (calculateHelper.checkNumber(textView.getText().toString().substring(size - 2))) preview(); else { isPreview = false; textView2.setText(""); } } break; case R.id.dot: textView.append("."); isDot = true; break; case R.id.equal: result = textView.getText().toString(); double r = calculateHelper.process(result); if (!isDot) textView.setText(String.valueOf((int) r)); else textView.setText(String.valueOf(r)); textView2.setText(""); isDot = false; isPreview = false; break; } } };
기호버튼도 마찬가지로 각각 버튼에 맞게 +,-,*,/ 를 textView에 더해준다.
isPreview는 사칙연산이나 괄호를 눌렀을때 true 그 이외에는 false로 설정된다.
이 isPreview 변수는
textView의 값을 result에 복사하고 CalculatorHelper에서 후위 표기식의 계산을 수행하도록 한다.
그리고 그 결과값을 textView2로 옮긴다.
4. CalculateHelper 클래스 추가
[File]-[New]-[Java Class]를 통해 클래스를 추가한다.
내용은 다음과 같이 작성한다.
[CalculateHelper.java]
package com.example.dumb; import java.util.ArrayList; import java.util.HashMap; import java.util.Stack; public class CalculateHelper { public static double num1; public static double num2; public static double resultNumber; private ArrayList splitTokens(String equation){ String[] constant = equation.split(" "); ArrayList constantList = new ArrayList(); double number=0; boolean flag= false; for(String data:constant){ if(data.equals(" ")){ continue; } if(checkNumber(data)){ number = number*10+Double.parseDouble(data); flag=true; } else{ if(flag){ constantList.add(number); number=0; } flag=false; constantList.add(data); } } if(flag){ constantList.add(number); } return constantList; } private ArrayList infixToPostfix(ArrayList constant){ ArrayList result= new ArrayList(); HashMap level = new HashMap(); Stack stack = new Stack(); level.put("*",3); level.put("/",3); level.put("+",2); level.put("-",2); level.put("(",1); for(Object object : constant){ if(object.equals(")")){ stack.push(object); }else if(object.equals(")")){ while(!stack.peek().equals("(")){ Object val = stack.pop(); if(!val.equals("(")){ result.add(val); } } stack.pop(); }else if(level.containsKey(object)){ if(stack.isEmpty()){ stack.push(object); }else{ if(Double.parseDouble(level.get(stack.peek()).toString()) >= Double.parseDouble(level.get(object).toString())){ result.add(stack.pop()); stack.push(object); }else{ stack.push(object); } } }else{ result.add(object); } } while(!stack.isEmpty()){ result.add(stack.pop()); } return result; } private Double postFixEval(ArrayList expr){ Stack numberStack = new Stack(); for(Object o : expr){ if(o instanceof Double){ numberStack.push(o); }else if(o.equals("+")){ num1=(Double)numberStack.pop(); num2=(Double)numberStack.pop(); numberStack.push(num2+num1); }else if(o.equals("-")){ num1=(Double)numberStack.pop(); num2=(Double)numberStack.pop(); numberStack.push(num2-num1); }else if(o.equals("*")){ num1=(Double)numberStack.pop(); num2=(Double)numberStack.pop(); numberStack.push(num2*num1); }else if(o.equals("/")){ num1=(Double)numberStack.pop(); num2=(Double)numberStack.pop(); numberStack.push(num2/num1); } } resultNumber = (Double)numberStack.pop(); return resultNumber; } public Double process(String equation){ ArrayList postfix = infixToPostfix(splitTokens(equation)); Double result = postFixEval(postfix); return result; } public boolean checkNumber(String str){ char check; if(str.equals("")){ return false; } for(int i=0;i<str.length();i++){ check=str.charAt(i); if(check<48||check>58){ if(check!='.') return false; } } return true; } }
CalculatorHelper 클래스는 앞서 입력한 계산식을 후위 표기법으로 바꾼 후 연산을 실행한다.
CalculateHelper에는 splitTokens, infixToPostfix, postFixEval, process, checkNumber 함수가 있다.
[CalculateHelper.splitTokens] : 연산식을 ArrayList에 담아 반환한다.
이 글을 보면 스택 계산기의 후위 표기법에 대해 아주 잘 나타나 있다.
[CalculateHelper.infixToPostfix] : 중위 표기법을 후위 표기법으로 바꾼다.
private ArrayList infixToPostfix(ArrayList constant){ ArrayList result= new ArrayList(); HashMap level = new HashMap(); Stack stack = new Stack(); level.put("*",3); level.put("/",3); level.put("+",2); level.put("-",2); level.put("(",1); for(Object object : constant){ if(object.equals(")")){ stack.push(object); }else if(object.equals(")")){ while(!stack.peek().equals("(")){ Object val = stack.pop(); if(!val.equals("(")){ result.add(val); } } stack.pop(); }else if(level.containsKey(object)){ if(stack.isEmpty()){ stack.push(object); }else{ if(Double.parseDouble(level.get(stack.peek()).toString()) >= Double.parseDouble(level.get(object).toString())){ result.add(stack.pop()); stack.push(object); }else{ stack.push(object); } } }else{ result.add(object); } } while(!stack.isEmpty()){ result.add(stack.pop()); } return result; }
해쉬맵을 통해 우선순위를 곱셈, 나눗셈은 3, 덧셈 뺄셈은 2, 여는 괄호는 1로 설정해놨다.
if(object.equals(")")){ stack.push(object); }else if(object.equals(")")){ while(!stack.peek().equals("(")){ Object val = stack.pop(); if(!val.equals("(")){ result.add(val); } } stack.pop(); }
여는 괄호가 나오면 무조건 스택에 넣고 닫는 괄호가 나오면 여는 괄호까지의 모든 내용을 pop 한다.
else if(level.containsKey(object)){ if(stack.isEmpty()){ stack.push(object); }else{ if(Double.parseDouble(level.get(stack.peek()).toString()) >= Double.parseDouble(level.get(object).toString())){ result.add(stack.pop()); stack.push(object); }else{ stack.push(object); } } }else{ result.add(object); } } while(!stack.isEmpty()){ result.add(stack.pop()); }
우선순위가 높은게 나오면 pop해서 result에 추가하고 낮은걸 스택에 쌓는다.
[CalculateHelper.postFixEval]: 후위표기법의 식을 계산한다.
후위 표기법의 식을 탐색한다. 숫자가 나올경우 스택에 Push하고 연산자가 나올 경우 첫번째 pop한 수와 두번째로 pop
한 수에 연산을 한다.
[CalculateHelper.process]: 최종적으로 계산값을 리턴하는 함수
public Double process(String equation){ ArrayList postfix = infixToPostfix(splitTokens(equation)); Double result = postFixEval(postfix); return result; }
[CalculateHelper.checkNumber]: 아스키코드로 숫자인지 판별해 boolean값을 반환하는 함수
public boolean checkNumber(String str){ char check; if(str.equals("")){ return false; } for(int i=0;i<str.length();i++){ check=str.charAt(i); if(check<48||check>58){ if(check!='.') return false; } } return true; }
5. 앱 아이콘 변경, 빌드
다음으로 앱 아이콘을 바꾸고 빌드해보자.
[File]-[New]-[Image Asset]을 클릭한다.
Select Path로 이미지가 저장된 경로를 연다.
이미지를 선택하고 적당한 크기로 조절한다.
아이콘이 바뀌었음을 확인할 수 있다.
이렇게 에뮬레이터 대신 실제 기기로 실행해보면 어플이 기기에 배포된다. Google Play에 공유, 업로드 할 앱을 빌드하려면 다음과 같이 하면 된다.
[Build]-[Build Bundle(s) / APK(s)]-[Build APK(s)]
'Java > Android Studio' 카테고리의 다른 글
공공데이터 API 요청 보내고 응답 받기 (0) 2021.07.17 액티비티 간 전환하기 (2) 2021.06.27 안드로이드 스튜디오 계산기(3): CalculateHelper 클래스 추가, 빌드 (0) 2020.09.10 안드로이드 스튜디오 계산기(2): 레이아웃,버튼 추가, MainActivity (0) 2020.09.10 안드로이드 스튜디오 계산기(1): 프로젝트 생성, 에뮬레이터 실행 (0) 2020.09.10