뷰를 이용한 화면 구성 - 화면을 구성하는 방법

이윤설·2024년 8월 24일
0

6-1 화면을 구성하는 방법

액티비티-뷰 구조

액티비티(Activity)-뷰(View) 구조는 안드로이드 애플리케이션에서 사용자 인터페이스(UI)를 구성하는 기본적인 설계 패턴이다. 이를 이해하기 쉽게 설명하자면, 안드로이드 앱의 화면을 구성하는 액티비티(Activity)뷰(View) 간의 관계를 나타낸 것이다.

1. 액티비티(Activity)란?

  • 액티비티는 안드로이드 애플리케이션에서 하나의 화면을 담당하는 컴포넌트다.
  • 액티비티는 화면을 출력하는 컴포넌트 일뿐, 그 자체가 화면은 아니다.
  • 사용자가 보는 앱 화면을 구성하고, 해당 화면에서 이루어지는 사용자 인터랙션(클릭, 입력 등)을 처리한다.
  • 액티비티는 앱 실행 시 첫 화면을 시작하고, 앱 내에서 여러 화면 간 전환을 담당한다.

2. 뷰(View)란?

  • 뷰는 액티비티 안에 포함된 UI 요소를 의미한다. 버튼(Button), 텍스트(TextView), 입력창(EditText), 이미지(ImageView) 등이 모두 뷰다.
  • 뷰는 화면에 표시되는 각각의 개별 구성 요소이며, 사용자의 입력(터치, 스크롤 등)을 받아들인다.

3. 액티비티-뷰 구조

  • 액티비티는 하나 이상의 뷰로 구성되며, 이 뷰들을 배치하고 조정한다.
  • 뷰는 계층 구조(Tree Structure)로 액티비티 내에서 구성된다.
    즉, 부모 뷰가 있고, 그 안에 자식 뷰들이 중첩되는 형태로 화면을 구성한다.
  • 예를 들어, 하나의 액티비티에서 여러 개의 버튼과 텍스트를 보여줄 때, 액티비티가 이 뷰들을 관리하면서, 사용자와 상호작용하게 된다.

비유

액티비티를 하나의 "창문"이라고 생각하면, 뷰는 그 창문 안에 있는 "여러 개의 패널"이나 "위젯"들입니다. 창문(액티비티)이 여러 패널(뷰)을 포함하여 사용자에게 보여주고, 각각의 패널이 자신만의 역할을 수행한다.

요약

  • 액티비티는 하나의 화면을 나타내며, 앱의 핵심 구성 요소다.
  • 는 그 화면 안에 표시되는 개별 UI 요소다.
  • 액티비티는 여러 뷰를 포함하며, 이들을 배치하고 관리한다.

화면이 10개라면 액티비티도 10개를 만들어야 하는가?

Fragment를 사용하면 사용자에게 보여줄 화면이 10개여도 액티비티 1개로 만들수 있다. 그렇지만 학습 초반이므로 액티비티 1개당 화면 1개를 만든다고 이해해도 좋다.


Mainactivity.kt 기본세팅

다음은 Mainactivity.kt의 기본 세팅되어있는 코드이다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }
}

쉽게 풀어 설명하면:

  • super.onCreate(savedInstanceState) 함수: 안드로이드에서 onCreate 함수는 화면이 처음 생성될 때 호출된다. 이 함수는 기본적으로 Activity가 시작될 때 필요한 초기 설정을 처리한다.
    부모 클래스인 AppCompatActivity의 onCreate 메서드를 호출한다. 안드로이드에서 기본적인 UI 초기화 작업이 이곳에서 처리된다.

  • 엣지 투 엣지 설정 (enableEdgeToEdge()):
    이 함수는 엣지 투 엣지 디자인을 활성화하는 역할을 한다.
    엣지 투 엣지란 화면의 모든 영역을 사용하는 디자인이다.
    이는 최신 안드로이드 기기에서 화면을 더 넓게 활용할 수 있도록 해준다.
    일반적으로 시스템 바(상태바와 내비게이션 바)가 차지하는 영역까지 화면 요소가 확장된다.

  • 레이아웃 설정 (setContentView(R.layout.activity_main)):
    이 코드는 XML 파일에 정의된 레이아웃을 현재 화면에 표시한다.
    R.layout.activity_main은 res/layout/activity_main.xml 파일을 가리킨다.

    • R은 리소스(Resource)를 의미하며, 앱의 모든 리소스(레이아웃, 이미지 등)가 여기에 정의된다.
    • 이 코드로 XML에서 정의한 UI 요소들이 화면에 나타난다.
  • 시스템 바(상단 상태바, 하단 내비게이션 바) 인셋 처리(WindowInsets):

ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
    val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
    insets
}

