본문 바로가기
Project/Android

[Android] Activity와 ViewBinding으로 화면 전환하기

by eoieiie 2024. 4. 1.

Activity란?


Activity는 안드로이드의 4대 컴포넌트(구성요소)로서 앱의 화면을 담당합니다. 우리가 앱을 켰을 때 보이는 홈 화면이 보통 "메인 액티비티"라고 불립니다. 결제 화면은 "결재 액티비티"라고 할 수 있는 거죠. 일반적으로 액티비티는 웹 페이지와 같이 화면을 채우는 UI창이지만, 필요에 따라 다른 창 위에 작게 띄울 수도 있습니다. 메인 화면에서 결제창으로 넘어갈 때 다른 액티비티를 호출하여 앱과 사용자의 상호작용을 만드는 거죠. 

 

액티비티는 Kotlin클래스 파일과 레이아웃 XML파일로 구성되어 있습니다. XML파일은 우리가 한번 만든 적이 있죠? XML은 레이아웃을, 클래스 파일은 동작을 구현합니다. 

 

 

[Android] [팁 계산기] 5. 나머지 레이아웃 추가

자 이제 레이아웃의 구현은 거의 다 끝났습니다. switch, Button, TextView 위젯을 사용하여 세부적인 기능들을 더 추가해보도록 하겠습니다. 팁을 반올림하기 위한 Switch 추가 우선 XML을 먼저 보도록

yellowsickmedicine.tistory.com

 

Activity의 구성


안드로이드 스튜디오에서 새 파일을 만들 때 클래스 파일과 XML파일은 자동으로 생성됩니다. 클래스 파일은 일반적으로 MainActivity.kt, XML파일은 res/layout 경로에 activity_main.xml으로 저장되어 있습니다. 

 

좀 전에 액티비티는 두 파일의 연결로 이루어진다고 했었는데, 그 과정은 다음과 같이 진행됩니다:

 

  1. onCreate() 메서드로 액티비티를 생성
  2. setContentView()의 파라미터로 XML파일을 넣어 액티비티의 레이아웃을 정의
  3. AndroidManifst.xml 파일에서
  4. <application> 태그 안에 생성한 액티비티를 등록

 

사실 위 과정은 안드로이드 스튜디오에서 자동으로 이루어지긴 합니다. 만약 수동으로 클래스 파일이나 레이아웃 파일을 만들었다면 직접 저렇게 연결해야 하니 알아두시면 좋아요!

 

View Binding


XML과 Kotlin클래스 파일이 어떻게 연결되고 구성되는지 알아봤으니 실제로 뷰와 클래스 코드가 상호작용하는 원리를 알아봅시다. 이를 위해 알아두어야 할 2가지 키워드가 있습니다. 

 

findViewById

예전에는 액티비티에서 텍스트뷰의 값을 변경하거나 뭔가 작업을 하려면 클래스 파일 안에서 텍스트뷰의 아이디를 참조하여 변수를 선언하고, xml의 뷰와 변수를 연결시켜야 했었습니다. 그래서 텍스트뷰나 다른 뷰들의 수가 많아질수록 클래스 파일의 코드 역시 길어집니다. 

 

viewBinding

