그림과 같이 입력한 값을 보여주는 입력상자(EditText) 하나와 15개의 버튼으로 구성된 간단한 계산기를 만들면서 안드로이드 프로그래밍 기초를 정리한다.
정리 방법은 다음과 같이 단계별로 예제를 구현하고, 예제 소스는 GitHub에서 받을 수 있다.
- 계산기 디자인과 기본 코드 (branch: master)
- 생성한 클래스를 이벤트 리스너로 사용 (branch: step1)
- 코드 줄이기 (branch: step2)
- 실제 계산 기능 구현 (branch: step3)
- 후위 표기법으로 계산 기능 구현 (branch: step4)
- 동적 생성 (branch: step5)
각각의 단계별 예제는 GitHub의 branch에서 받을 수 있다.
먼저, 계산기를 그림과 같이 디자인 하면서
레이아웃(Layout) 사용법과 디자인된 개체에 이벤트를 연결하는 방법을 익힌다.
안드로이드 스튜디오에서 새로운 프로젝트를 생성하고,
디자인을 위해 activity_main.xml (app/src/main/res/layout/) 파일을 열어서 다음과 같이 코드를 작성한다.
전체코드
전체 레이아웃을 LinearLayout으로 지정하여 생성하고 [라인 1],
전체 레이아웃의 자식으로 EditText(입력상자) 하나 [라인 10]와 4개의 LinearLayout를 생성한다 [라인 18~].
하위에 있는 4개의 LinearLayout은 각각 4개(마지막은 3개)의 버튼을 자식으로 가진다 [라인 23~].
자식으로 가진다는 의미는 XML에서 <LinearLayout>와 </LinearLayout>사이에 코딩 한다는 의미이다.
간단하게 LinearLayout, EditText, Button의 주요 속성을 정리한다.
LinearLayout의 layout_width와 layout_height는 위젯(widget)의 크기를 의미한다.
너비(width)와 높이(height)를 지정하는 것으로 match_parent나 wrap_content로 지정하거나 직접 값을 dp단위로 입력할 수 있다.
match_parent는 부모 크기에 100%로 맞추는 것이고,
wrap_content는 그 위젯의 내용물(content)에 맞추어 자동으로 크기를 조절하는 것이다.
여기에서는 스마트폰의 해상도에 맞게 크기가 조절되도록 값을 직접 입력하지 않고,
match_parent나 wrap_content를 이용하였다.
최상위 LinearLayout은 너비와 높이를 모두 부모에 맞추었다 [라인 5, 6].
최상위 LinearLayout은 부모가 없기 때문에 휴대폰 크기에 맞추
orientation은 자식 위젯의 나열 방향을 의미하는 것으로
EditText와 4개의 LinearLayout는 수직(vertical)으로 쌓았고, 각 버튼들은 수평(horizontal)으로 쌓았다.
LinearLayout외에도 FrameLayout,TableLayout, GridLayout, RelativeLayout 등이 있다.
EditText는 사용자가 입력하는 값을 받는 위젯으로 너비와 높이는 레이아웃과 동일하게 사용한다 [라인 10].
너비는 화면 너비만큼[라인 12], 높이는 컨텐츠 높이로 지정하였다 [라인 13].
id는 Java에서 XML에 작성된 위젯을 찾기 위한 식별자를 의미한다 [라인 11].
실제 Id의 값은 @+id/위에 지정되고, 지정된 id는 안드로이드에서 관리된다.
gravity는 입력한 값에 대한 정렬을 의미하는 것으로 왼쪽(left), 오른쪽(right), 위쪽(top) 등으로 지정할 수 있다 [라인 16].
Button의 id, 너비, 높이는 EditText나 Layout과 동일한 개념이고,
버튼에 표시할 텍스트를 나타내는 text 속성과 가중치(layout_weight) 속성 등이 있다.
현재 Button이 4개 생성되었고, 버튼의 너비는 컨텐츠 길이만큼(wrap_content) 생성하도록 지정하였다.
이경우 화면이 Button의 너비 합보다 크기 때문에 우측에 여백이 생기게 된다.
이 여백을 각 Button의 가중치만큼 나누어 가지도록 하는 속성이 layout_weight이다 [라인 27,~].
모두 1씩 지정하였으니 같은 비율로 커지게 된다.
디자인을 마쳤으니, MainActivity.java (/src/main/java/com/gujc/calculatorsample/)을 열어서 다음과 같이 작성한다.
전체코드
먼저 사용할 위젯을 변수로 지정하여 Java에서 제어한다.
EditText는 TextView 클래스로 선언한 editText (인스턴스)변수로 제어한다 [라인2].
이 변수와 EditText 위젯을 연결하기 위해 findViewById 함수를 사용한다 [라인10].
위젯 생성시 지정한 id를 이용하여 해당 위젯을 찾아서 변수에 할당하고,
이 클래스 변수를 이용하여 값을 가지고 오거나 지정한다.
15개의 버튼 중 2개의 버튼만 코드로 작성했다 (나머지는 각자).
button0은 클래스내에서 전역 변수로 [라인 3],
button1은 onCreate 함수(이벤트)내에서 사용하는 지역 변수로 선언하였다 [라인20].
개념을 위해 이렇게 한 것으로 용도에 따라 전역으로 사용할 것인지, 지역으로 사용할 것인지 결정하면 된다.
setOnClickListener를 이용하여 각 버튼을 클릭했을 때 [라인 14, 22],
editText에 버튼의 값이 찍히도록 작성하였다.
클릭된 버튼의 값을 얻기(getText) 위해 클릭된 버튼을 알아야 한다.
여기서는 button0, button1을 각각 다르게 두가지 방식으로 작성했다.
button0은 클릭하면 button0의 값을 가지고 오도록 했고 [라인 16],
button1은 클릭 이벤트의 파라미터를 이용하였다 [라인 24, 25].
클릭 이벤트의 파라미터는 클릭된 개체(버튼)를 넘겨 주는데, Button 클래스가 아닌 클래스들의 부모(View)로 넘겨준다.
따라서 이것을 버튼으로 형변환 한 뒤에 사용하면 된다 [라인 24].
button0 코딩보다 button1 코딩이 더 깔끔하고 방어가 잘 된 코딩인데
그 이유는 나머지 13개의 버튼에 대하여 클릭 이벤트를 작성해 보면 알 수 있다.
버튼을 클릭하면 클릭된 버튼의 값만 출력된다.
앞서 클릭된 모든 버튼의 값이 이어서 출력되도록 작성해 보길 바란다.
모든 버튼에 클릭 이벤트를 작성하면,
두 번째 방식아 코드 양은 한 줄 더 많지만, 복사/붙여넣기 하고 수정하는 글자 수가 적다는 것을 알 수 있다.
이외에 더 많은 장점이 있다.
두 번째 예제로 이 장점을 정리하고, 이벤트 코드 양을 줄이는 방법을 정리한다.
전체 코드
앞서의 예제와 다르게 이상의 코드는 위젯을 찾고 이벤트 리스너를 등록하는 코드에서 button0, button1 등의 변수를 사용하지 않았다 [라인 2-16].
클래스 변수로 뭔가를 할 경우에는 사용하지만, 그렇지 않다면 생략하고 사용해도 된다.
첫 예제와 다른 것은 이벤트 클래스를 생성해서 사용하는 것이다.
첫 예제는 다음 코드처럼 OnClickListener 클래스를 생성(new)해서 변수에 담지 않고 버튼의 이벤트 리스너로 지정했다.
button0.setOnClickListener(new View.OnClickListener() {
이번 예제는 생성해서 mClickListener 변수에 담아서 사용한다 [라인 19].
Button.OnClickListener mClickListener = new View.OnClickListener() {
그리고 이 mClickListener변수를 모든 버튼에 지정했다.
즉, 15개의 버튼이 하나의 이벤트 리스너로 처리 되는 것이다.
OnClickListener 이벤트에서는 사용자가 클릭한 버튼을 파라미터(View)로 제공한다 [라인 20].
클릭한 버튼(view)의 고유번호(id)를 이용하여(getId()) [라인 21],
클릭한 버튼이 Button0이면 입력상자(editText)에 0을 넣고,
클릭한 버튼이 Button1이면 입력상자(editText)에 1… 등을 넣어 준다.
코드를 2개의 버튼에 대해서만 작성했지만, 15개 버튼에 대해서 모두 작성해야 한다.
이렇게 클릭된 버튼이 무엇인지 파악해서 필요한 행동을 하게 작성하는데 switch 문(다수의 if)이 사용되었다 [라인 22, 25].
이러한 코드는 일반적인 코드 작성법이지만 15개의 버튼에 대해서 작성한다면 아주 많은 코드를 작성해야 한다.
이 부분에 정리는 뒤에 하고 여기서는 넘어간다.
여기서 id (getId())는 고유한 숫자 값이다 [라인 21].
앞서 xml에서 지정한 id는 문자열이지만,
이 값을 안드로이드가 숫자로 변환해서 가지고 있고, 개발자가 지정한 문자는 일종의 상수처럼 선언되어 사용된다.
그래서 id를 사용할 때 “button0”처럼 사용하지 않고, R.id.button0으로 사용한다 [라인 22, 25].
이 예제 코드는
15개의 findViewById() 함수와
15개의 case 문 (실제로는 if문)이 사용되었다.
더욱이 이 예제는 버튼을 클릭하면 해당 값만 출력되지만 다음 코드와 같이 누적되게 작성하면 더 복잡하게 보인다.
editText.setText(editText.getText().toString() + "0");
첫 예제에서 클릭된 변수를 인식하는 2번째 방식을 이용하여 [라인 24],
복잡한 코드를 다음과 같이 줄여서 구현할 수 있다.
Button.OnClickListener mClickListener = new View.OnClickListener() {
public void onClick(View view) {
Button button= (Button) view;
editText.setText(editText.getText().toString() + button.getText().toString());
}
};
개념은 아주 간단하다.
클릭된 버튼이 무엇인지 알아야 했던 이유는
해당 버튼이 (view.getId())
무엇인지 (button0, button1, button2...) 알아서 해당 값(0, 1, 2 ...)을 지정하기 위한 것이었다.
1번 버튼이 클릭되면 값 1을 지정하기 위한 것이다.
이 1 값은 버튼의 텍스트(text)로 지정되어 있으니,
버튼이 클릭되면 해당 버튼의 값(getText())을 editText의 값에 넣어준다 (setText()).
세 번째 예제로, 15번이 사용된 findViewById() 함수를 줄여본다
두 번째 예제의 코드를 살펴보면,
버튼의 id가 button0, button1, button2, button? … 으로 숫자만 바뀌는 것을 알 수 있다.
따라서, 반복문을 이용하여 0부터 14까지 15번을 반복하고,
해당 id를 문자열로 생성해서 가지고 올 수 있으면, 다음과 같이 몇 줄로 구현할 수 있다.
for(int i=0; i<15; i++) {
String buttonID = "button" + i;
int resID = getResources().getIdentifier(buttonID, "id", getPackageName());
findViewById(resID).setOnClickListener(mClickListener);
}
전체 코드
"button" 문자열과 변수 i의 값을 이용하여, 15 버튼의 id를 문자열로 생성한다 (buttonID).
앞서서 몇 번 정리했지만,
안드로이드는 xml에서 문자열로 지정한 id를 고유 번호를 부여해서 따로 관리하기 때문에 변수buttonID의 값으로 찾을 수 없다.
따라서 리소스 정보에 접근할 수 있는 getResources()를 이용하여, 지정된 문자열에 맞는 id 값을 받고 (resID),
이 id를 findViewById()함수로 찾아서 구현한다.
안드로이드(Android) 프로그래밍은 간단한걸 구현해도 제법 많은 코드가 필요하다.
그래서 Kotlin 등이 각광 받을 테지만
이상의 방법과 같이 작성하면
Android를 사용하든 Kotlin을 사용하든, 쉽고 가독성 좋은 코드를 작성할 수 있을 것이다.
다음으로 실제 계산하는 기능을 구현한다.
부록
지금까지 Java(Android) 사용법과 코드를 줄이는 방법을 정리하였다.
이번에는 화면(XML)에서 코드를 줄이는 방법을 간단하게 정리한다.
다음의 activity_main.xml 파일을 자세히 보면,
모든 위젯은 너비(layout_width)와 높이(layout_height)가 있고,
Layout은 orientation가,
Button은 layout_weight가 동일하게 (“=” 버튼 예외) 사용된 것을 알 수 있다.
~~ 생략 ~~
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="7"
android:textSize="14sp" />
<Button
android:id="@+id/button8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="8" />
~~ 생략 ~~
전체코드
이렇게 디자인과 관련된 코드는 웹 개발처럼 스타일(Style)로 미리 지정해서 사용하면 쉽게 사용할 수 있다.
app/src/main/res/values/styles.xml 파일을 열어서 다음과 같이 작성한다.
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="layoutStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:orientation">horizontal</item>
</style>
<style name="buttonStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
</style>
</resources>
전체코드
layoutStyle은 LinearLayout에 공통으로 사용할 스타일이고
buttonStyle은 Button에 공통으로 사용할 스타일이다.
공통으로 사용할 스타일을 styles.xml 파일에 미리 등록해서 사용한다.
그리고, 다음과 같이 activity_main.xml 파일을 수정한다.
~~ 생략 ~~
<LinearLayout style="@style/layoutStyle">
<Button
android:id="@+id/button7"
android:text="7"
android:onClick="mClickListener"
style="@style/buttonStyle"/>
<Button
android:id="@+id/button8"
android:text="8"
android:onClick="mClickListener"
style="@style/buttonStyle"/>
<Button
android:id="@+id/button9"
android:text="9"
android:onClick="mClickListener"
style="@style/buttonStyle"/>
~~ 생략 ~~
전체코드
기존의 반복 코드를 지우고,
style 속성에 layoutStyle, buttonStyle을 지정해서 사용한다.
HTML의 CSS 클래스처럼 사용하는 것이다.
activity_main.xml 파일을 보면,
모든 버튼에 다음 코드가 추가 된 것을 볼 수 있다.
android:onClick="mClickListener"
이벤트 리스너를 Java에서 지정한 것이 아니고,
HTML처럼 디자인에서 지정하였다.
버튼을 클릭하면(onClick) mClickListener함수를 호출한다.
MainActivity.java 파일을 다음과 같이 수정해야 한다.
public void mClickListener (View view) {
Button button= (Button) view;
editText.setText(editText.getText().toString() + button.getText().toString());
};
전체코드
기존에는 mClickListener를 OnClickListener 클래스의 변수로 선언해서 사용했는데,
이 클래스 변수를 디자인(xml)에서는 알 수가 없기 때문에
전역(public) 함수로 선연해서 디자인에서 호출할 수 있도록 해준다.