안드로이드 뷰, 액티비티, 플레그먼트를 이용한 화면 구성법


데스크탑 수준이 아닌 성능과 작은 화면에서 빠르고 최적화된 화면의 구성은 스마트폰에서 필수적이다. 내부적으로 구현되는 알고리즘이 아무리 우수하다고 할지라도 사용자가 보고 있는 화면과의 상호작용의 불편함이나 느린경우 사용자는 더 이상 애플리케이션을 사용하지 않을 가능성이 높다. 사용자는 간단하고 빠르게 작동하는 애플리케이션을 원하지 내부적인 알고리즘을 보고 애플리케이션을 사용하지 않는다. 이런 측면에서 안드로이드에서 화면을 구성하는 핵심 요소인 액티비티, 플레그먼트, 레이아웃, 뷰에 대해 내부적으로 어떻게 작동 되는지에 대해 살펴보고 빠른 반응을 하기 위한 최적화 기법에 대해서 소개한다.

또한 애플리케이션 하나로 스마트폰 뿐만 아니라 테블릿기기에서 화면구성을 최적화하는 방법에 대해서 살펴본다.

2.1 액티비티

액티비티는 안드로이드 애플리케이션 화면을 구성하는 핵심 요소중 하나이다. 하나의 화면을 구성하기 위해서는 반드시 하나의 액티비티가 존재해야하며 다음에 설명할 플레그먼트와, 레이아웃, 뷰들이 액티비티 내에 구성된다. 액티비티는 폰의 상태와 사용자의 액션에 따라 발생하는 생명주기를 가지고 있다. 예를 들어 사용자가 애플리케이션을 실행 하여 액티비티가 최초로 생성 되거나, 백키를 눌러 액티비티를 종료했더나, 홈키는 눌러 화면을 잠시 빠져나가는 등에 대한 상태를 가지고 있다.

<그림 2.1.1> 액티비티의  생명주기

 

onCreate()
액티비티가 최초생성 되거나, Pause 상태일때 메모리 부족으로 인해 액티비티가 제거되어 새롭게 생성해야 되는 경우 호출 된다. 보통 여기서 setContentView()를 통해 뷰가 생성되며, 데이터를 처리하는 작업이 진행된다. SaveInstance에 아무런 정보가 없다면 최초로 생성되는 것이며, 값이 있을 경우 메모리부족으로 인해 호출 되었다는 것을 알 수 있다.

onStart()
onCreate()후 호출되거나, 다른화면으로 빠져나가 대기 상태에서 다시 복귀될때 호출 된다.

onResume()
onStart()후 호출 되거나, 새로운 액티비티의 시작으로 잠시 정지된 상태에서 다시 화면에 표시될때 호출 된다. 여기서 사용자와 상호작용이 가능하다.

onPause()
새로운 액티비티가 실행되는 경우 호출되며 사용자와 상호작용이 중단된다. onPause()상태일때 메모리가 부족하게 되는 경우 메모리 확보를 위해 강제로 종료 될 수 있다.

onStop()
홈키를 누르는등 액티비티가 사용자에게 전혀 보이지 않는 상태이며, onPause()상태와 마찬가지로 메모리가 부족하게 되는 경우 강제 종료 될 수 있다.

onRestart()
onStop()상태에서 활성상태로 복귀할때 호출된다. onRestart()가 호출이 된 것은 onStop()중에 메모리가 부족하여 강제로 종료되지 않았다는 것이며, 그대로 복귀시킨다.

onDestory()
명시적으로 액티비티를 finish()로 종료하거나, 메모리 부족등으로 시스템에서 강제로 종료시키는 경우에 발생한다.

 

강제종료되는 액티비티

액티비티의 생명주기에서 onPause()와 onStop()일때 메모리 부족의 경우 액티비티가 강제로 종료된다. 이렇게 강제 종료가 되면 이전에 수행 했던 작업이 저장되지 않아 처음부터 새롭게 작업해야 되는 경우나 특정 정보를 이용해서 작업 해야 하는데 정보가 없어져 작업을 수행 할 수 없는 문제가 발생된다. 이런 문제점을 해결 하기 위해 저장할 수 있도록 저장과 복구를 할 수 있는 콜백 메서드를 제공한다.

