뷰의 레이아웃

Compose 방식 사용해 보기
Jetpack Compose는 Android에 권장되는 UI 도구 키트입니다. Compose에서 레이아웃을 사용하는 방법을 알아보세요.

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

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

View 객체는 종종 위젯이라고 하며 Button 또는 TextView과 같은 여러 서브클래스 중 하나일 수 있습니다. ViewGroup 객체는 일반적으로 레이아웃이라고 하며 LinearLayout 또는 ConstraintLayout와 같은 다양한 레이아웃 구조를 제공하는 여러 유형 중 하나일 수 있습니다.

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

  • UI 요소를 XML로 선언합니다. Android는 위젯 및 레이아웃과 같이 View 클래스 및 서브클래스에 상응하는 간단한 XML 어휘를 제공합니다. Android 스튜디오의 Layout Editor를 사용하여 드래그 앤 드롭 인터페이스로 XML 레이아웃을 빌드할 수도 있습니다.

  • 런타임에 레이아웃 요소를 인스턴스화합니다. 앱은 ViewViewGroup 객체를 만들고 그 속성을 프로그래매틱 방식으로 조작할 수 있습니다.

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

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

XML 쓰기

Android의 XML 어휘를 사용하면 일련의 중첩된 요소가 있는 HTML로 웹페이지를 만드는 것과 동일한 방식으로 UI 레이아웃과 포함된 화면 요소를 빠르게 디자인할 수 있습니다.

각 레이아웃 파일은 정확히 하나의 루트 요소를 포함해야 하며, 이 요소는 View 또는 ViewGroup 객체여야 합니다. 루트 요소를 정의한 후 추가 레이아웃 객체 또는 위젯을 하위 요소로 추가하여 점진적으로 레이아웃을 정의하는 View 계층 구조를 빌드할 수 있습니다. 예를 들어 세로 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로 저장된 경우 다음과 같이 Activity에 로드합니다.

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);
}

Android 프레임워크는 Activity가 실행되면 Activity에서 onCreate() 콜백 메서드를 호출합니다. 활동 수명 주기에 관한 자세한 내용은 활동 소개를 참고하세요.

속성

모든 ViewViewGroup 객체는 고유한 다양한 XML 속성을 지원합니다. 일부 속성은 View 객체와 관련이 있습니다. 예를 들어 TextViewtextSize 속성을 지원합니다. 그러나 이러한 속성은 이 클래스를 확장하는 View 객체에도 상속됩니다. 일부 속성은 id 속성과 같이 루트 View 클래스에서 상속되기 때문에 모든 View 객체에 공통적으로 적용됩니다. 다른 속성은 레이아웃 매개변수로 간주되며, 이는 View 객체의 특정 레이아웃 방향을 설명하는 속성으로 해당 객체의 상위 ViewGroup 객체에서 정의합니다.

ID

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

android:id="@+id/my_button"

문자열 시작 부분의 at 기호 (@)는 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를 만들 때 뷰 객체의 ID를 정의하는 것이 중요합니다. 상대 레이아웃에서 동위 뷰는 고유 ID로 참조되는 다른 동위 뷰에 상대적인 레이아웃을 정의할 수 있습니다.

ID는 트리 전체에서 고유할 필요는 없지만, 검색하는 트리 부분 내에서 고유해야 합니다. 트리 전체가 될 수도 있으므로 가능하면 고유하게 만드는 것이 좋습니다.

레이아웃 매개변수

layout_something이라는 XML 레이아웃 속성이 상주하는 ViewGroup에 적합한 View의 레이아웃 매개변수를 정의합니다.

모든 ViewGroup 클래스는 ViewGroup.LayoutParams를 확장하는 중첩 클래스를 구현합니다. 이 서브클래스에는 뷰 그룹에 맞게 각 하위 뷰의 크기와 위치를 정의하는 속성 유형이 포함되어 있습니다. 그림 2와 같이 상위 뷰 그룹은 하위 뷰 그룹을 포함한 각 하위 뷰의 레이아웃 매개변수를 정의합니다.

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

모든 LayoutParams 서브클래스에는 설정 값을 위한 고유한 문법이 있습니다. 각 하위 요소는 상위 요소에 적절한 LayoutParams를 정의해야 하지만 자체 하위 요소에 다른 LayoutParams를 정의할 수도 있습니다.