뷰바인딩은 위 단점을 극복한 기능입니다. ID에 따른 변수 선언을 하지 않고, ID를 바로 변수처럼 사용할 수 있습니다. ID를 직접 안 적어도 되고, 코드의 길이가 상대적으로 줄어들어서 정확성과 속도 면에서 많은 개선이 이루어졌다고 볼 수 있습니다. 사용법은 다음과 같습니다.

 

  1. build.gradle파일에서 viewBinding을 허용 (보통 이 단계 이후 Android Studio상단에 Sync now 팝업이 뜨는데, 눌러주시면 됩니다.
  2. 메인 액티비티 파일에 Binding 인스턴스 생성
  3. SetContentView에 XML파일 대신 Binding루트 넘기기

이렇게 하면 모든 View를 클래스 파일에서 binding. {id}로 참조할 수 있게 된 겁니다!

 

//build.gradle (module: 앱이름)

android {
    ...
    buildFeatures {
        viewBinding = true
    }
}

 

// MainActivity.kt 에서 원래 내용을 다음과 같이 수정해주면 됩니다!

class MainActivity : AppCompatActivity() {
  private lateinit var binding: ActivityMainBinding

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

//import com.fsos.intent_changescreen.databinding.ActivityMainBinding 이 부분이 없으면 추가해야 합니다

 

요약하자면 뷰 바인딩은 화면에 표시되는 UI요소들을 코드에서 더 효율적으로 다룰 수 있도록 돕는 기능이라고 생각하시면 되겠습니다. 뷰 바인딩에 대한 실습은 [팁 계산기] 포스팅에서 추후 다뤄보고 링크를 남기도록 할게요. 아래에서도 간략히 실습한 내용을 다루니 이해가 안 되셔도 천천히 따라오시면 됩니다. 

 

Intent


 

인텐트는 애플리케이션 내의 다양한 컴포넌트 간의 통신을 위한 메커니즘입니다. 화면 간의 이동이나 서비스의 시작 등에 사용됩니다. 예를 들면 어떤 버튼을 클릭하면 현재 화면에서 다른 화면으로 전환되는 로직의 구현은 인텐트를 통해 이루어집니다. 크게 두 가지 방법이 있습니다.

 

명시적 인텐트

목적지를 직접 정하는 방법입니다. 현재 액티비티에서 다른 액티비티로 화면을 전환하는 경우에, 백그라운드에서 실행되는 서비스를 시작할 때 사용됩니다. 

 

암시적 인텐트

직접적인 목적지의 지정 없이, 어떠한 작업을 수행할 건지에 대한 내용만 선언하는 방법입니다. 보통 목적지의 지정은 필터링으로 아니면 사용자에게 선택을 맡기는 방식으로 진행됩니다. 예를 들어 웹 주소를 클릭했을 때 웹 브라우저를 열 때, 연라거에 연락처를 추가할 때, 사진을 찍을 때 사용됩니다. 

 

요약하자면 암시적 인텐트는 앱이나 액티비티 간의 유용한 통신을 가능하게 하며, 안드로이드 시스템이 적절한 앱을 선택하여 실행하므로 다른 앱과의 결합을 쉽게 합니다. 반면에 명시적 인텐트는 명확하게 대상을 지정하여 실행되므로, 주로 앱 내부의 구성 요소 간 통신에 사용됩니다. 

 

인텐트의 사용법

//maiactivity.kt


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

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

        binding.button.setOnClickListener {
            val intent = Intent(this, ViewNext::class.java)
            startActivity(intent)
        }
    }
}

 

위 코드는 ViewBinding을 사용하여 바인딩된 뷰의 버튼을 클릭했을 때 다음 액티비티로 이동하는 동작을 정의합니다. 중요한 부분이니 줄별로 내용을 설명해 보겠습니다. 

 

class MainActivity : AppCompatActivity() {:

MainActivity 클래스를 정의하는 부분입니다. 

 

private lateinit var binding: ActivityMainBinding:

private로 변수가 해당 클래스 안에서만 사용됨을 선언하고, var키워드로 변수를 사용하며 이름은 binding으로 해 줍니다. 이 변수는 나중에 ViewBinding객체를 참조하는 데 사용됩니다. ActivityMainBinding은 ViewBinding클래스의 이름으로 보통 xml파일의 이름을 스네이크 케이스로 변환하여 마지막에 Binding단어를 붙여서 만듭니다. (예: activity_main.xml -> ActivityMainBinding)

 

override fun onCreate(savedInstanceState: Bundle?) {}:

메서드를 재정의(override)하는 부분입니다. fun은 function의 줄임말이고, 재정의되는 메서드는 onCreate 메서드입니다. Activity의 초기화를 담당한다고 생각하시면 되겠습니다. savedInstanceState: Bundle? 은 Activity의 이전 상태를 저장합니다. 예를 들어 화면의 회전이나 앱의 재시작에서 이전 상태를 복원하는 데 사용됩니다. {} 안에는 메서드의 실제 내용이 들어갑니다. 

 

super.onCreate(savedInstanceState):

상위 클래스인 AppCompatActivity의 onCreate() 메서드를 호출합니다. 아까 말했던 이전 상태의 복원을 구현했다고 보면 됩니다.

 

binding = ActivityMainBinding.inflate(layoutInflater):

바인딩이라는 변수를 선언하고, activity_main이라는 xml파일의 뷰 요소들을 그 안에 할당합니다.

 

setContentView(binding.root):

액티비티의 화면에 어떤 내용을 표시할지를 결정합니다. 매개변수로 좀 전에 선언했던 binding의 root라는 속성을 전달한다는 뜻입니다. root는 바인딩된 xml파일의 루트 뷰를 나타냅니다.  (루트 뷰: 레이아웃 파일에서 가장 상위에 위치한 뷰. 즉 다른 모든 뷰들의 부모라고 보시면 됩니다! 부모 요소를 참조하면 내용 전체를 참조하는 게 되니까요.)

 

binding.button.setOnClickListener {... }:

변수 binding의 button이라는 속성을 가져옵니다. 클릭 시 어떤 이벤트를 처리할 건지를 정의하는 부분이며, setOnClickListener {... }가 이벤트의 내용을 결정합니다. 

 

val intent = Intent(this, ViewNext::class.java)

val은 변수를 선언하는 키워드로, 할당한 후 값을 변경할 수 없는 변수를 사용할 때 사용됩니다. 변수의 이름은 intent로, 할당할 내용은 Intent클래스이며 2가지 매개변수를 받습니다. this는 현재 액티비티 나타내고, viewnext class java는 ViewNext액티비티의 클래스를 자바 객체로 변환하고 시작할 액티비티와 클래스를 지정합니다. 

 

startActivity(intent)

주어진 인텐트를 활용하여 새로운 액티비티를 시작하는 메서드입니다. 

 

여기서 잠깐 뷰 바인딩과 인텐트의 차이점에 대해서 짚고 넘어갈게요. 

버튼을 클릭했을 때 다른 앱이 열린다고 가정합시다. 

이때 버튼의 구성 또는 모양, 클릭 시의 이벤트 처리는 뷰 바인딩을 통해 처리되고, 클릭 시 다른 앱으로  이동하는 화면의 전환은 인텐트(암시적)를 통해 처리됩니다! 이제 조금 이해가 되시나요? 한번 간단하게 구현해 봅시다. 

 

우선 새 파일을 만들어줍시다. 저희가 구현할 로직은 "버튼을 누르면 다른 레이아웃으로 전환"입니다. 

새 프로젝트를 열어줍니다. 작성할 내용은 다음과 같습니다:

 

