Developing Myself Everyday
article thumbnail

사진: UnsplashAnna Kolosyuk

 

 

우리는 안드로이드 개발을 하면서 XML을 통해 화면을 그리고 이를 사용해 왔습니다. 그런데 이게 어떤 과정으로 어떻게 이뤄지는지 생각해본적은 없는것 같습니다. 그래서 이번 게시글에서 안드로이드가 화면을 어떻게 그리는지 알아보고자 합니다.


 

 

 

안드로이드 프레임워크의 요청


안드로이드의 프레임워크는 Activity가 포커스를 받을 때, 레이아웃을 그리도록 Activity에 요청합니다. 안드로이드 프레임워크에서는 알아서 그리기 절차를 처리하지만 Activity에서는 레이아웃 계층 구조의 루트 노드를 제공해야 합니다.

 

루트노드는 `setContentView()` 메서드를 통해 제공합니다. 그렇기 때문에 무의식적으로 안드로이드를 개발하면서 아래와 같이 코드를 작성하고 루트 노드를 넘겨왔습니다.

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

 

 

바인딩을 사용했다면 아래와 같습니다. 확실히 루트 노드를 제공한다는 것이 보입니다.

private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
}

 

 

이렇게 해서 안드로이드 프레임워크가 레이아웃 계층 구조의 루트 노드를 요청한다는 것을 알게 되었습니다. 그럼 이제 레이아웃에 대해 좀 더 자세히 알아볼까요?

 

 

 

 

 

레이아웃의 View와 ViewGroup


안드로이드에서 레이아웃(Layout)이란 애플리케이션에서 액티비티에 들어가는 UI를 위한 구조를 정의를 말합니다. 

 

레이아웃의 모든 요소는  ViewViewGroup의 계층으로 이루어져 있습니다. View는 사용자가 볼 수 있고 상호작용할 수 있는 어떠한 것들을 말합니다.

 

ViewGroup은 아래의 그림과 같이  View와 다른 ViewGroup 레이아웃 구조를 정의하는 눈에 보이지 않는 컨테이너입니다.

 

 

 

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

 

 

View의 계층

View는 아래의 그림과 같이 많은 서브 클래스들이 상속하고 있습니다.

 

 

레이아웃 선언

레이아웃은 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 레이아웃 파일은 앱을 컴파일하는 시점에 View 리소스 안에 컴파일 됩니다.

 

 

View 리소스 안에 컴파일된 레이아웃은 R.layout.의 형태로 아래와 같이 setContentView()를 통해 레이아웃 리소스 참조로 전달합니다. 

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

 

 

setContentView

setContentView()를 좀 더 자세하게 보겠습니다. 이름에서 알 수 있듯이 이 메서드의 역할은 UI에 표시되는 컨텐츠 뷰를 설정하는 메서드입니다.

 

setContentView()는 아래와 같이 2가지 작업을 합니다.

@Override
public void setContentView(@LayoutRes int layoutResID) {
    initViewTreeOwners();
    getDelegate().setContentView(layoutResID);
}

 

 

initViewTreeOwners() 메서드는 ViewTreeOwner를 설정 역할을 합니다. 이 작업은 컨텐츠 뷰를 설정하기 이전에 진행됩니다. 이렇게 하면 프로세스 및 리스너 연결 작업에서 이들을 볼 수 있게 됩니다.

private void initViewTreeOwners() {
    ViewTreeLifecycleOwner.set(getWindow().getDecorView(), this);
    ViewTreeViewModelStoreOwner.set(getWindow().getDecorView(), this);
    ViewTreeSavedStateRegistryOwner.set(getWindow().getDecorView(), this);
    ViewTreeOnBackPressedDispatcherOwner.set(getWindow().getDecorView(), this);
}

 

 

setContentView()의 내부에서는 XML 파일을 View 객체로 구현하는 작업이 inflate 작업이 진행됩니다.

 

Fragment에는 이러한 setContentView()가 없기에 LayoutInflater를 사용해서 inflate 작업을 하게 됩니다.

 

 

 

 

안드로이드가 프레임워크가 그리는 방법


이렇게 해서 안드로이드 프레임워크는 원하던 대로 레이아웃 계층 구조의 루트 노드를 전달받았습니다. 이젠 이것을 그리는 것만 남았습니다.

 

루트 노드에서부터 시작해 뷰그룹이나 뷰들을 트리형태로 나타낸 레이아웃 트리를 순회하면서  View 들을 그립니다.

 

각각의 View 아래 그림과 같은 생명주기를 갖습니다.

 

 

부모 뷰에 의해 addView() 메서드가 호출되면  View의 생명주기가 시작됩니다.

 

