The Android Developer Challenge is back! Submit your idea before December 2.

레이아웃   Part of Android Jetpack.

레이아웃은 앱에서 사용자 인터페이스를 위한 구조(예: 액티비티)를 정의합니다. 레이아웃의 모든 요소는 ViewViewGroup 객체의 계층을 사용하여 빌드됩니다. 일반적으로 View는 사용자가 보고 상호작용할 수 있는 것을 그립니다. ViewGroup은 그림 1과 같이 View와 다른 ViewGroup 객체의 레이아웃 구조를 정의하는 투명 컨테이너입니다.

그림 1. UI 레이아웃을 정의하는 뷰 계층.

일반적으로 View 객체는 '위젯'이라고 하고 여러 하위 클래스 중 하나가 될 수 있습니다(예: Button 또는 TextView). ViewGroup 객체는 대개 '레이아웃'이라고 부르고 다양한 레이아웃 구조를 제공하는 여러 유형 중 하나일 수 있습니다(예: LinearLayout 또는 ConstraintLayout).

레이아웃을 정의하는 방법은 두 가지입니다.

  • UI 요소를 XML로 선언. Android는 위젯과 레이아웃 등과 같이 View 클래스와 하위 클래스에 해당하는 간단한 XML 어휘를 제공합니다.

    Android Studio의 Layout Editor를 사용하여 드래그 앤 드롭 인터페이스로 XML 레이아웃을 빌드할 수도 있습니다.

  • 런타임에 레이아웃 요소 인스턴스화. 앱 프로그래밍 방법으로 View 및 ViewGroup 객체를 만들 수 있습니다(그리고 그 속성을 조작하기도 합니다).

XML에서 UI를 선언하면 동작을 제어하는 코드와 앱을 표현하는 것을 분리할 수 있습니다. 또한 XML 파일을 사용하면 다양한 화면 크기와 방향에 여러 가지 레이아웃을 쉽게 제공할 수 있습니다(자세한 내용은 다양한 화면 크기 지원에서 설명).

Android 프레임워크는 이 메서드 중 하나 또는 두 가지 모두를 사용하여 앱의 UI를 빌드하는 유연한 수단을 제공합니다. 예를 들어 앱의 기본 레이아웃을 XML에서 선언한 다음, 런타임에서 레이아웃을 수정할 수 있습니다.

팁:런타임에서 레이아웃을 디버깅하려면 Layout Inspector 도구를 사용하세요.

XML 쓰기

Android의 XML 어휘를 사용하면 UI 레이아웃과 그 안에 들어있는 화면 요소를 HTML에서 웹 페이지를 디자인할 때와 같은 방식으로 신속하게 디자인할 수 있습니다. 즉 일련의 중첩된 요소를 사용하는 것입니다.

각 레이아웃 파일에는 반드시 딱 하나의 루트 요소만 있어야 하며, 이는 View 또는 ViewGroup 객체여야 합니다. 루트 요소를 정의한 후에 더 많은 레이아웃 객체 또는 위젯을 하위 요소로 추가하여 계층적으로 레이아웃을 정의하는 뷰 계층을 빌드할 수 있습니다. 예를 들어 다음은 수직 LinearLayout을 사용하여 TextViewButton을 보유하는 XML 레이아웃을 나타낸 것입니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>

레이아웃을 XML로 선언하고 나면 그 파일을 Android 프로젝트의 res/layout/ 디렉토리 내에 .xml 확장자로 저장하여 적절하게 컴파일되도록 합니다.

레이아웃 XML 파일의 구문에 대한 자세한 정보는 레이아웃 리소스 문서에서 확인할 수 있습니다.

XML 리소스 로드

앱을 컴파일하는 경우, 각 XML 레이아웃 파일이 View 리소스 안에 컴파일됩니다. Activity.onCreate() 콜백 구현에서 앱 코드로부터 레이아웃 리소스를 로드해야 합니다. setContentView()를 호출하고 R.layout.layout_file_name의 형태로 레이아웃 리소스에 대한 참조로 전달합니다. 예를 들어 XML 레이아웃이 main_layout.xml로 저장된다면 다음과 같이 액티비티에 대해 로드합니다.

Kotlin

fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_layout)
}

Java

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

