안드로이드 수업에서는 안드로이드 환경에서 프로그램을 짜는 과정을 배우게 됩니다. 안드로이드 환경은 크게 모바일, 태블릿, 웨어로 구분됩니다.
안드로이드 프로젝트에서 ‘프로젝트’라는 단어를 사용하는 이유는, 여러 종류의 파일 및 프로그램, 심지어는 이미지와 같은 미디어 파일들이 묶여 하나의 앱을 구성하기 때문입니다.
안드로이드 프로젝트는 사용자가 지정한 workspace 내에 폴더/디렉토리로 생성되며, 다음의 핵심 파일을 포함하고 있습니다.
기본적으로 xml 파일이 사용자 인터페이스 역할을 하면서 이벤트에 따라 java 파일과 상호작용을 합니다. java 파일은 이벤트 요청에 따라 동적인 작업을 수행합니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">HelloWorld</string>
<string name="hello_world">Hello World!</string>
<string name="action_settings">Settings</string>
</resources>
aapt:Android Asset Packaging Tool
가 수행MainActivity.java
는 사용자와 상호작용하여 새로운 액티비티를 생성하거나 요구사항을 만족시키는 작업을 수행package com.example.myapplication;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
* XML 레이아웃의 장점
레이아웃 재활용(구조와 속성을 함축적으로 기술) 언어 이식성 소스, 화면단 간 분업에 용이 컴파일의 영역이므로 런타임 성능 이슈가 없음 XML 컴파일의 결과는 이진 포맷으로 변환되므로 용량상 낭비 없음
activity_main.xml 이 화면의 레이아웃이라면, 실제 화면을 구성하고 나타내는 것이 View 입니다. (쟁반 위의 그릇 비유)
크고 작은 각각의 레이아웃을 액티비티라고도 부르는데 이것이 화면을 구성하는 기본 단위입니다. 액티비티는 직접적으로 보이지 않으며, 이것 안의 View 가 사용자에게 보여지는 실체입니다.
안드로이드 앱 > 여러 액티비티 > 액티비티를 구성하는 View
ViewGroup 을 쟁반 레이아웃, Widget 을 반찬 담는 그릇이라 생각하면 편합니다. ViewGroup은 모아놓은 View들을 배치하는 역할을, 각각의 View는 상호작용을 합니다.
때로 ViewGroup이면서 실제 보이는 Widget처럼 사용되는 ListView 와 같은 클래스도 존재합니다. 이것은 여러 View가 모여 구현된 하나의 복합적인 위젯이므로, 그만큼 많은 기능을 수행합니다.
본 수업에 들어가기에 앞서, 안드로이드 앱의 전체 실행 과정을 살펴보겠습니다. 소스코드가 컴파일되어 바이트 실행파일로 변환, 가상머신에서 실행되는 과정 자체는 JAVA의 그것과 유사합니다.
.java
형식의 소스파일 작성, 컴파일.class
형식의 바이트 코드를 .dex
형식의 안드로이드 가상머신용 실행 파일로 변환, .apk
로 패키징.apk
내의 .dex
중심으로 어플리케이션 실행
안드로이드 프로젝트가 컴파일 및 패키징되어 Signing 이후 배포되는 과정입니다.
Signing 은 debug key 를 .apk
파일에 서명하는 방식으로 진행되며, 이는 타인에 의한 위변조 방지 및 개발자 식별 등에 쓰입니다.
배포 시에는 Google Play 에 서명한 key 값을 가지고 올라갑니다.
이제 본격적으로 View에 대해서 살펴봅시다.
앞서 쟁반 레이아웃 개념인 ViewGroup과 그릇 개념인 Widget(View)에 대해 설명했습니다. 또한 레이아웃을 액티비티라고도 부른다고도 했었습니다.
둘의 역할은 다르지만, View 부모 클래스로부터 상속되었다는 공통점이 있습니다. 따라서 View의 전반적인 속성에 대해 살펴보겠습니다.
<Button
...
android:id="@+id/button" // 'button' id 부여
android:layout_below="@id/textView" // id가 'textView'인 위젯 아래에 'button' 배치
... />
R.java
에 정의하거나 R.java
로부터 참조한다는 의미R.java
이 파일은 주소록이라고 설명했습니다. 따라서 id와 같은 여러 리소스의 이름이 컴파일되어 저장됩니다.
이것들은 View 부모 클래스 자체의 속성이므로 ViewGroup 혹은 Widget 모두에서 지정 가능합니다.
<Button
...
android:background="@drawable/ic_launcher" // 'drawble' 내 'ic_launcher' id 객체를 배경으로 지정
... />
background 속성은 배경뿐 아니라 색상도 지정할 수 있습니다.
’#’ 다음에 16진수로 색상 강도를 조정합니다. (ex. android:background="#ffff1325"
)
visibility 속성은 런타임 중 얼마든지 변경 가능합니다.
<TextView
...
android:id="@+id/textView1"
android:focusable="false" // Set focusable to false
... />
그릇에 해당하는 Widget 중, TextView 와 ImageView 를 살펴보겠습니다.
-> TextView -> Button
-> EditText
문자열을 strings.xml 에 id 를 지정하여 정의해 놓으면 재사용성과 다국어 스위칭에 유리 (변수화!)
<!-- 직접 대입 방식 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text!"
android:textSize="30dp" />
<!--
변수 처리 방식.
R.java 주소록의 "hello_world" 아이디값을 탐색해
strings.xml 에 등록된 문자열 리소스를 가져옴.
-> 재사용 및 다국어 처리에 유리
-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:textSize="30dp" />
그럼 TextView 연습을 위한 안드로이드 프로젝트를 생성해 보겠습니다.
자동으로 생성되는 위 파일들 중 직접적으로 다룰 것은 MainActivity.java, activity_main.xml, strings.xml 세 개입니다.
MainActivity.java 의 MainActivity 클래스는 기본적인 화면 레이아웃을 생성합니다.
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState):
setContentView(R.layout.activity_main);
}
}
다음은 activity_main.xml 레이아웃, 위젯 수정 예제입니다. LinearLayout 을 추가하고 그 안에 TextView 위젯을 여러 개 넣습니다.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textColor="#ff0000"
android:textSize="18pt"
android:textStyle="italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:text="@string/tv2_name"
android:textColor="#ff00ff"
android:background="#3355bb"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:text="@string/tv3_name"
android:textColor="#707070"
android:textSize="3mm"
android:typeface="serif"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
LinearLayout 으로 내부 위젯이 순차적으로 나타나되, android:orientation=”vertical” 속성으로 인해 세로로 보입니다.
첫 번째 위젯은 text 속성에 직접 문자열을 대입하고, 나머지는 string.xml 에 변수처리 되었습니다. 그리고 나머지 속성값들도 확인해볼 수 있습니다.
TextView 가 문자열을 담는 그릇이라면 ImageView 는 그림을 담는 그릇(위젯)입니다.
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/banana"
android:tint="#70ff00ff" />
string 변수처리와 같이 “@drawable/ID” 와 같은 방식으로 이미지를 지정해줍니다. 마찬가지로 R.java 에 해당 이미지 경로가 컴파일되어 있기 때문에 id 참조가 가능합니다.
레이아웃에 ImageView 위젯을 생성해 봅시다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="30dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:text="@string/sana"
android:textColor="#707070"
android:textSize="36sp"
android:typeface="serif"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="SmallSp" />
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/sana"
android:adjustViewBounds="true"
android:maxHeight="180dp"
android:contentDescription="@string/sana" />
</LinearLayout>
maxWidth, maxHeight 속성으로 가로 세로 최대크기를 지정할 수 있으며, adjustViewBounds 로 이미지 비율을 유지할 수 있습니다. tint 속성은 이미지의 색상을 지정합니다. 무엇보다 에러가 발생하지 않도록 파일명 작성에 유의합시다.
본 강의에서는 3강에서 다루던 ImageView 를 마무리하고 Button, EditText 와 함께 레이아웃에 대해 살펴보겠습니다.
<ImageView
android:id="@+id/imageid"
android:scaleType="centerCrop"
android:padding="20dp"
android:cropToPadding="true" />
버튼과 텍스트 입력 창입니다. 이것들은 사용자의 입력을 받아들이는 대표적인 방법입니다.
이 두 클래스는 TextView 의 파생 클래스임을 기억합시다. (text 등의 속성 사용가능)
activity_main.xml 의 Design 탭에서 드래그 앤 드롭으로 각 위젯을 생성할 수 있지만, 직접 xml 태그를 타이핑하는 연습을 해서 확실히 이해하도록 합시다.
다음은 EditText, Button 각각 하나씩 생성한 예제입니다.
<EditText
android:id="@+id/et_default"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/default_placeholder"
android:autofillHints="@string/default_placeholder"
android:inputType="text"
android:layout_gravity="center" />
<Button
android:id="@+id/btn_default"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_confirm"
android:layout_gravity="center" />
이렇게 생성한 위젯들은 재사용을 위해 id 변수처리를 해야 합니다. (JAVA 코드에서 활용) 버튼을 클릭하면 Click Event 가 발생하는데, 이것의 처리는 JAVA 코드 내에서 이루어집니다.
...
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Add onclick event to button
Button btn = (Button) findViewById(R.id.btn_default);
btn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
EditText edit = (EditText) findViewById(R.id.et_default);
String str = edit.getText().toString();
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
}
});
}
}
앱 실행 시 텍스트 입력 후 CONFIRM 클릭하면 해당 팝업창이 나타납니다.
이제 레이아웃을 의미하는 ViewGroup 의 속성을 살펴보겠습니다.
이런 사이즈 값들은 절대값이 아니라 상대값을 활용해 여러 기기에 유동적으로 적용되도록 합니다.
padding 은 뷰(혹은 레이아웃)의 내부 여백을 채우고, layout_margin 은 속성이 지정된 뷰의 바깥 여백으로 지정되므로 그 특성을 잘 고려합시다.
RelativeLayout 과 FrameLayout 은 안드로이드 UI 에서 가장 많이 사용되는 레이아웃입니다. 특히 FrameLayout 은 고유한 특성으로 인해 꼭 필요한 경우가 존재합니다.
RelativeLayout 은 말 그대로 쟁반(ViewGroup)과 그릇(View, Widget)을 상대적으로 배치하는 것입니다. 따라서 항상 기준 요소가 필요하고, 그 기준에 따라 상대적인 위치가 정해지므로, XML 요소의 순서와 실제 화면 배치 순서는 무관합니다.
기준 요소는 일반 위젯이 될 수 도 있고, 부모 뷰그룹이 될 수도 있습니다. 이때, 기준 요소 재사용을 위해 id 지정은 필수입니다.
먼저 위젯 간의 상대 속성을 알아봅시다.
먼저 기준 요소를 지정합니다.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<!-- Target Element -->
<TextView
android:id="@+id/target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/target_text"
android:layout_centerInParent="true"
android:textSize="30sp"
android:background="#00D8FF" />
</RelativeLayout>
기준 요소에 상대적으로 각 요소들을 배치해 봅니다.
...
<TextView
android:id="@+id/top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/top_text"
android:layout_above="@id/target"
android:textSize="12sp"
android:background="#1DDB16" />
<TextView
android:id="@+id/right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/right_text"
android:layout_toRightOf="@id/target"
android:textSize="12sp"
android:background="#1DDB16" />
<TextView
android:id="@+id/bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bottom_text"
android:layout_below="@id/target"
android:textSize="12sp"
android:background="#1DDB16" />
...
...
ID “target” 인 중앙 요소에 상대적인 설정들입니다. 세 개만 작성했는데, 실습을 통해 완성해보면 좋습니다.
이번에는 부모 요소에 상대적인 배치 속성들입니다.
layout_alignBaseline: ~와 베이스라인을 맞춤
부모 요소를 기준으로 위 속성들을 활용해 봅시다.
RelativeLayout 에서 상대적으로 얽혀 있는 여러 요소들의 참조관계는 컴파일 시 한번에 읽도록 되어 있습니다. 이는 빠른 배치가 가능하게 하며, 요소 간 종속적 관계에서 기준이 되는 요소가 XML 상에서 먼저 정의되어 있어야 참조할 수 있습니다.
계속 설명했듯 RelativeLayout 은 요소 간 상대적이기 때문에 참조관계를 많이 정의하면 할수록 헷갈리기 쉽습니다.
일반적으로 부모와 관계되는 기준 요소를 가장 먼저 정의하고, 부모의 변에 달라붙는 위젯, 나머지 상대 요소 순으로 정의하면 의도한 대로 위치하게 됩니다.
AbsoluteLayout 은 RelativeLayout 와는 반대로 절대 좌표에 차일드 뷰를 위치시킵니다. 레이아웃, 뷰그룹, 위젯 간의 관계나 순서에 관계없이 지정한 고정 좌표에만 배치되므로, 자유도가 높고 위치가 상대적으로 변화될 걱정이 없습니다.
그러나 이 레이아웃이 실제적으로 사용되지 않는 이유는, 절대 좌표가 다양한 모바일 환경에 적절히 적용될 수 없기 때문입니다.
x, y 축 고정 좌표에 요소를 위치시키면 화면이 큰 모바일에서는 여백이 많이 발생할 것이고, 반대로 작은 화면의 모바일에서는 잘려서 나올 수 있습니다.
세로 화면, 가로 화면에 따라서도 보여지는 것이 달라지게 됩니다. (공식 문서에서도 사용하지 말라고 합니다.)
다음은 JAVA 와 함께 유용하게 사용될 수 있는 FrameLayout 을 살펴보겠습니다.
FrameLayout 에서의 모든 요소들은 순차적으로 중첩됩니다. 이 말은, 나중에 중첩된 요소가 가장 앞단에 보이게 된다는 의미입니다.
이것은 빈번하게 사용되지는 않지만, 겹겹이 쌓아나가는 특성을 잘 활용하여 필요한 곳에 적용할 수 있습니다.
특정 조건에 따라 차일드 뷰 하나만 선택적으로 나타나게 할 수 있습니다.
Gravity 는 위치를 지정하므로, foregroundGravity 속성은 뚜껑 이미지의 위치를 지정합니다. 기본값인 fill 은 이미지가 프레임 전체를 커버하도록 합니다.
FrameLayout 은 보이거나 안보이거나, 마지막 요소가 나타나거나… 등등의 특성으로 인해 JAVA 와 동적인 협업에 사용됩니다.
<!-- activity_main.xml -->
...
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click, Button" />
...
ImageView 의 이미지는 기본 이미지를 활용했고, visibility 속성이 별도로 지정되어 있지 않으므로 default “visible” 입니다.
/* MainActivity.java */
...
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
ImageView img = (ImageView) findViewById(R.id.img);
if (img.getVisibility() == View.VISIBLE) {
img.setVisibility(View.GONE);
} else {
img.setVisibility(View.VISIBLE);
}
}
});
}
}
...
본 강의에서는 TableLayout, 레이아웃의 중첩, 앱 실행 중에 속성을 바꾸는 것을 학습합니다.
TableLayout 은 바둑판이라고 생각하면 됩니다.
화면에 무언가를 그리고 출력하기 위한 Canvas와 Toast에 대해 살펴봅니다.
카톡이 왔을 때 잠깐 화면에 나타났다 사라지는 메시지를 생각하면 됩니다.
CustomView는 Canvas에 여러 가지를 그려서 표현하는 새로운 뷰입니다. 이 뷰는 사용자에 의해 상속되어 확장됩니다.
Paint 클래스 객체는 보여지는 요소의 속성이나 스타일을 포함합니다.