액티비티(Activity)-뷰(View) 구조는 안드로이드 애플리케이션에서 사용자 인터페이스(UI)를 구성하는 기본적인 설계 패턴이다. 이를 이해하기 쉽게 설명하자면, 안드로이드 앱의 화면을 구성하는 액티비티(Activity)와 뷰(View) 간의 관계를 나타낸 것이다.
액티비티를 하나의 "창문"이라고 생각하면, 뷰는 그 창문 안에 있는 "여러 개의 패널"이나 "위젯"들입니다. 창문(액티비티)이 여러 패널(뷰)을 포함하여 사용자에게 보여주고, 각각의 패널이 자신만의 역할을 수행한다.
Fragment를 사용하면 사용자에게 보여줄 화면이 10개여도 액티비티 1개로 만들수 있다. 그렇지만 학습 초반이므로 액티비티 1개당 화면 1개를 만든다고 이해해도 좋다.
다음은 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 파일을 가리킨다.
시스템 바(상단 상태바, 하단 내비게이션 바) 인셋 처리(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 개발의 최신 트렌드와 권장 사항을 반영하여, 개발자가 처음부터 좋은 관행을 따를 수 있도록 돕기 위함이다.
단어가 비슷해서 혼동될 수 있는 개념이다.
이를 유튜브를 예시로 알아보자.
좋은 접근이다! 유튜브 앱을 예로 들어 설명해 보겠다.
우리가 유튜브 앱을 실행하면 가장 먼저 뜨는 화면이 홈 화면이다. 홈 화면에서는 추천 동영상, 인기 동영상 등이 표시된다.
이 홈 화면은 유튜브 앱의 첫 화면으로 설정되어 있다. 이 설정은 유튜브 앱의 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)이 첫 화면으로 뜨게 되는 것이다.
유튜브 앱을 켜면 홈 화면이 표시되는데, 이 홈 화면이 구성될 때 필요한 작업들은 모두 HomeActivity
의 onCreate
메서드에서 이루어진다.
예를 들어, HomeActivity
의 onCreate
메서드에서는 다음과 같은 작업을 할 수 있다:
setContentView(R.layout.activity_home)
)이 모든 작업은 onCreate
메서드에서 처리된다. 따라서, 유튜브 앱의 첫 화면을 띄우기 위해 필요한 설정과 초기화 작업들은 onCreate
메서드 안에서 이루어진다.
첫 화면을 결정하는 곳: 유튜브 앱이 시작될 때 홈 화면이 첫 화면으로 설정되어 있는지 여부는 AndroidManifest.xml
파일에서 설정된다.
첫 화면을 구성하는 작업: 홈 화면이 선택되면, 그 화면의 구성 요소들을 초기화하고 준비하는 작업은 해당 액티비티(예: HomeActivity
)의 onCreate
메서드에서 처리된다.
유튜브 앱을 실행하면 제일 먼저 홈 화면이 뜨는데, 이 홈 화면이 첫 화면으로 결정된 것은 AndroidManifest.xml
에서다. 그리고 홈 화면이 제대로 구성되도록 하기 위해 필요한 작업들은 onCreate
메서드에서 수행된다.
액티비티에서 뷰로 화면을 구성하는 방법은 두가지다.
1. 액티비티 코드로 작성하는 방법
장점:
단점:
단점:
XML 레이아웃 사용
코드로 UI 생성:
하이브리드 접근:
Jetpack Compose:
결론적으로, 현업에서는 여전히 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)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onCreate 함수란 무엇인가?
onCreate는 안드로이드에서 액티비티가 처음 생성될 때 호출되는 메서드다. 쉽게 말하면, 앱을 켜면 제일 먼저 실행되는 코드가 여기에 들어간다고 보면 된다.
안드로이드 액티비티는 생명주기(lifecycle)를 가진다.
생명주기는 액티비티가 생성되고, 실행되다가 중단되고, 다시 시작되거나 종료되는 일련의 과정이다.
onCreate는 그 생명주기 중 첫 번째 단계로, 액티비티가 메모리에 로드되면서 실행된다.
override가 무슨 의미인가?
override는 부모 클래스에서 이미 정의된 함수를 재정의한다는 뜻이다. AppCompatActivity라는 부모 클래스에 이미 onCreate 함수가 정의되어 있는데, 우리가 이 함수를 다시 정의해서 우리가 원하는 로직을 넣는 것이다.
savedInstanceState: Bundle? 이란?
savedInstanceState는 앱이 임시 중단되었다가 다시 시작될 때(예: 화면 회전 시) 이전 상태를 저장하고 복원하는 역할을 한다. 일단 초보자 단계에서는 이 부분은 넘어가도 괜찮다. 빈 값이 들어올 수도 있기 때문에 ?(null 가능) 표시가 있다.
super.onCreate(savedInstanceState)의 역할은?
이 부분이 가장 헷갈릴 수 있다. super.onCreate(savedInstanceState)는 부모 클래스의 onCreate 함수를 호출하는 것이다. 부모 클래스에서 기본적으로 설정해 놓은 작업들을 수행하는데, 이 작업들이 중요하기 때문에 반드시 호출해야 한다. 부모 클래스에서 하는 작업에는 기본적인 UI 초기화나 리소스 설정 등이 포함된다. 이 작업을 무시하면 앱이 제대로 동작하지 않을 수 있다. 그래서 항상 onCreate 함수 내에서 제일 먼저 super.onCreate(savedInstanceState)를 호출해 부모 클래스가 해야 할 기본적인 작업을 먼저 처리하게 한다.
this는 현재 객체를 가리킨다. 이 경우, MainActivity를 의미한다. TextView(this)나 ImageView(this)에서 this는 뷰의 컨텍스트(Context)를 나타내는데, 컨텍스트는 뷰가 어느 액티비티에 속해 있는지, 어떤 리소스에 접근할 수 있는지를 알려준다. this를 사용해 현재 액티비티의 컨텍스트를 전달하는 것이다.
apply와 also의 차이는 두 가지다:
apply는 주로 객체를 초기화할 때 사용하며, 리턴값으로 해당 객체를 반환한다. 블록 내부에서는 this로 객체를 참조한다.
also는 주로 객체를 사용하면서 추가적인 작업을 수행할 때 사용하며, 리턴값으로 역시 해당 객체를 반환한다. 블록 내부에서는 it으로 객체를 참조한다.
코드에서 ImageView 객체에 대해 특정 작업(이미지 설정)을 수행하고 싶다면 also가 적합하다. 반면 객체를 설정(초기화)하는 경우 apply가 더 적합하다. 이 예제에서 apply를 사용해도 동작은 동일하지만, 더 의미적으로 적절한 것은 also이다.
it는 also 블록 안에서 사용할 수 있는 기본 객체 참조이다.
이 경우, it는 ImageView 객체를 가리킨다.
즉, it.setImageDrawable(...)는 ImageView 객체의 setImageDrawable 메서드를 호출하는 것이다.
아니다. 뷰를 화면에 표시하려면 어떤 컨테이너에 등록해줘야 하지만, 그 컨테이너가 반드시 LinearLayout일 필요는 없다. RelativeLayout, ConstraintLayout, FrameLayout 등 다양한 레이아웃에 등록할 수 있다.
중요한 점은 뷰가 루트 레이아웃에 포함되어야 화면에 표시된다는 것이다.
루트 레이아웃이 아닌 다른 뷰에 추가해도 된다.
setContentView는 레이아웃을 화면에 표시하는 핵심 메서드다. 이 메서드는 레이아웃을 설정하고 그 안에 포함된 뷰들이 화면에 나타나도록 한다. XML 레이아웃을 사용할 때도 이 메서드를 통해 XML 파일을 지정한다. 만약 setContentView를 호출하지 않으면, 액티비티는 아무것도 화면에 표시하지 않게 된다.
<?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 폴더에 위치해야한다.
AndroidManifest.xml
파일에서 설정된다.onCreate
메서드에서 처리된다.onCreate
메서드를 호출하여 기본적인 UI 초기화 작업을 수행한다.this
로 객체를 참조한다.it
으로 객체를 참조한다.also
블록 안에서 사용할 수 있는 기본 객체 참조이다.res/layout
폴더에 위치해야 한다.