액티비티 내의 onCreate() 콜백 메서드는 액티비티가 시작될 때 Android 프레임워크에 의해 호출됩니다(수명 주기에 대한 설명은 액티비티 문서에서 확인하세요).

특성

모든 View와 ViewGroup 객체는 고유한 여러 가지 XML 특성을 지원합니다. 어떤 특성은 View 객체에만 적용되지만(예: TextView는 textSize 특성 지원), 이 특성은 이 클래스를 확장할 수 있는 View 객체로부터 상속받은 것입니다. 어떤 특성은 루트 View 클래스에서 상속되기 때문에(예: id 특성) 모든 View 객체에 공통적으로 적용됩니다. 그리고 나머지 특성은 "레이아웃 매개변수"로 간주됩니다. 이들은 View 객체의 특정한 레이아웃 방향을 설명하는 것으로, 이는 해당 객체의 상위 ViewGroup 객체에서 정의된 바에 따릅니다.

ID

View 객체는 트리 내에서 뷰를 고유하게 식별할 수 있는 정수 ID가 연결될 수 있습니다. 앱을 컴파일할 때 이 ID는 정수로 참조되지만, 일반적으로 레이아웃 XML 파일의 id 특성에서 문자열로 할당됩니다. 이는 모든 View 객체에 공통적인 XML 특성이며(View 클래스에서 정의), 매우 자주 사용하게 될 것입니다. XML 태그 내에서 ID의 구문은 다음과 같습니다.

android:id="@+id/my_button"

문자열 시작 부분에 있는 앳 기호(@)는 XML 파서가 ID 문자열의 나머지를 파싱하고 확장하여 ID 리소스로 식별해야 한다는 것을 나타냅니다. 더하기(+) 기호는 이것이 새 리소스 이름이며, 이것을 반드시 생성하여 리소스에 추가해야 한다는 것을 뜻합니다(R.java 파일에서). Android 프레임워크는 다른 ID 리소스도 아주 많이 제공합니다. Android 리소스 ID를 참조할 때에는 더하기 기호는 필요하지 않지만 android 패키지 네임스페이스를 다음과 같이 반드시 추가해야 합니다.

android:id="@android:id/empty"

android 패키지 네임스페이스가 들어가면 이제 로컬 리소스 클래스에서가 아니라 android.R 리소스 클래스에서 ID를 참조하게 됩니다.

뷰를 생성하고 이를 앱 참조하는 데 쓰이는 보편적인 패턴은 다음과 같습니다.

  1. 레이아웃 파일에서 뷰/위젯을 정의한 다음 이를 고유한 ID에 할당합니다.
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. 그런 다음 뷰 객체의 인스턴스를 생성하고 이를 레이아웃에서 캡처합니다(일반적으로 onCreate() 메서드에서).

    Kotlin

    val myButton: Button = findViewById(R.id.my_button)
    

    Java

    Button myButton = (Button) findViewById(R.id.my_button);
    

RelativeLayout을 생성할 때 View 객체에서 ID를 정의하는 것이 중요합니다. 관계 레이아웃에서는 형제 뷰가 또 다른 형제 뷰와 관련된 자신의 레이아웃을 정의할 수 있으며, 이를 고유한 ID로 참조하게 됩니다.

ID는 트리 전체를 통틀어 고유할 필요는 없지만, 트리에서 검색하고 있는 부분 내에서는 고유해야 합니다(이것이 트리 전체인 경우가 잦으므로 가급적이면 완전히 고유한 것을 쓰는 것이 가장 좋습니다).

레이아웃 매개변수

layout_something이라는 XML 레이아웃 특성이 뷰가 상주하는 ViewGroup에 대해 적절한 뷰의 레이아웃 매개변수를 정의합니다.

모든 ViewGroup 클래스가 중첩된 클래스를 하나씩 구현하며 이것이 ViewGroup.LayoutParams를 확장합니다. 이 하위 클래스에는 각 하위 뷰의 크기와 위치를 뷰 그룹에 적절한 방식으로 정의하는 속성 유형이 들어 있습니다. 그림 2에서 볼 수 있듯이, 상위 뷰 그룹이 각 하위 뷰의 레이아웃 매개변수를 정의합니다(하위 뷰 그룹 포함).