  1. 2개의 xml과 2개의 class(총 2개의 액티비티)
  2. 서로를 연결할 코드

 

 

MainActivity


//첫 화면의 XML, activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">


    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/press"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.5" />


</androidx.constraintlayout.widget.ConstraintLayout>
//첫 화면의 클래스파일, MainActivity.kt

package com.fsos.intent_changescreen

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.fsos.intent_changescreen.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

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

        binding.button.setOnClickListener {
            val intent = Intent(this, ViewNext::class.java)
            startActivity(intent)
        }
    }
}

 

 

ViewNext


//전환되는 화면의 XMl, nextview.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/quiz"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".ViewNext">


    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/jjan"
        android:textSize="100sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.5" />

</androidx.constraintlayout.widget.ConstraintLayout>
//전환되는 화면의 클래스파일, ViewNext.kt

package com.fsos.intent_changescreen

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.fsos.intent_changescreen.databinding.NextviewBinding

class ViewNext : AppCompatActivity() {

    private lateinit var binding: NextviewBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = NextviewBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}

 

AndroidManifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Intent_changeScreen"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- ViewNext 액티비티 추가 -->
        <activity
            android:name=".ViewNext"
            android:exported="true">
        </activity>
    </application>

</manifest>

 

여기서 중요하게 봐야 할 게  몇 가지 있습니다. 이거 고치느라 하루 다 잡아먹었습니다. 

 

  1. 클래스의 이름:
    클래스를 새로 만들 때, xml파일의 이름을 기반으로 바인딩을 하게 되는데요, 이때 이름을 지정하는 규칙이 존재합니다. 만약 파일 이름이 스네이크 케이스라면 캐멀케이스로 변환되어야 합니다. 즉  '_' 기호를 지우고  각 단어의 첫 글자를 대문자로 변환하여 클래스 이름을 생성합니다. 
  2. Manifeset 파일 정의 여부:
    추가되는 액티비티들은 모두 Manifest파일 안에서 정의되어야 합니다. 
  3. ViewBinding의 정의 여부:
    viewBinding은 gradle 파일 안에서 선언되고 기능이 활성화되어야 합니다. 
  4. <intent-filter>는 하나만 존재:
    <intent-filter>는 앱의 시작점, 즉 사용자가 앱을 켰을 때 제일 먼저 보이는 화면을 의미합니다. 당연히 하나만 존재하겠죠?

자 이제 저희는 두 액티비티를 만들고, intent와 viewBinding을 활용해 연결해 주었습니다. 실행을 해 볼까요?

 

 

 

아직은 많이 어렵죠? 저도 그렇습니다. 액티비티 간 전환의 구현에만 하루를 다 썼네요. 그래도 이제 조금씩 앱의 구성과, 원하는 내용을 만드는 것에 대한 감이 잡혀갑니다. 이 포스팅의 끝에는 엄청난 결과물이 있을 거라 확신합니다. 읽어주셔서 감사하고, 도움이 되었으면 좋겠습니다. 

 

댓글