onSaveInstanceState(Bundle)
onPause()전에 호출되며 Bundle 파라미터를 통해서 저장해야 될 정보를 저장 할 수 있다.

onRestoreInstanceState(Bundle)
액티비티가 메모리 부족으로 종료되어 복원을 위해 호출 되며, onStart()후에 호출된다. onCreate(Bundle)의 파라미터인 Bundle을 이용해서 복구를 할 수도있다.

onConfigurationChanged(Configuration)
AndroidManifest.xml에서 configChanges설정을 통해 액티비티가 새롭게 생성되지 않는 대신 호출 된다. 화면방향, 화면밀도, 화면사이즈등의 정보가 담긴 Configuration 파라미터와 함께 호출 된다.

액티비티 화면변화

일반적으로 화면회전 하게 되면 액티비티는 제거되고 새롭게 생성된다. 가로 화면 전용 레이아웃 리소스가 있다면  전혀다른 레이아웃으로 바꾸기 위해서 새롭게 생성되는 것은 정상이다. 가로 전용 레이아웃이 없고 단순히 뷰가 늘어 나게  가변 될 수 있도록 작업이 되어 있는 경우라면 새롭게 생성하는 것은 낭비이다. 이렇게 화면의 변화가 생길때 액티비티가 새롭게 생성 되는 경우를 피하고 싶은 경우 AndroidManifest.xml에서 configChanges를 설정하면 된다. 아래 코드는 화면의 방향이 바뀌었을때를 처리 하도록하였다.  안드로이드 3.2이후 orientation은 screenSize와 같이 사용해야 작동한다.

<코드 2.1.1> targetSdkVersion 안드로이드 3.2(API 13) 이전

<activity
  android:name=”.MainActivity”
  android:configChanges=”orientation” />

<코드 2.1.1> targetSdkVersion 안드로이드 3.2(API 13) 이후

<activity
  android:name=”.MainActivity”
  android:configChanges=”orientation|screenSize” />

 

2.2 뷰와 레이아웃

뷰는 사용자 인터페이스를 구성하기위한 기본요소이다. 화면에 사각영역으로 자리를 잡고 드로잉과 터치이벤트에 대한 처리를 당담하게 된다. 이 뷰는 1.1에서 설명한 액티비티내에서 구성될 수 있으며 앱 위젯이나 윈도우등에도 구성 될 수있다. 안드로이드에서는 기본적으로 뷰를 상속하여 만든 TextView, ImageView, Button, ProgressBar, ListView, GridView 등 훌륭한 뷰들이 존재하며 직접 상속해서 개발 할 수도 있다. 하나하나의 뷰를 화면에 배치를 좀 더 효율적으로 관리 하기위한  ViewGroup이 존재 한다. ViewGroup을 상속해서 만든 FrameLayout, LinerLayout, RelativeLayout, GridLayout 등을 레이아웃이라하며 안드로이드에서 기본적으로 제공되며, 직접 상속해서 개발 할 수 있다.  안드로이드에서 제공하는 기본적인 뷰와 레이아웃은 X장에서 자세히 살펴 보겠다.

<그림 2.2.1> 안드로이드의 기본 화면 구성

안드로이드이 기본적인 화면 구성은 그림 2.2.1과 같이 액티비티내에 레이아웃이 존재하며, 그 하위로 또 다른 레이아웃이나 뷰를 배치하게 된다.

 

레이아웃 인플레이터 시스템

안드로이드의 뷰와 레이아웃은 하나의 객체로 이루어져 있다. 그렇기 때문에 액티비티내 뷰또는 레이아웃을 생성 하기위해서는 코드를 통해서 객체화해야 한다. 만일 액티비티에 복잡한 레이아웃과 뷰로 구성이 되는 경우 UI를 구성하는 객체와 데이터를 구성하는 등의 다양한 객체가 섞여 구조적으로 문제가 생기게 된다. 안드로이드에서는 이런 문제점을 해결하기 위해 레이아웃의 정보를 XML 문서로 정의하여 객체를 생성하고 속성을 변경할 수 있는 시스템을 도입하였다.