그림 2. 각 뷰와 연관된 레이아웃 매개변수가 있는 뷰 계층을 시각화한 것.

모든 LayoutParams 하위 클래스에는 설정 값에 대한 각기 자신만의 구문이 있다는 점을 참고하세요. 각 하위 요소는 자신의 상위에 적합한 LayoutParams를 정의해야 합니다. 다만 이것은 자신의 하위에 대해 각기 다른 LayoutParams도 정의할 수 있습니다.

모든 뷰 그룹에는 너비와 높이가 포함되며(layout_widthlayout_height), 각 뷰는 이들을 반드시 정의해야 합니다. 선택 사항으로 여백과 테두리도 포함하는 LayoutParams도 많습니다.

너비와 높이는 정확한 치수로 지정할 수 있습니다. 다만 이것은 자주 하지 않는 것이 좋습니다. 그보다는 다음과 같은 상수 중 하나를 사용하여 너비 또는 높이를 설정하는 경우가 더 많습니다.

  • wrap_content는 콘텐츠에 필요한 치수대로 자동으로 크기를 조정하도록 뷰에 지시합니다.
  • match_parent는 상위 뷰 그룹이 허용하는 한 최대한으로 커지도록 뷰에 지시합니다.

일반적으로 픽셀과 같이 절대적인 단위를 사용하여 레이아웃 너비와 높이를 지정하는 것은 권장하지 않습니다. 그 대신, 밀도 독립적인 픽셀 단위(dp), wrap_content 또는 match_parent와 같이 상대적인 측정치를 사용하는 것이 더 낫습니다. 이렇게 하면 앱이 다양한 기기 화면 크기에 걸쳐서도 적절하게 표시되도록 보장하는 데 도움이 되기 때문입니다. 허용된 측정 유형은 사용 가능한 리소스 문서에 정의되어 있습니다.

레이아웃 위치

뷰의 모양은 직사각형입니다. 뷰에는 위치가 있으며, 이는 한 쌍의 왼쪽상단 좌표, 그리고 두 개의 치수가 너비와 높이를 나타내는 형식으로 표현됩니다. 위치와 치수의 단위는 픽셀입니다.

뷰의 위치를 검색할 수 있습니다. getLeft()getTop() 메서드를 호출하면 됩니다. 전자는 뷰를 나타내는 직사각형의 왼쪽, 즉 X 좌표를 반환합니다. 후자는 뷰를 나타내는 직사각형의 상단, 즉 Y 좌표를 반환합니다. 이들 메서드는 둘 다 해당 뷰의 상위에 상대적인 뷰의 위치를 반환합니다. 예를 들어, getLeft()가 20을 반환하는 경우 이는 해당 뷰가 그 뷰의 바로 상위의 왼쪽 가장자리에서 오른쪽으로 20픽셀 떨어진 곳에 있다는 뜻입니다.

그 외에도 불필요한 계산을 피하기 위해 여러 가지 편의 메서드가 제공됩니다. 구체적으로 getRight()getBottom()을 들 수 있습니다. 이들 메서드는 해당 뷰를 나타내는 직사각형의 오른쪽과 하단 가장자리의 좌표를 반환합니다. 예를 들어 getRight()를 호출하는 것은 getLeft() + getWidth() 계산과 비슷합니다.

크기, 패딩 및 여백

뷰의 크기는 너비와 높이로 표현됩니다. 사실 하나의 뷰는 두 쌍의 너비 및 높이 값을 소유합니다.

첫 번째 쌍을 측정된 너비측정된 높이라고 합니다. 이들 치수는 뷰가 상위 내에서 얼마나 커지고자 하는지를 정의합니다. 측정된 치수를 가져오려면 getMeasuredWidth()getMeasuredHeight()를 호출하면 됩니다.

두 번째 쌍은 단순히 너비높이라고 일컬으며, 때로는 그리기 너비그리기 높이로 부를 때도 있습니다. 이러한 치수는 그리기 시간 및 레이아웃 후에 뷰가 화면에 표시되는 실제 크기를 정의합니다. 이들 값은 측정된 너비 및 높이와 달라도 되지만 꼭 달라야 하는 것은 아닙니다. 너비와 높이를 가져오려면 getWidth()getHeight()를 호출하면 됩니다.

