1. 화면을 구성하는 방법
액티비티-뷰 구조
안드로이드 앱의 기본 구조는 컴포넌트를 기반으로 합니다. 즉, 안드로이드 앱은 액티비티, 서비스, 브로드캐스트 리시버, 콘텐츠 프로바이더와 같은 컴포넌트를 적절하게 조합해서 만듭니다. 그런데 이 중에서 화면을 출력하는 컴포넌트는 액티비티뿐입니다. 결국 앱에서 화면을 출력하고 싶다면 액티비티를 만들어야 하고 이렇게 만든 액티비티에서 출력한 내용이 기기의 화면에 보이는 것입니다.
액티비티는 화면을 출력하는 컴포넌트일 뿐이지 그 자체가 화면은 아닙니다. 따라서 액티비티에서 적절한 화면을 구성해야 합니다. 별도로 화면 구성을 하지 않고 단순히 액티비티만 실행하면 텅 빈 흰색 화면만 보입니다. 만약 화면에 내용을 표시하려면 뷰 클래스를 이용하여 구성해야 합니다.
예를 들어 화면에 문자열을 출력하려면 TextView 클래스를 이용하고, 이미지를 출력하려면 ImageView 클래스를 이용합니다. 이런 TextView, ImageView 등의 클래스를 뷰 클래스라고 합니다. 결국 액티비티가 실행되면서 뷰 클래스를 이용해 화면을 구성하고 이를 기기의 화면에 출력하는 구조입니다.
액티비티 코드로 화면 구성하기
액티비티에서 뷰로 화면을 구성하는 방법은 2가지 입니다. 액티비티 코드로 작성하는 방법과 레이아웃 XML 파일로 작성하는 방법입니다. 액티비티 코드로 작성하는 방법은 화면을 구성하는 뷰 클래스를 액티비티 코드에서 직접 생성합니다.
package com.example.myfirstapplication
import android.graphics.Typeface
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.core.content.ContextCompat
class MainActivity : ComponentActivity() {
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.run_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)
}
}
레이아웃 XML로 화면 구성하기
이번에는 액티비티의 화면 구성을 레이아웃 XML 파일로 구현해 보겠습니다. 화면을 구성하는데 필요한 TextView, ImageView 등을 XML의 태그로 명시해 화면을 구성하는 방법입니다.
<activity_main.xml>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
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/run_1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="Lake Louise, AB, 캐나다"/>
</LinearLayout>
<MainActivity.kt>
package com.example.myfirstapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 화면 출력 XML 명시
setContentView(R.layout.activity_main)
}
}
액티비티 코드와 XML 파일 모두 화면 구성이 가능하지만 효율성을 고려한다면 XML 파일로 화면을 구현하는 것이 더 좋습니다. 액티비티에는 작성해야 할 코드가 많은데 화면을 구현하는 코드까지 길어지면 유지보수 면에서 부담스럽겠죠.
따라서 화면 구현은 XML 파일로 분리하고 액티비티에서는 네트워킹, 데이터 핸들링, 사용자 이벤트 처리 등의 코드만 작성하는 것이 더 효율적입니다. 특히 데이터 서비스를 주 목적으로 하는 앱이라면 대부분 화면 구현을 XML 파일에 작성합니다.
2. 뷰 클래스
안드로이드에서 화면을 만들어 표시하는 컴포넌트는 액티비티이며 액티비티가 실행되면서 뷰 클래스를 이용해 화면을 구성합니다. 안드로이드는 TextView, ImageView, EditText, Button, Spinner, ListView 등 많은 뷰 클래스를 제공합니다.
뷰 객체의 계층 구조
액티비티 화면을 구성할 때 사용하는 클래스는 모두 View의 하위 클래스입니다. 그래서 화면 구성과 관련한 클래스를 통칭하여 뷰 클래스라고 합니다. 뷰 클래스의 구조를 다이어그램으로 나타내면 다음과 같습니다.
- View
- 모든 뷰 클래스의 최상위 클래스입니다. 액티비티는 View의 서브 클래스만 화면에 출력합니다.
- ViewGroup
- View의 하위 클래스지만 자체 UI는 없어서 화면에 출력해도 아무것도 나오지 않습니다. 다른 뷰 여러 개를 묶어서 제어할 목적으로 사용합니다. 일종의 그릇 역할을 하는 클래스이며 실제로는 ViewGroup의 서브 클래스인 레이아웃 클래스를 사용합니다.
- TextView
- 특정 UI를 출력할 목적으로 사용하는 클래스, 문자열을 출력하는 뷰입니다. TextView 이외에도 다양한 클래스가 있습니다.
레이아웃 XML의 뷰를 코드에서 사용하기
화면 구성을 레이아웃 XML 파일에 작성하고 액티비티에서 setContentView() 함수로 XML 파일을 지정하면 화면을 출력합니다. 예를 들어 다음과 같이 레이아웃 XML을 작성했다면 화면에 "hello" 라는 문자열을 출력합니다. XML 태그로 입력한 TextView 객체가 생성되고 그 객체의 내용이 화면에 출력됩니다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello" />
그런데 때로는 이렇게 XML로 선언한 객체를 코드에서 사용해야 할 때가 있습니다. 여기서 문제는 우리가 직접 생성한 객체가 아니므로 이름이 없어서 지칭할 수가 없다는 것입니다. 결국 방법은 각 객체를 어떻게 부를 것인지 식별자를 부여하고 그 식별자로 객체를 얻어 와야 합니다. 이때 사용하는 속성이 id입니다.
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello" />
id 속성은 android:id="@+id/text1" 형태로 추가합니다. 위 코드에서 text1이 id값입니다. 이 값은 식별자로 이용할 것이므로 앱에서 유일해야 합니다. 이처럼 XML에 id 속성을 추가하면 자동으로 R.java 파일에 상수 변수로 추가됩니다.
id 속성값은 "@+id/text1" 형태로 추가하는데 XML 속성값이 @로 시작하면 R.java 파일을 의미합니다. 따라서 이 표현식은 R.java 파일에 text1이라는 상수 변수를 추가하라는 의미입니다.
이제 코드에서 R.java 파일의 상수 변수로 객체를 얻을 수 있습니다. 이때 findViewById() 함수를 이용합니다.
// XML 화면 출력
setContentView(R.layout.activity_main)
// id값으로 뷰 객체 획득
val textView1: TextView = findViewById(R.id.text1)
setContentView()는 액티비티의 화면을 출력하는 함수입니다. 이 함수를 호출하는 것만으로도 XML의 내용이 화면에 출력됩니다. 화면에 뷰의 내용이 나왔다는 것은 뷰 객체가 생성되었다는 것을 의미합니다. 이렇게 생성된 뷰 객체를 findViewById(R.id.text1) 처럼 가져오면 됩니다.
findViewById() 함수로 얻은 뷰 객체의 타입을 제네릭으로 명시해도 됩니다.
// XML 화면 출력
setContentView(R.layout.activity_main)
// id값으로 뷰 객체 획득
val textView1 = findViewById<TextView>(R.id.text1)
레이아웃 XML에 구현한 대로 뷰가 화면에 나오는데 굳이 코드에서 그 객체를 가져와야하는가 하는 의문이 들 수 있습니다. 안드로이드 앱을 만들 때 보통 화면을 보여주고 끝나지 않습니다. 화면에 보여주는 텍스트를 바꿔줘야할 수도 있고 특정 뷰 객체에 이벤트를 추가해야 할 수도 있습니다. 이런 경우 코드에서 뷰 객체를 가져와야 하기 때문에 위와 같은 코드가 필요한 것입니다.
뷰의 크기를 지정하는 방법
뷰가 화면에 나올 때 어떤 크기로 보여야 하는지는 필수 정보이며 이 크기를 설정하는 속성은 layout_width, layout_height 입니다.
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello" />
크기를 나타내는 속성값에는 다음 3가지 중 하나를 선언합니다.
- 수치
- match_parent
- wrap_content
가로세로 크기를 100px처럼 수치로 지정할 수 있습니다. 이때 단위는 생략할 수 없으며 px, dp 등의 단위를 사용합니다. 또는 match_parent나 wrap_content 처럼 특정한 단어로 지정할 수 있습니다. match_parent는 부모의 크기 전체를 뜻합니다. 앞에서 살펴보았듯이 뷰 객체는 계층 구조로 이용하므로 어떤 뷰의 크기가 match_parent이면 자신보다 상위 계층의 크기를 뜻합니다.
wrap_content는 자신의 콘텐츠를 화면에 출력할 수 있는 적절한 크기를 의미합니다. 예를 들어 TextView에 "hello"라는 문자열을 대입하고 크기를 wrap_content로 지정했다면 이 문자열이 출력될 정도의 크기로 설정됩니다. 만약 문자열이 길어지거나 글꼴이 커지면 뷰 크기도 따라서 커집니다.
뷰의 간격 설정
뷰의 간격은 margin과 padding속성으로 설정합니다. 물론 기본값이 있으며 이를 바꾸고 싶을 때 사용합니다. margin 속성을 뷰와 뷰 사이의 간격이며 padding 속성은 뷰의 콘텐츠와 테두리 사이의 간격입니다.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BUTTON1"
android:backgroundTint="#0000ff"
android:padding="30dp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BUTTON2"
android:backgroundTint="#ff0000"
android:paddingBottom="50dp"
android:layout_marginLeft="50dp" />
</LinearLayout>
뷰의 표시 여부 설정
visibility 속성은 뷰가 화면에 출력되어야 하는지를 설정합니다. 값을 visible, invisible, gone 으로 설정하며 기본값은 visible입니다.
invisible과 gone은 모두 화면에 뷰가 보이지 않게 하지만 자리를 차지하는지 여부에 따라 차이가 있습니다. invisible은 뷰가 화면에 보이지 않지만 자리는 차지하며 gone으로 설정하면 자리조차 차지하지 않습니다.
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BUTTON2"
android:visibility="invisible" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BUTTON2"
android:visibility="gone" />
3. 뷰 바인딩
뷰 바인딩(view binding)은 레이아웃 XML 파일에 선언한 뷰 객체를 코드에서 쉽게 이용하는 방법입니다. 안드로이드는 UI를 구성할 때 대부분 레이아웃 XML 파일을 이용합니다. 레이아웃 XML 파일에 등록한 뷰는 findViewById() 함수로 얻어서 사용해야 합니다. 그런데 이 작업은 꽤 귀찮습니다. 한 화면을 구성하는 데만도 많은 뷰가 필요하고, 또 뷰는 대부부 코드에서 이용합니다. 따라서 코드에서 뷰 객체를 선언하고 모두 findViewById() 함수로 하나하나 가져와야 합니다.
그래서 무의미한 반복 작업을 싫어하는 개발자들은 다음과 같이 생각했습니다.
"액티비티에서 findViewById() 함수를 이용하지 않고 레이아웃 XML 파일에 등록된 뷰 객체를 쉽게 사용할 수는 없을까?
이전부터 많은 개발자가 같은 고민을 했고 이러한 기능을 지원하는 butterknife라는 라이브러리도 등장했습니다. 그런데 이제 butterknife 같은 라이브러리의 도움을 받지 않고서도 코드에서 레이아웃 XML에 선언한 뷰를 쉽게 이용할 수 있습니다. 더욱이 훨씬 간결한 코드로 말이죠.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/visibleBtn"
android:layout_width="match_content"
android:layout_height="wrap_content"
android:text="visible" />
<Button
android:id="@+id/targetView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hello world"
android:background="#FF0000"
android:textColor="#FFFFFF" />
<Button
android:id="@+id/invisibleBtn"
android:layout_width="match_content"
android:layout_height="wrap_content"
android:text="invisible" />
</LinearLayout>
이처럼 작성한 레이아웃 XML 파일이 있다고 가정하겠습니다. 이곳에 선언한 뷰 3개를 코드에서 id값으로 얻어서 사용할 수도 있습니다. 그런데 뷰 바인딩 기법을 이용하면 코드에서 훨씬 더 간편하게 뷰 객체를 이용할 수 있습니다.
우선 뷰 바인딩을 사용하려면 그레들 파일에 다음처럼 선언해야 합니다.
android {
...
buildFeatures {
viewBinding = true
}
}
이렇게 하면 레이아웃 XML 파일에 등록된 뷰 객체를 포함하는 클래스가 자동으로 만들어집니다. 즉, 직접 코드에서 뷰를 선언하고 findViewById() 함수를 호출하지 않아도 이를 구현한 클래스가 자동으로 만들어지므로 이 클래스를 이용해 뷰를 사용하기만 하면 됩니다.
자동으로 만들어지는 클래스의 이름은 레이아웃 XML 파일명을 따릅니다. 첫 글자를 대문자로 하고 밑줄(_)은 빼고 뒤에 오는 단어를 대문자로 만든 후 'Binding'을 추가합니다. 예를 들어 다음과 같습니다.
- activity_main.xml → ActivityMainBinding
- item_main.xml → ItemMainBinding
자동으로 만들어진 클래스의 inflate() 함수를 호출하면 바인딩 객체를 얻을 수 있습니다. 이때 인자로 layoutInflater를 전달합니다. 그리고 바인딩 객체의 root 프로퍼티에는 XML의 루트 태그 객체가 자동으로 등록되므로 액티비티 화면 출력은 setContentView() 함수에 binding.root 를 전달하면 됩니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: BUndle?) {
super.onCreate(savedInstanceState)
// 바인딩 객체 획득
val binding = ActivityMainBinding.inflate(layoutInflater)
// 액티비티 화면 출력
setContentView(binding.root)
// 뷰 객체 이용
binding.visibleBtn.setOnClickListener {
binding.targetView.visibility = View.VISIBLE
}
binding.invisibleBtn.setOnClickListener {
binding.targetView.visibility = View.INVISIBLE
}
}
}
바인딩 객체에 등록된 뷰 객체명은 XML 파일에 등록한 뷰의 id값을 따릅니다.
그런데 build.gradle 파일에 뷰 바인딩을 이용하겠다고 선언하면 레이아웃 XML 하나당 바인딩 클래스가 자동으로 만들어지는데, 이때 어떤 레이아웃 XML 파일은 바인딩 클래스로 만들 필요가 없을 수도 있습니다. 이때는 XML 파일의 루트 태그에 tools:viewBindingIgnore="true" 라는 속성을 추가합니다. 이 속성을 추가하면 해당 XML 파일을 위한 바인딩 클래스를 만들지 않습니다.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
...
tools:viewBindingIgnore="true">
4. 카카오톡 비밀번호 확인 화면 만들기
먼저 새로운 모듈을 만듭니다. 새로운 앱을 만들 때 새 프로젝트로 시작해도 되지만, 똑같은 환경이라면 한 프로젝트에 새로운 모듈을 만들어 시작할 수도 있습니다. 안드로이드 스튜디오에서 모듈은 곧 앱입니다. 즉, 새로운 모듈을 만든다는 것은 새로운 앱을 만드는 것과 같습니다.
- 안드로이드 스튜디오를 실행하고 프로젝트를 연 상태에서 [File > New > New Module] 메뉴를 선택합니다.
- 왼쪽 템플릿 목록에서 Phone & Tablet 을 선택하고 상세 정보를 입력합니다.
- 액티비티를 추가하는 창이 나오면 [Empty Views Activity] 를 선택한 후 <Next>를 클릭합니다.
- 마지막으로 <Finish> 를 클릭해 모듈 생성을 완료합니다.
화면에 출력할 문자열을 리소스로 등록합니다. res/values/strings.xml 파일을 열어 다음처럼 문자열을 추가합니다.
<resources>
<string name="app_name">My Application</string>
<string name="main_desc">
회원님의 소중한 정보 보호를 위해, 카카오계정의 현재 비밀번호를 확인해 주세요.
</string>
<string name="password_txt">
비밀번호가 기억나지 않으세요?
</string>
</resources>
앱의 화면을 구성하기 위해 activity_main.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:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/main_desc"
android:textSize="17dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="kkang104@gmail.com"
android:textColor="#CFCFCE"
android:layout_marginTop="10dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#D4D4D3"
android:layout_marginTop="10dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="비밀번호"
android:inputType="textPassword" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_txt"
android:layout_marginTop="10dp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="확인"
android:layout_marginTop="16dp" />
</LinearLayout>
MainActivity.kt 에서 레이아웃 xml 을 불러와 화면에 출력합니다.
package com.example.myapplication
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 화면 출력 XML 명시
setContentView(R.layout.activity_main)
}
}
안드로이드 스튜디오에서 <Run> 버튼을 클릭해 앱을 실행하면 아래와 같은 앱 화면이 출력됩니다.
'Android' 카테고리의 다른 글
[깡쌤의 안드로이드 프로그래밍 with 코틀린][3. 앱의 기본 기능 구현하기] 8. 사용자 이벤트 처리하기 (0) | 2024.12.25 |
---|---|
[깡쌤의 안드로이드 프로그래밍 with 코틀린][3. 앱의 기본 기능 구현하기] 7. 뷰를 배치하는 레이아웃 (0) | 2024.12.25 |
[깡쌤의 안드로이드 프로그래밍 with 코틀린][2. 코틀린 이해하기] 5. 코틀린의 유용한 기법 (0) | 2024.12.25 |
[깡쌤의 안드로이드 프로그래밍 with 코틀린][2. 코틀린 이해하기] 4. 코틀린 객체지향 프로그래밍 (0) | 2024.12.25 |
[깡쌤의 안드로이드 프로그래밍 with 코틀린][2. 코틀린 이해하기] 3. 코틀린 시작하기 (0) | 2024.12.25 |