LayoutInflater는 XML문서로 정의된 레이아웃 정보를 뷰객체로 만들어준다. 코드 2.2.1에서 LayoutInflater를 이용해서 inflate() 메서드로 코드 2.2.2의 레이아웃의 정보를 가진 XML문서를 뷰로 전환 해준다. 이렇게 레이아웃 XML문서를 뷰로 전환 하는 것을 안드로이드에서는 인플레이트한다고 칭한다.

<코드 2.2.1>

LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.main, null);

<코드 2.2.2> main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >


   <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="hello world"
    android:textSize="12sp" />

</RelativeLayout>

이렇게 인플레이터 시스템이 없을 경우 개발자는 직접 뷰 또는 레이아웃을 직접 객체화 시키는 코드를 작성해야 하며, 이 과정에 레이아웃과 뷰들간의 관계에 대해서 코드를 통해 눈으로 판단하기가 매우 힘들다. CODE 2.2.3은 CODE 2.2.2의 XML 레이아웃을 동일하게 작동하도록 코드를 통해 작성한 것이다. 간단한 예제를 통해서 보듯 XML이 훨씬 직관적이고 구조적인 것을 볼 수 있다.

<코드 2.2.3>

RelativeLayout layout = new RelativeLayout(getApplicationContext());
layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
 
TextView textView = new TextView(getApplicationContext());
textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
textView.setText("hello world");
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);

layout.addView(textView);

안드로이드에서는 XML 레이아웃을 바로 그래픽으로 보여주는 것 뿐만 아니라 배치를 하거나 속성을 바꿀 수 있는 툴 을 제공하여 좀 더 쉽고  빠른 레이아웃 작업을 할 수 있도록 도와준다.

<그림 2.2.1> 안드로이드 레이아웃 편집 툴

레이아웃 편집 툴은 각종 안드로이드에서 제공되는 기본 뷰를 쉽게 배치할 수도 있으며, 다양한 단말에서 어떻게 보여지는지에 대한 미리보기도 지원한다.

 

액티비티와 레이아웃 리소스

액티비티는 단순히 화면을 구성하는 기본단위이며 액티비티 내에 뷰를 배치하여 화면을 구성해야 한다. 구성 후 액티비티 내부에 존재하는 뷰 객체를 찾아서 필요한 데이터를 뷰를 통해 표현해야 한다.

액티비티에 뷰를 구성하기위해서는 setContentView() 메서드를 호출하면 되는데, 2가지의 인자가 존재한다. 하나는 뷰 또 다른 하나는 레이아웃 리소스 아이디이다. 레이아웃 리소스를 인자로 주게되면 내부적으로 2.2.1에서 LayoutInflater를 통해 뷰 객체를 만든 후 뷰를 인자로 다시 넘기게 된다. 즉, 코드 2.2.4에서 리소스아이디를 인자로 주게 되면 내부적으로 코드 2.2.5와 같이 LayoutInflater로 레이아웃 리소스 아이디로 뷰를 인플레이트하게 된다.

<코드 2.2.4>

setContentView(R.layout.main);

<코드 2.2.5>

LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View view = inflater.inflate(R.layout.main, null);

setContentView(view);

이렇게 액티비티에 뷰가 보여지게 되면 수많은 뷰 객체 중 필요한 객체만 찾아와 속성을 변경하는 등의 작업이 필요하게 된다. findViewById() 메서드를 통해 레이아웃에 선언한 아이디로 해당 객체를 불러올 수 있다. 코드 2.2.6에서 TextView에 아이디값을 주었다. 아이디 값은 “@+id/아이디명” 으로 선언하게된다. 이미 정의된 아이디를 선언하려면 “+”를 제외한 “@id/아이디명”을 값으로 주면 된다.

<코드 2.2.6> main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <TextView
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="hello world"
    android:textSize="12sp" />

</RelativeLayout>

<코드 2.2.4>

setContentView(R.layout.main);
TextView titleTextView = (TextView) findViewById(R.id.title);
titleTextView.setText(“Hi Android!”);