뷰의 치수를 측정하기 위해 뷰는 자신의 패딩을 감안합니다. 패딩은 뷰의 왼쪽, 상단, 오른쪽 및 하단 부분에 대해 픽셀로 표시됩니다. 패딩은 정해진 픽셀 수를 사용하여 뷰의 콘텐츠를 오프셋하는 데 쓰일 수도 있습니다. 예를 들어 왼쪽 패딩을 2로 설정하면 해당 뷰의 콘텐츠를 왼쪽 가장자리에서 오른쪽으로 2픽셀 밀어냅니다. 패딩을 설정할 때에는 setPadding(int, int, int, int) 메서드를 사용하면 되고, 이를 쿼리하려면 getPaddingLeft(), getPaddingTop(), getPaddingRight()getPaddingBottom()을 사용하면 됩니다.

뷰는 패딩을 정의할 수 있지만, 여백에 대한 지원은 전혀 제공하지 않습니다. 다만 뷰 그룹이 그와 같은 지원을 제공합니다. 자세한 내용은 ViewGroupViewGroup.MarginLayoutParams를 참조하세요.

치수에 대한 자세한 내용은 치수 값을 참조하세요.

일반 레이아웃

ViewGroup 클래스의 각 하위 클래스는 각기 고유한 방식으로 자신 안에 중첩된 뷰를 표시합니다. 아래는 Android 플랫폼에서 기본 제공되는, 보다 보편적인 레이아웃 유형을 몇 가지 나타낸 것입니다.

참고: 하나 이상의 레이아웃을 또 다른 레이아웃에 중첩하여 UI 디자인을 이룰 수도 있지만, 레이아웃 계층을 가능한 한 얕게 유지하도록 애써야 합니다. 중첩된 레이아웃이 적을수록 레이아웃이 더욱 빠르게 그려집니다(가로로 넓은 뷰 계층이 깊은 뷰 계층보다 낫습니다).

선형 레이아웃

여러 하위 요소를 하나의 가로 방향 또는 세로 방향 행으로 정리하는 레이아웃. 이 레이아웃은 창의 길이가 화면 길이를 웃도는 경우 스크롤바를 생성합니다.

상대적 레이아웃

여러 하위 객체의 위치를 서로 상대적으로 나타내거나(하위 객체 A가 하위 객체 B의 왼쪽), 상위와 상대적으로 나타낼 수 있도록 해줍니다(상위의 맨 위에 맞춰 정렬).

웹 뷰

웹 페이지를 표시합니다.

어댑터로 레이아웃 빌드하기

레이아웃의 콘텐츠가 동적이거나 미리 정의되지 않은 경우, AdapterView의 하위 클래스가 되는 레이아웃을 사용하여 런타임에 뷰로 레이아웃을 채울 수 있습니다. AdapterView 클래스의 하위 클래스는 Adapter를 사용하여 자신의 레이아웃에 데이터를 바인딩합니다. Adapter가 데이터 소스와 AdapterView 레이아웃 사이의 중개자 역할을 합니다. Adapter가 데이터를 검색하여(배열 또는 데이터베이스 쿼리와 같은 소스로부터) 각 항목을 뷰로 변환해서 AdapterView 레이아웃에 추가될 수 있도록 합니다.

어댑터로 지원되는 일반적인 레이아웃의 몇 가지 예는 다음과 같습니다.

목록 뷰

스크롤 단일 열 목록을 표시합니다.

그리드 뷰

열과 행의 스크롤 눈금을 표시합니다.

데이터로 어댑터 뷰 채우기

ListView 또는 GridView와 같은 AdapterView를 채우려면 AdapterView 인스턴스를 Adapter에 바인딩하면 됩니다. 이는 외부 소스로부터 데이터를 검색하여 각 데이터 항목을 나타내는 View를 생성합니다.

Android는 Adapter의 하위 클래스를 여러 개 제공합니다. 이는 여러 가지 종류의 데이터를 검색하고 AdapterView에 대한 뷰를 빌드하는 데 유용합니다. 가장 보편적인 어댑터 두 가지를 예로 들면 다음과 같습니다.