🧸 ViewCompat.setOnApplyWindowInsetsListener: 이 메서드는 특정 뷰에 대해 WindowInsets를 처리하기 위한 리스너를 설정한다. 쉽게 말해, 이 리스너는 시스템 바 영역을 계산하여 적절한 패딩을 추가한다.

🧸 findViewById(R.id.main): XML 파일에서 id가 main인 뷰를 찾는다. 이 뷰가 전체 화면을 감싸는 컨테이너일 가능성이 크다.

🧸 val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()): 이 코드에서 systemBars는 상태바와 내비게이션 바의 크기를 얻는다. 이 값이 패딩으로 적용된다.

🧸 v.setPadding(...): 패딩을 설정하여 시스템 바가 차지하는 영역만큼 화면 요소들이 안전하게 배치되도록 한다. systemBars.left, systemBars.top, systemBars.right, systemBars.bottom으로 각 방향별 패딩을 설정한다.

🧸 insets: 마지막으로 insets를 반환하여 원래 WindowInsets 객체를 유지한다.

결론: 위 코드는 Android 앱의 최신 권장 사항과 모범 사례를 반영한 기본 설정이다. Android 개발의 최신 트렌드와 권장 사항을 반영하여, 개발자가 처음부터 좋은 관행을 따를 수 있도록 돕기 위함이다.

앱의 첫 화면 결정 vs 첫 화면 구성

단어가 비슷해서 혼동될 수 있는 개념이다.
이를 유튜브를 예시로 알아보자.

좋은 접근이다! 유튜브 앱을 예로 들어 설명해 보겠다.

1. 앱의 첫 화면 결정 (AndroidManifest.xml에서 설정)

우리가 유튜브 앱을 실행하면 가장 먼저 뜨는 화면이 홈 화면이다. 홈 화면에서는 추천 동영상, 인기 동영상 등이 표시된다.

이 홈 화면은 유튜브 앱의 첫 화면으로 설정되어 있다. 이 설정은 유튜브 앱의 AndroidManifest.xml 파일에서 다음과 같이 설정되어 있을 것이다:

<activity android:name=".HomeActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

위 설정에서 HomeActivity가 유튜브 앱의 첫 화면으로 지정된다. 이 파일에서 설정된 대로, 앱을 실행하면 홈 화면(HomeActivity)이 첫 화면으로 뜨게 되는 것이다.

2. 첫 화면의 구성 (onCreate 메서드에서 처리)

유튜브 앱을 켜면 홈 화면이 표시되는데, 이 홈 화면이 구성될 때 필요한 작업들은 모두 HomeActivityonCreate 메서드에서 이루어진다.

예를 들어, HomeActivityonCreate 메서드에서는 다음과 같은 작업을 할 수 있다:

  • 홈 화면 레이아웃을 설정 (setContentView(R.layout.activity_home))
  • 추천 동영상 리스트를 불러와 화면에 표시
  • 상단의 검색 바와 메뉴 아이콘을 초기화

이 모든 작업은 onCreate 메서드에서 처리된다. 따라서, 유튜브 앱의 첫 화면을 띄우기 위해 필요한 설정과 초기화 작업들은 onCreate 메서드 안에서 이루어진다.

요약

  1. 첫 화면을 결정하는 곳: 유튜브 앱이 시작될 때 홈 화면이 첫 화면으로 설정되어 있는지 여부는 AndroidManifest.xml 파일에서 설정된다.

  2. 첫 화면을 구성하는 작업: 홈 화면이 선택되면, 그 화면의 구성 요소들을 초기화하고 준비하는 작업은 해당 액티비티(예: HomeActivity)의 onCreate 메서드에서 처리된다.