findViewById는 View를 리턴해주지만 내부적으로 TextView의 객체가 생성되어 있기 때문에 TextView로 캐스팅 하면된다. 이렇게 XML에서 선언한 TextView를 아이디를 통해 객체를 가져올 수 있으며, 속성을 변경 할 수 있다. 한 가지 주의할 점은 LayoutInflater는 XML문서를 위에서 아래로 읽기 때문에 레이아웃 내에 동일한 아이디를 가지게 되는 경우 XML에서 밑 부분에 선언된 아이디가 최종적인 뷰 객체가 된다.

 

2.3 플레그먼트

액티비티는 스마트폰을 기준으로 설계된 구조이기 때문에 하나의 하나의 화면을 구성을 할 수 있다. 즉, 한 화면에 액티비티 여러개를 구성하지 못한다. 스마트폰에서는 화면이 작기 때문에 하나의 화면에 액티비티 하나를 배치하는 것에 문제가 없었다. 하지만 태블릿기기가 등장하면서 한 화면에 여러개의 액티비티를 구성하여 화면을 좀 더 넓게 쓸 필요가 있었다. 레이아웃으로 화면을 좀 더 넓게 사용도 가능하지만, 액티비티에 종속적인 생명주기로 인해 관리가 매우 힘들다. 이런 문제점을 해결하기위해서 안드로이드 3.0 (허니콤) 부터 플레그먼트라는 개념을 도입하게 되었다. 액티비티를 조각화 하여 배치 할 수 있는 독립적인 부분이 있으며, 액티비티와 같은 생명주기를 가지고 있다. 그림 2.3.1에서 보는것과 같이 액티비티내에 다양한 플레그먼트를 배치하고 그 안에 레이아웃을 구성 했다.

<그림 2.3.1> 플레그먼트를 이용한 화면 구성

테블릿 기기의 경우 스마트폰과는 다르게 큰 공간을 가지고 있기 때문에 플레그먼트를 이용하게 된다면 공간을 효율적으로 사용가능 하다. 하나의 앱으로 다양한 스마트폰과 테블릿을 지원하기 위해서는 필수적인 요소이다.

플레그먼트는 그림 2.3.2에서 보듯이 액티비티에 비해 좀 더 명확성을 갖기위해 Created부분과 Destory부분이 세부적으로 나누어졌다. 하지만 크게 나누어 본다면 액비티비와 동일한 구조로 이루어 졌다.

 

<그림 2.3.2> 플레그먼트 생명주기와 액티비티와의 비교

onAttach()
플레그먼트가 액티비티 레이아웃에 포함되는 순간 호출되며 플레그먼트를 아직 생성되지 않았다.

onCreate()
플레그먼트가 생성되는 시점에 호출되며 화면에 보이지는 않는다.

onCreateView()
플레그먼트에 표시 할 레이아웃을 인플레이트 하여 뷰를 생성 할 수 있다. 플레그먼트가 최초로 생성되거나 메모리부족으로 인해 플레그먼트가 메모리상에서 제거되었다가 다시 생성되어야 하는 시점에 호출 된다.

onActivityCreated()
액티비티가 생성된 후에 호출되며 뷰가 만들어진 상태이기 때문에 뷰의 속성을 변경하는 등의 작업이 가능하다.

onStart()
액티비티와 동일하게 플레그먼트가 화면에 표시될때 호출된다. 아직은 사용자와 상호작용이 불가능 한 상태다.

onResume()
플레그먼트가 화면에 완전히 그렸으며, 사용자와 상호작용이 가능하다.

onPause()
플레그먼트가 화면에서 보이지 않는 상태로 액티비티와 동일하다. 액티비티에서는 새로운 액티비티가 뛰워 진것과 동일하게 플레그먼트도 백스택에 의해 추가된 경우에 호출된다.

onStop()
플레그먼트가 화면에서 보이지 않는 상태로 액티비티와 동일하다.

onDestoryView()
View 리소스를 해제 할 수 있도록 호출된다. 백스택을 사용한 경우 다시 돌아올때 onCreateView()가 호출된다.

onDestory()
플레그먼트 상태를 완전히 종료 할 수 있도록 호출 한다.