ArrayAdapter
데이터 소스가 배열인 경우에 이 어댑터를 사용합니다. 기본적으로 ArrayAdapter가 각 배열 항목에서 toString()을 호출하고 그 콘텐츠를 TextView에 배치함으로써 각 항목에 대한 뷰를 생성합니다.

예를 들어 ListView로 문자열 배열을 표시하고자 하는 경우, 생성자를 사용하여 새로운 ArrayAdapter를 초기화해서 각 문자열과 문자열 배열에 대한 레이아웃을 지정하면 됩니다.

Kotlin

val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)

Java

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, myStringArray);

이 생성자에 대한 인수는 다음과 같습니다.

  • 개발자의 앱 Context
  • 배열에 있는 각 문자열에 대한 TextView가 들어 있는 레이아웃
  • 문자열 배열

그런 다음 ListView에서 setAdapter()를 호출하기만 하면 됩니다.

Kotlin

val listView: ListView = findViewById(R.id.listview)
listView.adapter = adapter

Java

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

각 항목의 외관을 사용자 지정하려면 배열의 객체에 대한 toString() 메서드를 재정의하면 됩니다. 아니면, 각 항목에 대하여 TextView가 아닌 다른 뷰를 생성하고자 하는 경우(예를 들어 각 배열 항목에 ImageView를 원하는 경우), ArrayAdapter 클래스를 확장하고 getView()를 재정의하여 각 항목에 대해 원하는 유형의 뷰를 반환하도록 할 수 있습니다.

SimpleCursorAdapter
데이터 소스가 Cursor인 경우에 이 어댑터를 사용합니다. SimpleCursorAdapter를 사용하는 경우, Cursor에 있는 각 행에 대하여 사용할 레이아웃을 지정해야 합니다. 또한 Cursor의 어느 열이 레이아웃의 어느 뷰에 삽입되어야 할지도 지정해야 합니다. 예를 들어 사람 이름과 전화번호로 이루어진 목록을 생성하고자 하는 경우, 각 사람에 대해 행이 하나씩 있고 이름과 번호에 대해 열이 들어 있는 Cursor를 반환하는 쿼리를 수행하면 됩니다. 그런 다음 레이아웃에서 각 결과에 대하여 Cursor의 어느 열을 원하는지 지정하는 문자열 배열을 만들 수 있고, 각 열이 배치되어야 하는 상응하는 뷰를 지정하는 정수 배열을 만들면 됩니다.

Kotlin

val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME,
                          ContactsContract.CommonDataKinds.Phone.NUMBER)
val toViews = intArrayOf(R.id.display_name, R.id.phone_number)

Java

String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                        ContactsContract.CommonDataKinds.Phone.NUMBER};
int[] toViews = {R.id.display_name, R.id.phone_number};

SimpleCursorAdapter를 인스턴스화하는 경우, 각 결과에 대해 사용할 레이아웃과 결과가 들어 있는 Cursor와 다음의 두 배열을 전달합니다.

Kotlin

val adapter = SimpleCursorAdapter(this,
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0)
val listView = getListView()
listView.adapter = adapter

Java

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
ListView listView = getListView();
listView.setAdapter(adapter);

그러면 SimpleCursorAdapterCursor에 있는 각 행에 대한 뷰를 하나씩 생성합니다. 이때 상응하는 toViews 뷰 안에 각 fromColumns 항목을 삽입하여 제공된 레이아웃을 사용합니다.

.

앱 수명이 남아 있는 동안 어댑터가 읽는 기본 데이터를 변경하는 경우, notifyDataSetChanged()를 호출해야 합니다. 이렇게 하면 첨부된 뷰에 데이터가 변경되었으며 새로고침을 해야 한다는 것을 알려줍니다.

클릭 이벤트 처리

AdapterView에 있는 각 항목에서의 클릭 이벤트에 응답하려면 AdapterView.OnItemClickListener 인터페이스를 구현하면 됩니다. 예를 들면 다음과 같습니다.

Kotlin

listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
    // Do something in response to the click
}

Java

// Create a message handling object as an anonymous class.
private OnItemClickListener messageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something in response to the click
    }
};

listView.setOnItemClickListener(messageClickedHandler);

추가 리소스

레이아웃은 Sunflower 데모 앱에서 사용됩니다.