유튜브 앱을 실행하면 제일 먼저 홈 화면이 뜨는데, 이 홈 화면이 첫 화면으로 결정된 것은 AndroidManifest.xml에서다. 그리고 홈 화면이 제대로 구성되도록 하기 위해 필요한 작업들은 onCreate 메서드에서 수행된다.

액티비티 코드로 화면 구성하기

액티비티에서 뷰로 화면을 구성하는 방법은 두가지다.
1. 액티비티 코드로 작성하는 방법
장점:

  • 동적인 UI 생성에 유리하다.
  • 런타임에 UI를 쉽게 수정할 수 있다.
  • 복잡한 로직을 UI 생성과 직접 연결할 수 있다.

단점:

  • 코드가 복잡해지고 길어질 수 있다.
  • UI와 로직이 분리되지 않아 유지보수가 어렵다.
  • 시각적으로 UI를 파악하기 어렵다.
  1. 레이아웃 XML 파일로 작성하는 방법
    장점:
  • UI와 로직이 분리되어 유지보수가 용이하다.
  • XML은 직관적이고 구조화되어 있어 UI 구조를 쉽게 파악할 수 있다.
  • Android Studio의 레이아웃 편집기를 사용해 시각적으로 UI를 설계할 수 있다.
  • 다양한 화면 크기와 방향에 대응하기 쉽다.

단점:

  • 매우 동적인 UI 생성에는 제한이 있을 수 있다.

현업 개발자들의 최근 사용 경향

  1. XML 레이아웃 사용

    • 대부분의 현업 개발자들은 여전히 XML을 사용하여 레이아웃을 정의한다.
    • UI/UX 디자이너와의 협업이 용이하다.
    • 코드와 UI의 분리로 인해 유지보수가 쉽다.
  2. 코드로 UI 생성:

    • 동적인 UI 요소나 복잡한 커스텀 뷰를 만들 때 사용된다.
    • 특정 상황에서 XML로는 구현하기 어려운 레이아웃을 만들 때 활용된다.
  3. 하이브리드 접근:

    • 기본 레이아웃은 XML로 정의하고, 동적인 부분은 코드로 추가하는 방식을 많이 사용한다.
  4. Jetpack Compose:

    • 최근에는 Jetpack Compose를 사용하여 선언적으로 UI를 구성하는 방식이 점점 더 인기를 얻고 있다.
    • 이는 코드로 UI를 작성하지만, XML의 장점과 코드의 유연성을 모두 갖는다.

결론적으로, 현업에서는 여전히 XML 레이아웃을 주로 사용하면서 필요에 따라 코드로 UI를 보완하는 방식을 많이 채택하고 있다. 그러나 Jetpack Compose의 등장으로 UI 개발 방식이 점차 변화하고 있다.