onDetach()
플레그먼트가 액티비티의 레이아웃에서 제거 될 때 호출된다.

onSaveInstanceState()
액티비티와 동일하게 플레그먼트가 메모리 부족으로 강제로 없어 지는 경우를 복원을 위해 Bundle에 정보를 저장 할 수 있다.




안드로이드 기본 레이아웃 사용법


ViewGroup을 상속하여 만든 다양한 레이아웃을 안드로이드에서는 기본제공한다. 기본으로 제공하는 레이아웃은 다양한 스마트폰과 테블릿에서 확장 가능한 애플리케이션을 만들 수 있도록 설계되었다. 이번 장에서는 여러 스마트폰 크기를 지원하기 위해 레이아웃과 뷰의 배치방법에 대해 소개하며, 기본으로 제공하는 레이아웃들의 특징을 살펴 보겠다.

안드로이드는 다양한 크기와 형태를 지원하기 위해서 레이아웃을 절대 픽셀 위치를 주는 것은 대단히 위험한 일이다. 항상 레이아웃의 크기는 변할 수 있다는 것을 잊어서는 안된다. 그렇기 때문에 레이아웃은 고정된 영역과 확장 가능한 영역을 정의해야 한다. 고정된 영역은 사용자가 터치를 하는 아이콘이 될 수 있으며 그 크기는 고정된다. 확장 가능한 영역은 고정된 영역과 반대로 공간을 채우기 위해 크기가 조정되는 부분이다.

<그림 3.0.1> 레이아웃의 고정된 영역과 확장가능한 영역 분리

예를 들어 액션바에 아이콘을 배치하는 경우 아이콘의 고정된 크기를 유지하기 위해 액션바의 높이는 고정된 높이를 가지게되며, 수평으로 전체 화면을 채우기 위해서는 넓이는 조정되어야 한다. 그림 3.0.1에서 보듯이 확장가능한 영역과 고정된 영역을 정의한 모습이다. 이렇게 레이아웃의 영역을 정의하면 그림 3.0.2에서 처럼 레이아웃의 크기가 변경 되어도 대응 할 수 있다.

<그림 3.0.2> 확장/축소 가능한 영역으로 인한 다양한 사이즈의 대응

이 처럼 다양한 사이즈에 대응하기 위해 고정된 영역과 확장 가능한 영역을 나누었다. 이렇게 화면을 구성하기 위해 기본적인 레이아웃을 안드로이드에서 제공하고 있다.


3.1 LinerLayout

일반적으로 안드로이드에서 가장 흔하게 쓰는 레이아웃 중 하나이다. 레이아웃 내부에 배치되는 뷰는 수평 또는 수직으로 나란하게 배치된다. 수평인지 수직인지 레이아웃의 방향을 설정하여 사용해야 하며, 내부에 배치되는 뷰들 간의 크기를 상대적으로 구성 가능 하다. 화면의 사이즈가 모두 다른 경우 각각의 화면에 최적화 하기 위해서 가장 필요한 레이아웃이다.

화면의 크기에 상관 없이 버튼을 화면의 비율에 맞게 각각 다른 크기로 3개를 나눈다고 생각 해보자. 이런 경우 고정된 높이를 지정하게 되면 화면의 크기가 더 커지거나 작아지는 경우 공간이 남거나 짤리게 된다. LinerLayout의 가중치를 이용하면 화면의 크기와 상관 없이 비율에 따라 내부에 배치되는 뷰의 크기가 비율에 맞게 크기가 동적으로 변한다.

코드 3.1.1은 내부에 배치될 layout_orientation속성의 값을 vertical로 주어 세로로 나란히 배치하게 하였다. 그리고 내부에 배치될 버튼의 layout_weight속성 값을 각각 1, 2, 3으로 주었다. 이렇게 가중치값을 통해 각각의 버튼들은 1:2:3의 크기로 높이가 정해진다.

그림 3.1.1에서 보듯 LinearLayout의 레이아웃의 높이는 layout_weight속성의 값에 따라 바뀌게되는 것을 볼 수있다. 화면의 크기에 상관없이 정해진 비율로 LinearLayout 내의 뷰들이 크기가 정해진다.

