해당 내용은 패스트캠퍼스 [35개 프로젝트로 배우는 Android 앱 개발 feat. Jetpack Compose]의 강의 내용을 기반으로 작성한 자습 기록입니다.



📚숫자세기(계수기) 앱


ℹ️앱 설명

화면 가운데에 숫자를 보여주고 + 버튼을 누르면 숫자가 1씩 올라가고, 초기화 버튼을 누르면 숫자가 초기화되어 0이 되는 간단한 앱.


✅구현 기능

  • ’+’ 버튼 클릭 시, 숫자를 1씩 올리기
  • 초기화 버튼을 클릭 시. 숫자를 0으로 변경하기

📔UI 구현

우선, 화면 중앙에 숫자를 표시하는 텍스트를 보여주고, 그 아래에 초기화 기능을 수행하는 버튼과 1씩 추가하는 ‘+’ 버튼을 둘 것이다.

이를 위해 최상단에 LinearLayout을 구현하였다. LinearLayout에 대한 자세한 부분은 나중에 자세히 다루도록 하자.

📖activity_main.xml

<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

여기서 android:layout_widthandroid:layout_height 속성은 화면의 너비와 높이를 지정하기 때문에 필수적인 속성이다. 또한 android:orientation은 LinearLayout의 속성으로 화면을 배치하는 방법을 나타내는데, 하위 View 요소들을 수직으로 배치하는 vertical과 수평으로 배치하는 horizontal의 값을 가지며 기본값은 horizontal이다.

LinearLayout 내부에는 숫자 텍스트를 표시하는 TextView와 Button 두 개를 수평으로 정렬하기 위해 LinearLayout을 하나 더 생성하여 그 내부에 구성하였다. 코드는 다음과 같다.

    <TextView
        android:id="@+id/numberTextView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="0"
        android:textColor="@color/blue"
        android:textSize="100sp"
        android:textStyle="bold|italic" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <Button
            android:id="@+id/resetButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:layout_weight="1"
            android:text="초기화" />

        <Button
            android:id="@+id/plusButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:layout_weight="1"
            android:text="+" />
    </LinearLayout>

뒤에서 있을 기능 구현을 위해 각각의 구성 요소에 id 값을 지정해주었다. 기능 구현 시, 해당 id 값을 통해 해당 요소를 인식하고 기능을 구현할 수 있다.
다음으로 기능 구현을 위한 코드를 작성해보자.


📔기능 구현 - 유저 입력값 받아오기

먼저 각 버튼의 기능을 먼저 구현해보자.

’+’ 버튼을 누르면 값을 1씩 추가하고, ‘초기화’ 버튼을 누르면 값을 초기화한다.

📖MainActivity.kt

private var number = 0

우선, TextView에 카운트 된 값을 보여주기 위해 이를 저장할 number 변수를 만들었다.

val resetButton = findViewById<Button>(R.id.resetButton)
val plusButton = findViewById<Button>(R.id.plusButton)

앞서 언급 했듯이, 각 요소에 설정해두었던 id 값을 가져와 해당하는 변수를 생성해준다.

        resetButton.setOnClickListener {
            number = 0
            Log.d("onClick", "숫자는 $number")
        }

        plusButton.setOnClickListener {
            number += 1
            Log.d("onClick", "숫자는 $number")
        }

resetButton을 누르면 number 값을 초기화하여 0으로, plusButton을 누르면 number 값을 1씩 증가하도록 하였고, 이를 확인하기 위해 로그값을 설정해 주었다.

button_click_log.png

로그캣에 값이 잘 들어오는 것을 볼 수 있다.

📔기능 구현 - 숫자세기

위의 기능을 토대로 텍스트 뷰에 숫자를 띄워보도록 하자.

📖MainActivity.kt

val numberTextView = findViewById<TextView>(R.id.numberTextView).apply {
            text = number.toString()
        }

텍스트 뷰 id 값을 가져와 변수로 만들어준 뒤, 초기값을 0으로 초기화 해 보여주도록 하였다. 값을 가져왔으니 버튼이 눌렸을 때 텍스트 뷰의 텍스트가 변경되도록 하기 위해 위 버튼 클릭 기능의 코드를 수정해주었다.

resetButton.setOnClickListener {
    number = 0
    numberTextView.text = number.toString()
    Log.d("onClick", "숫자는 $number")
}

plusButton.setOnClickListener {
    number += 1
    numberTextView.text = number.toString()
    Log.d("onClick", "숫자는 $number")
}

이렇게 하면 각각의 버튼이 눌렸을 때 해당되는 기능이 텍스트 뷰에 반영되어 보여진다.

📔추가 구현

📖화면 회전으로 인한 상태 변경 시, 값 유지

화면 회전이 진행 되었을 때, 텍스트 뷰의 값이 그대로 유지되지 않을 수 있다. 이는 activity의 생명 주기 때문인데, 화면이 가로로 변경되었을 때 생명 주기가 다시 시작된다. 이로 인해 값은 초기화 되어 화면 회전 시에 값이 유지가 되지 않는 것이다.

따라서 나는 onSaveInstanceState로 번들에 데이터를 저장해 두었고 onCreate()상태에서 저장된 상태를 다시 불러오도록 하였다.

📖MainActivity.kt

override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt("current_number", number)
}

onSaveInstaceState 를 재정의 하여 키와 데이터 값을 저장해 두었다.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        number = savedInstanceState?.getInt("current_number") ?: 0

oncreate() 호출 시 번들로부터 저장된 데이터를 꺼내온다.

화면 변경 전


화면 변경 후

📔전체 코드

📖activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/numberTextView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="0"
        android:textColor="@color/blue"
        android:textSize="100sp"
        android:textStyle="bold|italic" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <Button
            android:id="@+id/resetButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:layout_weight="1"
            android:text="초기화" />

        <Button
            android:id="@+id/plusButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:layout_weight="1"
            android:text="+" />
    </LinearLayout>

</LinearLayout>

📖MainActivity.kt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView

class MainActivity : AppCompatActivity() {

    private var number = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        number = savedInstanceState?.getInt("current_number") ?: 0

        val numberTextView = findViewById<TextView>(R.id.numberTextView).apply {
            text = number.toString()
        }
        val resetButton = findViewById<Button>(R.id.resetButton)
        val plusButton = findViewById<Button>(R.id.plusButton)

        resetButton.setOnClickListener {
            number = 0
            numberTextView.text = number.toString()
            Log.d("onClick", "숫자는 $number")
        }

        plusButton.setOnClickListener {
            number += 1
            numberTextView.text = number.toString()
            Log.d("onClick", "숫자는 $number")
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt("current_number", number)
    }