액티비티 코드로 작성하는 방법

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 이름 문자열 출력 TextView 생성
        val name = TextView(this).apply {
            typeface = Typeface.DEFAULT_BOLD
            text = "Lake Louise"
        }

        // 이미지 출력 ImageView 생성
        val image = ImageView(this).also {
            it.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.lake_1))
        }

        // 주소 문자열 출력 TextView 생성
        val address = TextView(this).apply {
            typeface = Typeface.DEFAULT_BOLD
            text = "Lake Louise, AB, 캐나다"
        }

        val layout = LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            gravity = Gravity.CENTER

            // LinearLayout 객체에 TextView, ImageView, TextView 객체 추가
            addView(name, WRAP_CONTENT, WRAP_CONTENT)
            addView(image, WRAP_CONTENT, WRAP_CONTENT)
            addView(address, WRAP_CONTENT, WRAP_CONTENT)
        }

        // LinearLayout을 화면에 출력
        setContentView(layout)
    }
}
  • oncreate
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
  1. onCreate 함수란 무엇인가?
    onCreate는 안드로이드에서 액티비티가 처음 생성될 때 호출되는 메서드다. 쉽게 말하면, 앱을 켜면 제일 먼저 실행되는 코드가 여기에 들어간다고 보면 된다.
    안드로이드 액티비티는 생명주기(lifecycle)를 가진다.
    생명주기는 액티비티가 생성되고, 실행되다가 중단되고, 다시 시작되거나 종료되는 일련의 과정이다.
    onCreate는 그 생명주기 중 첫 번째 단계로, 액티비티가 메모리에 로드되면서 실행된다.

  2. override가 무슨 의미인가?
    override는 부모 클래스에서 이미 정의된 함수를 재정의한다는 뜻이다. AppCompatActivity라는 부모 클래스에 이미 onCreate 함수가 정의되어 있는데, 우리가 이 함수를 다시 정의해서 우리가 원하는 로직을 넣는 것이다.

  3. savedInstanceState: Bundle? 이란?
    savedInstanceState는 앱이 임시 중단되었다가 다시 시작될 때(예: 화면 회전 시) 이전 상태를 저장하고 복원하는 역할을 한다. 일단 초보자 단계에서는 이 부분은 넘어가도 괜찮다. 빈 값이 들어올 수도 있기 때문에 ?(null 가능) 표시가 있다.

  4. super.onCreate(savedInstanceState)의 역할은?
    이 부분이 가장 헷갈릴 수 있다. super.onCreate(savedInstanceState)는 부모 클래스의 onCreate 함수를 호출하는 것이다. 부모 클래스에서 기본적으로 설정해 놓은 작업들을 수행하는데, 이 작업들이 중요하기 때문에 반드시 호출해야 한다. 부모 클래스에서 하는 작업에는 기본적인 UI 초기화나 리소스 설정 등이 포함된다. 이 작업을 무시하면 앱이 제대로 동작하지 않을 수 있다. 그래서 항상 onCreate 함수 내에서 제일 먼저 super.onCreate(savedInstanceState)를 호출해 부모 클래스가 해야 할 기본적인 작업을 먼저 처리하게 한다.

  • 그외
    이 부분은 딱히 어렵지는 않다. 하지만 여러 헷갈리는 점에 대해 알아보자.

1. ~View(this)에서 this는 무엇을 가리키는가?

this는 현재 객체를 가리킨다. 이 경우, MainActivity를 의미한다. TextView(this)나 ImageView(this)에서 this는 뷰의 컨텍스트(Context)를 나타내는데, 컨텍스트는 뷰가 어느 액티비티에 속해 있는지, 어떤 리소스에 접근할 수 있는지를 알려준다. this를 사용해 현재 액티비티의 컨텍스트를 전달하는 것이다.

2. - val image = ImageView(this).also {}는 왜 apply가 아니라 also인가?

apply와 also의 차이는 두 가지다:
apply는 주로 객체를 초기화할 때 사용하며, 리턴값으로 해당 객체를 반환한다. 블록 내부에서는 this로 객체를 참조한다.
also는 주로 객체를 사용하면서 추가적인 작업을 수행할 때 사용하며, 리턴값으로 역시 해당 객체를 반환한다. 블록 내부에서는 it으로 객체를 참조한다.
코드에서 ImageView 객체에 대해 특정 작업(이미지 설정)을 수행하고 싶다면 also가 적합하다. 반면 객체를 설정(초기화)하는 경우 apply가 더 적합하다. 이 예제에서 apply를 사용해도 동작은 동일하지만, 더 의미적으로 적절한 것은 also이다.

3. it는 무엇을 의미하는가?

it는 also 블록 안에서 사용할 수 있는 기본 객체 참조이다.
이 경우, it는 ImageView 객체를 가리킨다.
즉, it.setImageDrawable(...)는 ImageView 객체의 setImageDrawable 메서드를 호출하는 것이다.

4. 어떠한 뷰를 생성하고 나면 무조건 LinearLayout에 등록해야 하는가? (addView)

아니다. 뷰를 화면에 표시하려면 어떤 컨테이너에 등록해줘야 하지만, 그 컨테이너가 반드시 LinearLayout일 필요는 없다. RelativeLayout, ConstraintLayout, FrameLayout 등 다양한 레이아웃에 등록할 수 있다.
중요한 점은 뷰가 루트 레이아웃에 포함되어야 화면에 표시된다는 것이다.
루트 레이아웃이 아닌 다른 뷰에 추가해도 된다.