<그림 3.1.1> LinearLayout의 비율에 따른 높이 변화

layout_weight값을 이용하면 그림 3.0.2에서 설명한 확장/축소 가능한 영역을 지정할 수 있기 때문에 다양한 스크린 사이즈에 대응할 수 있다. 코드 3.1.2는 확장/축소가능한 영역인 텍스트를 감싸고 있는 LinearLayout의 layout_weight속성에 항상 꽉차도록 하기위해 1의 값을 주었다. 나머지 이미지와 버튼은 고정된 영역이기 때문에 별도로 layout_weight속성을 주지 않았다.

레이아웃을 실행 하게 되면 텍스트를 감싸고 있는 LinearLayout부분은 가로로 항상 꽉 차게 되며 왼쪽 아이콘과 오른쪽 버튼은 고정된 크기만큼 자리를 차지하게 된다. 그림 3.1.2에서 스크린의 가로와 세로 또는 화면의 스크린 사이즈에 관계 없이 다양한 화면에 대응할 수 있는 기본적인 구조를 가지는 것을 볼 수 있다.

<그림 3.1.2> LinearLayout의 가중치를 이용한 다양한 스크린 사이즈 대응

LinearLayout은 내부의 뷰를 가로 또는 세로로 배치하며, 가중치를 통해서 다양한 스크린사이즈에 대응 할 수 있는 안드로이드에서 가장 많이 쓰이며 반드시 알아야 할 레이아웃이다.


3.2. RelativeLayout

RelativeLayout도 LinearLayout과 마찬하기로 가장 흔하게 쓰이는 레이아웃이다. 내부의 뷰를 중첩된 형태로 구성이 가능하며 부모뷰 또는 같은 자식뷰들의 영역 대해 상대적인 위치를 지정 할 수 있다. 예를 들면 부모뷰를 기준으로 가운데 정렬을 하거나 하단/상단, 오른쪽/왼쪽으로 정렬 할 수 있다. 또한 자식뷰를 기준으로 왼쪽, 오른쪽, 상단, 하단으로 정렬 할 수 있다.

코드 3.2.1은 부모뷰를 기준으로 정렬하는 예제이다. 뷰모뷰인 RelativeLayout은 화면에 꽉 차도록 크기가 지정되었으며, 5개의 버튼은 각각의 정렬될 속성의 값을 주어 정렬 시켰다.

여기서 중요한 점은 화면 스크린의 크기에 상관없이 뷰모뷰를 기준으로 정렬 되기때문에 그림 3.2.1과 같이 정렬된다. RelativeLayout의 layout_alignParent속성을 이용하면 다양한 화면 스크린에 대해 쉬운 방법으로 대응 가능하다.

<그림 3.2.1> RelativeLayout의 뷰모뷰 기준의 정렬 방식

뷰모뷰를 기준으로 정렬되는 방식 뿐만 아니라 자식뷰간의 관계를 통해서 정렬되는 방식도 있다. 자식간의 뷰를 구분하기 위해서 자식의 아이디 값을 통해서 정렬된다. 코드 3.2.2는 버튼 하나를 부모 뷰를 기준으로 정가운데 배치후에 @+id/button_center라는 고유 아이디를 정해 주었다. 이 고유 아이디를 기준으로 4개의 방향으로 4개의 버튼을 배치 시켰다.

각각의 버튼은 layout_toLeftOf, layout_toRightOf, layout_above, layout_below속성을 통해 Center버튼을 기준으로 위치하게 된다. 그림 3.2.2 왼쪽 그림은 Center버튼을 기준으로 위치해 있을 뿐 정렬이 되지 않아 RelativeLayout의 기본 위치인 왼쪽 상단으로 배치 된 것을 볼 수 있다. 정렬된 기준도 같은 위치에 배치하기위해 layout_align속성을 사용하여 정렬하게 되면 그림 3.2.2 오른쪽 그림과 같이 정렬 되게 된다.

<그림 3.2.2> RelativeLayout의 자식뷰 정렬 방식