View의 생명주기는 크게 뷰의 위치와 크기를 지정하는 과정인 측정 레이아웃 단계로 나뉩니다.

 

 

측정 단계

View 객체의 measure() 메서드가 반환되면 getMeasuredWidth()  getMeasuredHeight() 값과 함께 모든 View 객체의 하위 요소 값을 설정합니다. View 객체의 측정된 너비 및 측정된 높이 값은 View 객체의 부모 요소가 부여한 제약 조건을 준수합니다.

 

그러면 측정 패스가 종료되고 모든 상위 요소에서 하위 요소의 측정값을 모두 수락합니다.

 

쉽게 말하자면 뷰모 View가 자식 방향의 크기를 정해준다고 생각하면 됩니다. 부모 View는 2가지 방법을 통해 자식 View에게 크기를 전달할 수 있습니다.

 

 

ViewGroup.LayoutParams 

ViewGroup.LayoutParams 클래스는 View 객체가 선호하는 크기 및 위치를 전달하는 방법입니다. 기본 ViewGroup.LayoutParams 클래스는 View의 기본 너비 및 높이를 설명합니다. 각 크기의 경우 다음 중 하나를 지정할 수 있습니다.

  • 정확한 크기
  • MATCH_PARENT: View의 기본 크기가 상위 요소의 크기에서 패딩을 뺀 값입니다.
  • WRAP_CONTENT: View의 기본 크기가 콘텐츠와 패딩을 포함할 만큼만 큽니다.

 

 

MeasureSpec

MeasureSpec 객체는 상위 요소에서 하위 요소로 요구사항을 트리에서 아래로 푸시하는 데 사용합니다. MeasureSpec은 다음 세 가지 모드 중 하나일 수 있습니다.

  • UNSPECIFIED: 상위 요소가 이를 사용하여 하위 View의 타겟 크기를 결정합니다. 예를 들어 LinearLayout은 높이가 UNSPECIFIED로 설정되고 너비가 EXACTLY 240으로 설정된 하위 요소에서 measure()를 호출하여 너비가 240픽셀일 경우 하위 요소 View의 높이를 알아낼 수 있습니다.
  • EXACTLY: 상위 요소가 이를 사용하여 하위 요소에 정확한 크기를 적용합니다. 하위 요소는 이 크기를 사용해야 하며 이것의 모든 하위 요소가 이 크기에 맞도록 보장합니다.
  • AT MOST: 상위 요소가 이를 사용하여 하위 요소에 최대 크기를 적용합니다. 하위 요소는 하위 요소와 이 하위 요소의 모든 하위 요소가 이 크기에 맞도록 보장해야 합니다.

 

 

레이아웃 단계

레이아웃 단계는 위에서 말했듯이 View의 크기를 정해주는 단계입니다.

onLayout(boolean changed, int left, int top,int right,int bottom)

 

 

그리기 단계

측정과 레이아웃 단계가 끝나면 각 View들은 자기 자신의 위치와 크기가 정해져 있습니다. 각각의 View들은 이제 onDraw() 를 호출해 자기 자신을 그립니다.

protected void onDraw (Canvas canvas)

 onDraw()의 매개변수는 뷰에서 자기 자신을 그릴 때 사용할 수 있는 Canvas 객체입니다. Canvas 클래스는 텍스트, 선, 비트맵 및 기타 여러 그래픽 프리미티브를 그리기 위한 메서드를 정의합니다.

 

View를 그리기 위한 단계들은 위에서부터 아래로 트리를 반복해서 순회하며 진행됩니다.


 

 

마무리하며


이렇게 해서 안드로이드가 화면을 어떻게 그리는지 알아보았습니다. 

 

 

 

 

Reference

 

Layouts in Views  |  Android Developers

A layout defines the visual structure for a user interface, such as the UI for an activity or app widget . You can declare a layout in two ways: The Android framework gives you the flexibility to use either or both of these methods for declaring and managi

developer.android.com

 

Android가 뷰를 그리는 방법  |  Android 개발자  |  Android Developers

활동이 포커스를 받으면 레이아웃을 그리라는 요청을 받습니다. Android 프레임워크에서 그리기 절차를 처리하지만 활동에서 레이아웃 계층 구조의 루트 노드를 제공해야 합니다. 그리기는 레이

developer.android.com

 

Android View Lifecycle

[ In Android, View class is the base for widgets, which are used to create interactive UI components(Button, TextView, EditText,…). Android’s widgets are sufficient for the needs of most apps. However, there may be occasions on which you feel the nee

codentrick.com

 

profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!