5. 무조건 setContentView를 해야 레이아웃이 화면에 표시되는가?

setContentView는 레이아웃을 화면에 표시하는 핵심 메서드다. 이 메서드는 레이아웃을 설정하고 그 안에 포함된 뷰들이 화면에 나타나도록 한다. XML 레이아웃을 사용할 때도 이 메서드를 통해 XML 파일을 지정한다. 만약 setContentView를 호출하지 않으면, 액티비티는 아무것도 화면에 표시하지 않게 된다.

정리

  • 첫 화면 여부는 AndroidManifest.xml에서 설정된다.
  • this는 현재 액티비티의 컨텍스트를 의미한다.
  • also는 객체를 초기화하지 않고 작업을 수행할 때 적합하다.
  • it은 ImageView 객체를 가리킨다.
  • 뷰는 어떤 레이아웃에든 등록해주어야 하지만, 반드시 LinearLayout일 필요는 없다.
  • setContentView를 해야만 레이아웃이 화면에 표시된다.

레이아웃 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"
    android:gravity="center">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:text="Lake Louise" />
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/lake_1" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:text="Lake Louise, AB, 캐나다" />
</LinearLayout>

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 화면 출력 XML 명시
        setContentView(R.layout.activity_main)
    }
}

activity_main.xml은 안드로이드 앱의 화면을 정의하는 XML 레이아웃 파일이다. 이 파일에서는 화면에 표시할 UI 요소를 배치하고 스타일을 정의하며, 액티비티가 시작될 때 이 파일이 적용되어 사용자에게 보여진다.

주의) activity_main.xml의 위치는 무조건 res의 layout 폴더에 위치해야한다.

총정리

  1. 액티비티(Activity)는 안드로이드 애플리케이션에서 하나의 화면을 담당하는 컴포넌트다.
  2. 뷰(View)는 액티비티 안에 포함된 UI 요소를 의미한다.
  3. 액티비티는 하나 이상의 로 구성되며, 이 뷰들을 배치하고 조정한다.
  4. 는 계층 구조(Tree Structure)로 액티비티 내에서 구성된다.
  5. 액티비티를 하나의 "창문"이라고 생각하면, 는 그 창문 안에 있는 "여러 개의 패널"이나 "위젯"들이다.
  6. 액티비티는 하나의 화면을 나타내며, 앱의 핵심 구성 요소다.
  7. 는 그 화면 안에 표시되는 개별 UI 요소다.
  8. 액티비티는 여러 를 포함하며, 이들을 배치하고 관리한다.
  9. 앱의 첫 화면AndroidManifest.xml 파일에서 설정된다.
  10. 첫 화면의 구성은 액티비티의 onCreate 메서드에서 처리된다.
  11. onCreate는 안드로이드에서 액티비티가 처음 생성될 때 호출되는 메서드다.
  12. super.onCreate(savedInstanceState)는 부모 클래스의 onCreate 메서드를 호출하여 기본적인 UI 초기화 작업을 수행한다.
  13. savedInstanceState는 앱이 임시 중단되었다가 다시 시작될 때 이전 상태를 저장하고 복원하는 역할을 한다.
  14. this는 현재 액티비티의 컨텍스트를 의미한다.
  15. apply는 객체를 초기화할 때 사용되며, 블록 내부에서는 this로 객체를 참조한다.
  16. also는 객체를 사용하면서 추가적인 작업을 수행할 때 사용되며, 블록 내부에서는 it으로 객체를 참조한다.
  17. italso 블록 안에서 사용할 수 있는 기본 객체 참조이다.
  18. 는 어떤 레이아웃에든 등록해주어야 하지만, 반드시 LinearLayout일 필요는 없다.
  19. setContentView는 레이아웃을 화면에 표시하는 핵심 메서드다.
  20. activity_main.xml은 안드로이드 앱의 화면을 정의하는 XML 레이아웃 파일이다.
  21. activity_main.xml의 위치는 무조건 res/layout 폴더에 위치해야 한다.

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글