이렇게 부모뷰 또는 자식뷰 간의 상대적인 위치를 통해 정렬하지 않고 자신뷰를 중첩 형태로 구성이 가능하다. 코드 3.2.3은 RelativeLayout에 배경이 들어 있는 이미지를 배치 하여 중첩 시킨 구조이다.

그림 3.2.3은 이미지뷰가 중첩되어 그려진 것을 볼 수 있다. 중첩되는 위치는 시스템에서 레이아웃 XML 문서를 읽는 방식에 있는데, 위쪽에서 아래쪽으로 읽혀 지기때문에 가장 위쪽에 있을 수록 뷰는 처음에 그려져 하단에 배치되며 아래쪽으로 있으면 뷰는 나중에 그려져 상단에 배치 된다.

<그림 3.2.3> RelativeLayout의 자식뷰 중첩 방식


3.3 FrameLayout

FrameLayout은 아주 단순한 레이아웃으로 보통 하나의 자식뷰 또는 레이아웃을 배치시키나 경우에 따라 더 많이 배치시키는 경우도 있다. 방금 설명한 RelativeLayout과 마찬가지로 내부에 들어가는 자식뷰들이 중첩된다. 또한 layout_gravity속성을 이용하면 뷰모뷰에 대한 자식뷰의 상적인 배치도 가능하다.

리스트뷰에 데이터를 가져오는 중에는 로딩화면을 보여줄경우 FrameLayout을 사용하여 간단히 처리가능하다. 코드 3.3.1 은 FrameLayout에 뷰를 리스트뷰와 로딩화면을 중첩해 놓은 다음 작업중일때는 로딩화면을 보이 하고 있고, 처리가 완료되면 로딩화면을 숨겨 리스트뷰가 보이도록 하는 아주 일반적인 구조이다.

실제로 FrameLayout을 이용한 구조는 리스트뷰를 쉽게 사용하기위해 만들어진 ListActivity또는 ListFragment에서 로딩화면을 보여주는 방식으로 사용된다. 아주 간단한 구조이기 때문에 플레그먼트를 구성할때도 많이 쓰인다.


3.4 TableLayout

TableLayout은 LinearLayout을 상속받아 구현된 레이아웃으로 경계선을 표시 하지 않는 행과 열의 가지는 격자 형태로 뷰를 정렬하는 레이아웃이다. 일반적으로 표에 뷰를 배치한다고 생각 하면 쉬우며 열과의 병합은 되나 행과의 병합은 되지 않는다. 행은 TableRow를 이용해서 하나의 행을 만들 수 있으며 내부에 뷰를 배치하면 하나의 열이 만들어지는 구조이다. 이때 TableRow중에 가장 많은 열의 개수는 테이블 내의 전체 열의 개수가 된다. 다양한 속성을 통해 특정 열을 숨기거나 화면에 꽉 차도록 늘리거나 줄이는 기능들이 있다.

코드 3.4.1은 TableLayout의 TableRow를 통해 4개 행을 배치하고 각각의 행에 버튼을 다양한 갯수로 열을 배치하였다.

기존의 레이아웃에서 반드시 뷰의 넓이와 높이 값을 주어야 했었으나 TableRow의 내의 뷰는 넓이와 높이 값을 별로도 주지 않아도 된다. TableRow의 내부의 뷰로 화면을 채우기에 작은 경우에 layout_stretchColumns속성을 이용하여 뷰를 늘려 채우게 가능하며 많은 경우에는 layout_shrinkColumns속성을 이용하여 뷰를 줄여 화면에 맞게 조정 가능하다. 그림 3.4.1에서 뷰로 화면에 채우지 못하지만 layout_stretchColumns속성을 통해 0~3까지의 값을 주어 균등하게 늘어난 것을 볼 수 있다. 균등하게 늘어나지 않고 특정 열만 늘어 나게 하고 싶으면 특정 열의 값을 주면 된다.

<그림 3.4.1> TableLayout 예제 실행

LinearLayout과 RelativeLayout에서 설명한 다양한 화면을 지원하기 위한 기본적인 구조를 가진다고 볼 수 있다. TableRow내의 뷰에서 layout_span속성을 이용하면 열을 병합도 가능하다. 이처럼 TableLayout은 TableRow과 같이 사용되며 다양한 속성을 통해서 명확하게 뷰를 배치할 수 있다.