모든 뷰 그룹에는 layout_widthlayout_height를 사용하여 너비와 높이가 포함되며, 각 뷰는 이들을 정의해야 합니다. 많은 LayoutParams에는 선택적으로 여백과 테두리가 포함됩니다.

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

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

일반적으로 픽셀과 같은 절대적인 단위를 사용하여 레이아웃 너비와 높이를 지정하지 않는 것이 좋습니다. 더 나은 접근 방식은 밀도 독립형 픽셀 단위 (dp), wrap_content 또는 match_parent와 같은 상대적인 측정값을 사용하는 것입니다. 앱이 다양한 기기 화면 크기에서 올바르게 표시되는 데 도움이 되기 때문입니다. 허용되는 측정 유형은 레이아웃 리소스에 정의되어 있습니다.

레이아웃 위치

뷰에는 직사각형 도형이 있습니다. 왼쪽상단 좌표로 표현되는 위치와 너비 및 높이로 표현되는 2차원의 위치가 있습니다. 위치와 치수의 단위는 픽셀입니다.

뷰의 위치를 가져오려면 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를 참고하세요.

측정기준에 대한 자세한 내용은 크기를 참고하세요.

프로그래매틱 방식으로 여백과 패딩을 설정하는 것 외에도 다음 예와 같이 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:layout_margin="16dp"
                android:padding="8dp"
                android:text="Hello, I am a TextView" />
      <Button android:id="@+id/button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginTop="16dp"
              android:paddingBottom="4dp"
              android:paddingEnd="8dp"
              android:paddingStart="8dp"
              android:paddingTop="4dp"
              android:text="Hello, I am a Button" />
  </LinearLayout>
  

앞의 예는 여백과 패딩이 적용되는 것을 보여줍니다. TextView에는 균일한 여백과 패딩이 전체적으로 적용되고 Button은 다양한 가장자리에 독립적으로 적용하는 방법을 보여줍니다.

일반 레이아웃

ViewGroup 클래스의 각 서브클래스는 서브클래스에 중첩된 뷰를 표시하는 고유한 방법을 제공합니다. 가장 유연한 레이아웃 유형이자 레이아웃 계층 구조를 얕게 유지하기 위한 최고의 도구를 제공하는 유형은 ConstraintLayout입니다.

다음은 Android 플랫폼에 내장된 몇 가지 일반적인 레이아웃 유형입니다.

선형 레이아웃 만들기

하위 요소를 하나의 가로 또는 세로 행으로 정리하고, 창의 길이가 화면 길이를 초과하는 경우 스크롤바를 생성합니다.

WebView에서 웹 앱 빌드

웹페이지를 표시합니다.

동적 목록 만들기

레이아웃의 콘텐츠가 동적이거나 미리 정의되지 않은 경우 RecyclerView 또는 AdapterView의 서브클래스를 사용할 수 있습니다. RecyclerView가 일반적으로 더 나은 옵션입니다. AdapterView보다 메모리를 더 효율적으로 사용하기 때문입니다.

RecyclerViewAdapterView로 가능한 일반적인 레이아웃은 다음과 같습니다.

목록

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

그리드

열과 행의 스크롤 그리드를 표시합니다.

RecyclerView는 더 많은 가능성과 맞춤 레이아웃 관리자 만들기 옵션을 제공합니다.

데이터로 어댑터 뷰 채우기

AdapterView ListView 또는 GridView 인스턴스를 외부 소스에서 데이터를 가져와 각 데이터를 나타내는 View 인스턴스를 만드는 AdapterView 인스턴스를 Adapter 바인딩하여 채울 수 있습니다.

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

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);
    

그러면 SimpleCursorAdapter는 각 fromColumns 항목을 상응하는 toViews 뷰에 삽입하여 제공된 레이아웃을 사용하여 Cursor에 있는 각 행의 뷰를 만듭니다.

앱 수명 동안 어댑터에서 읽은 기본 데이터를 변경하는 경우 notifyDataSetChanged()를 호출하세요. 이렇게 하면 연결된 뷰에 데이터가 변경되고 자체적으로 새로고침된다는 것을 알립니다.

클릭 이벤트 처리

AdapterView.OnItemClickListener 인터페이스를 구현하여 AdapterView의 각 항목에서 발생하는 클릭 이벤트에 응답할 수 있습니다. 예:

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);

추가 리소스

GitHub의 Sunflower 데모 앱에서 레이아웃이 사용되는 방식을 확인하세요.