3.5 GridLayout

안드로이드 4.0(API 14)부터 지원하는 레이아웃으로 지정된 행과 열로 격자 방식으로 자식뷰를 정렬하는 방식이다. 3.1에서 설명한 LinearLayout은 수평 또는 수직으로 정렬이 가능하나 수평과 수직을 동시에 정렬하기 위해서는 중첩해서 사용해야 하기때문에 계층구조가 복잡해짐과 동시에 성능 문제도 발생한다. 또한 TableLayout은 TableRow를 사용해야 하기때문에 계층구조가 복잡해지고 행과의 병합이 되지 않는다. 기존의 레이아웃의 문제를 개선하기 위해 행과 열을 격자에 따라 자유롭게 정렬을 제어 할 수 있게 하고 이로 인해 중첩을 피할 수 있는 구조로 설계되었다. TabletLayout과 마찬가지로 내부의 뷰들은 크기가 자동으로 지정되기때문에 넓이와 높이를 설정하는 layout_height와 layout_width값을 설정 할 필요는 없다.

코드 3.5.1은 GridLayout의 장점을 가장 잘 보여주는 예제이다. layout_columnCount 속성을 통해서 열이 5개라는 것과 layout_orientation을 통해서 가로순으로 배치하는 것으로 설정 한다. 이렇게 속성을 지정하게 되면 내부에 들어갈 자식뷰는 순차적으로 가로순으로 5개의 열에 배치되는 것이 기본적인 GridLayout의 형식이다. layout_columnCount외에도 layout_rowCount도 있는데 행의 갯수를 지정하는 것이며, 이때 두개를 동시에 속성을 설정하게 된다면 내부에 들어가는 자식뷰들은 가변적이기 때문에 동시에 설정되지 않고 하나만 설정된다.

내부에 들어가는 뷰들은 기본적으로 지정된 갯수에 따라 하나의 행또는 열에 배치된다. 하지만 layout_rowSpan, layout_columnSpan속성을 이용하면 행 또는 열의 크기를 지정 할 수 있다. 그림 3.5.1에서 2번째 버튼은 rowSpan속성의 값을 2로 주어 2개의 열이 합쳐진 것을 볼 수 있다. 이때 layout_gravity속성을 통해 2번째 공간을 3번째로 합칠지 또는 기본속성인 공간만 비울지 설정 할 수 있게된다. 5번째 버튼은 기본속성으로 layout_gravity에 별도의 값을 주지 않아 하나의 열의 공간만 비워져서 있다.

<그림 3.5.1> GridLayout의 사용 예제

이처럼 GridLayout을 이용하게되면 가상의 격자에서 원하는 위치에 자식뷰를 배치할 수 있는 구조이기 때문에 중첩구조를 사용하는 복잡한 레이아웃의 경우 좀 더 간단하게 만들 수 있다. 하지만 LinearLayout과 RelativeLayout과 같이 화면의 크기가 가변적인 상황에 유용하지 않다. 코드 3.5.2는 텍스트가 화면의 크기가 동적인 상황에 대해서 어떻게 처리하는지를 알아보기위해 3개의 열로 가로로 정렬되게 하였다.

텍스트의 크기가 변화되고 버튼과 이미지들은 양옆으로 고정되어 있어야 다양한 화면의 크기에 대응을 할 수 있는 기준이 된다. 그림 3.5.2의 가로모드에서는 왼쪽 이미지와 오른쪽 버튼이 화면에 맞게 표현되었지만 세로모드는 텍스트가 짤려 표현된 것을 볼 수 있다. GridLayout은 내부 자식뷰들 내에 표시되는 컨텐츠에 따라 뷰의 크기가 정해지기 때문에 LinearLayout처럼 화면의 크기가 가변적인 상황에 사용하지 못하는 단점이 있다.

<그림 3.5.2>

GridLayout은 내부 자식뷰의 크기가 정해져 있는 구조에서 LinearLayout 또는 TableLayout등의 단점인 중첩을 피할 수 있는 가장 최적화된 레이아웃이다.