안드로이드 앱(Kotlin|Java)/[2025~] 안드로이드 앱

Part1_Ch04_05 응급의료 정보 UI 그리기 (3)

heylo 2025. 4. 9. 15:28

목표

이번 소단원에서는, 이전 소단원 에서 구현하지 못한

  1. Spinner ( A / B / AB / O ) 를 통해 선택된 혈액형 표시하기
  2. Calendar 연결
  3. CheckBox 를 통해 주의사항의 Visibility 관리

이렇게 위 세 가지 UI 를 구현해보도록 할 것이다.


1) Spinner 에 adapter 연결

1-1) Spinner 와 adapter

Spinner 는

삼각형 버튼을 누르면, 리스트로 데이터가 나오고

그 데이터를 선택하면, 선택한 값을 노출시키는 역할이다.

 

개발하면서 정말 자주 마주하는 문제 중 하나가

반복을 정말 싫어한다는 것이다.

 

만약 Spinner를 xml 로 그려야한다면

xml 코드로 리스트 모양을 하나하나 그려야한다.

 

이는 있을 수 없는 일이다.

리스트형 데이터를 UI로 연결해주는 역할을 해주는 것이 바로 adapter 이다.

 

adapter 를 통해서 bloodTypeSpinner 에

A, B, AB, O 이 4가지를 리스트로 넣어주고,

그 중 하나를 선택했을 때

Spinner 에게 얘를 선택했어-라는 것을 알려주는 코드를 작성해볼 것이다.

 


1-2) InputActivity.kt

InputActivity.kt

class InputActivity : AppCompatActivity() {
    private lateinit var binding: ActivityInputBinding
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        binding = ActivityInputBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.bloodTypeSpinner.adapter = ArrayAdapter.createFromResource(
            /* context = */
            this,

        )
    }

ViewBinding 을 한 후

bloodTypeSpinner 에 adapter 를 연결하려고 한다.

 

우리는 ArrayAdapter 의 createFromResource 를 연결할 것인데,

이의 인자를 살펴보면

 

context

textArrayResId

textViewResID

이렇게 3가지를 인자로 한다.

 



두 번째 인자인 textArrayResId 를 위해 

arrays.xml 리소스 파일을 생성해보자.


1-2) arrays.xml 리소스 파일

New > Android Resource File > name: arrays

리소스 파일을 추가해보자.

 

 

RadioButton 에서 <item>을 통해 요소를 넣어주었던 것 처럼

string-array 에서도 <item>을 통해 요소를 넣어준다.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="blood_types">
        <item>A</item>
        <item>B</item>
        <item>O</item>
        <item>AB</item>
    </string-array>
</resources>

1-3) InputActivity.kt 에서 adapter 연결 완료

다시 InputActivity.kt 의

ArrayAdaptor 의 createFromResource 의 인자를 채우러 가보자.

textViewResID 는 이미 안드로이드에서 만들어서 제공하는

간단한 리스트인 simple_list_item_1 을 사용하자.

binding.bloodTypeSpinner.adapter = ArrayAdapter.createFromResource(
    this,
    R.array.blood_types,
    android.R.layout.simple_list_item_1
)

simple_list_item_1 를 살펴보기 위해

글자 위에 커서를 올리고, command 를 누른 채로 클릭 을 해보자.

 

살펴보니 단순한 TextView 이다.

여기에서 우리가 선택한 text 를 넣을 것이다.

 

여기까지 완료하면 아래와 같이

Spinner 에 요소가 들어가있는 것을 확인할 수 있다.

 

 


1-4) Spinner 공간 확보

현재 Spinner 에서 혈액형을 선택했을 때

옆에 혈액형이 표시되지 않는다.

따라서 Spinner 공간을 확보하기 위해

activity_input.xml 파일을 수정해보자.

  • 1-4-1) RadioGroup 에서gravity 를 지우고
  • End_toStartOf 를 지정하지 않고 지운다.
  • layout_width 를 wrap_content 로 변경하고
    <!-- Rh 타입 선택하는 RadioGroup 과 RadioButton -->
    <RadioGroup
        android:id="@+id/bloodTypeRadioGroup"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="@+id/bloodtypeTextView"
        app:layout_constraintStart_toStartOf="@+id/guideLine"
        app:layout_constraintTop_toTopOf="@id/bloodtypeTextView">
  • 1-4-2) Spinner 에서

Spinner 의 공간을 최대한 활용하기 위해서

layout_width 를 0dp 로 변경하고

왼쪽 constraint 를 최대한 왼쪽으로 맞추자.

 

<!-- 실제 혈액형 선택하는 Spinner -->
<Spinner
    android:id="@+id/bloodTypeSpinner"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="@id/bloodtypeTextView"
    app:layout_constraintEnd_toEndOf="@id/nameEditText"
    app:layout_constraintStart_toEndOf="@+id/bloodTypeRadioGroup"
    app:layout_constraintTop_toTopOf="@id/bloodtypeTextView" />

 

 

결과

Spinner 에서 혈액형 선택 시, 오른쪽에 표시된다.

 

 


2) Calendar 연결

2-1) layer 로 두 요소 묶기

날짜 또는 버튼 이미지를 선택했을 때

캘린더를 띄워볼 것이다.

 

날짜나 버튼이미지 어느 부분이든 클릭해도 되도록

이 두 개를 묶는 layer 를 만들어보자.

 

Palette 에서 command ( 또는 ctrl ) 을 누른 채로

두 요소를 클릭하여 선택하자.

 

 

선택된 상태에서 마우스 우클릭 > Add helpers > Layer 로 층을 추가한다.

 

 

activity_input.xml

<androidx.constraintlayout.helper.widget.Layer
    android:id="@+id/birthdateLayer"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:constraint_referenced_ids="birthdateValueTextView,birthdateImageView"
    tools:ignore="MissingConstraints" />

2-2) birthdateLayer 에 ClickListener 추가

 

InputActivity.kt

binding.birthdateLayer.setOnClickListener{
            val listener = OnDateSetListener { _, year, month, dayOfMonth ->
                binding.birthdateTextView.text = "$year-$month-$dayOfMonth"
            }

            DatePickerDialog(
                this,
                listener,
                2000,
                1,
                1
            ).show()
        }

content : this

listener : 사용자가 선택한 날짜를 받아주는 역할

초기날짜 설정 : 2000년 1월 1일

 

 

DatePickerDialog 에서 show() 를 하지 않으면

DatePickerDialog 를 만들어놓기만 한 것이고 보이지 않는다.

 

 

실행해보면,

내가 선택한 날짜는 1998-2-6 인데

보이는 날짜는 1998-1-6 이다.

 

 

안드로이드에서 제공하는 캘린더에 나오는 달(month)은 하나를 더 추가해주어야 날짜가 정확히 나온다.

따라서 아래 코드를 변경하자.

 

변경 전: binding.birthdateTextView.text = "$year-$month-$dayOfMonth"

변경 후: binding.birthdateTextView.text = "$year-${month.inc()}-$dayOfMonth"


3) CheckBox 를 통해 주의사항의 Visibility 관리

// 체크박스
binding.warningCheckBox.setOnCheckedChangeListener{ _, isChecked ->
    binding.warningEditText.isVisible = isChecked

}

체크박스 선택이 되면, 주의사항이 보이고

체크박스가 선택 되지 않으면, 주의사항이 안 보인다.

 

( 주의사항 노출 글자 길이는 다음 단원에서 수정할 것이다. )

 

 

다만, 초반에는 선택 여부에 따라서, 노출 여부가 적용되지 않는다.

 

값이 변경 되었을 때만, 화면에 노출여부를 선택하게 했기 때문이다.

기본적으로 선택되지 않은 것이 기본값이다.

 

처음에 선택이 되었는지 여부에 따라

노출여부를 한 번 더 설정해주어야 한다.

// 체크박스
binding.warningCheckBox.setOnCheckedChangeListener{ _, isChecked ->
    binding.warningEditText.isVisible = isChecked

}
// 초기 선택여부
binding.warningEditText.isVisible = binding.warningCheckBox.isChecked

추가 구현할 기능

현재까지는 선택한 정보가 이전 화면에 보이지 않는다.

이 문제를 해결하기 위해서

사용자가 입력한 값을 intent 를 통해서 데이터를 보낼 수는 있다.

그러나 intent 는

Activity 와 Activity 사이에  데이터를 주고받는 역할밖에 하지 못한다.

어딘가에 데이터를 저장하지 않았기 때문에

앱을 껐다가 켜면, 데이터가 사라진다.

다음 단원에서는 입력한 정보를 저장하고 데이터를 불러오는 것을 할 것이다.


전체코드

InputActivity.kt

package com.part1.chapter4

import android.app.DatePickerDialog
import android.app.DatePickerDialog.OnDateSetListener
import android.os.Bundl
import android.os.PersistableBundle
import android.util.Log
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.part1.chapter4.databinding.ActivityInputBinding

class InputActivity : AppCompatActivity() {
    private lateinit var binding: ActivityInputBinding
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        binding = ActivityInputBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 스피너
        binding.bloodTypeSpinner.adapter = ArrayAdapter.createFromResource(
            this,
            R.array.blood_types,
            android.R.layout.simple_list_item_1
        )

        // 캘린더
        binding.birthdateLayer.setOnClickListener{
            val listener = OnDateSetListener { _, year, month, dayOfMonth ->
                binding.birthdateTextView.text = "$year-${month.inc()}-$dayOfMonth"
            }

            DatePickerDialog(
                this,
                listener,
                2000,
                1,
                1
            ).show()
        }

        // 체크박스
        binding.warningCheckBox.setOnCheckedChangeListener{ _, isChecked ->
            binding.warningEditText.isVisible = isChecked

        }
        // 초기 선택여부
        binding.warningEditText.isVisible = binding.warningCheckBox.isChecked


    }
}

 

 

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


    <!-- 이름 -->
    <TextView
        android:id="@+id/nameTextView"
        style="@style/Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="36dp"
        android:text="이름"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/nameEditText"
        style="@style/Value"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="36dp"
        android:inputType="text"
        app:layout_constraintBaseline_toBaselineOf="@+id/nameTextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@id/guideLine" />

    <!-- 생년월일 -->
    <TextView
        android:id="@+id/birthdateTextView"
        style="@style/Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="생년월일"
        app:layout_constraintStart_toStartOf="@id/nameTextView"
        app:layout_constraintTop_toBottomOf="@+id/nameTextView" />

    <TextView
        android:id="@+id/birthdateValueTextView"
        style="@style/Value"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:paddingEnd="8dp"
        android:text="0000-00-00"
        app:layout_constraintBaseline_toBaselineOf="@id/birthdateTextView"
        app:layout_constraintEnd_toStartOf="@id/birthdateImageView"
        app:layout_constraintStart_toStartOf="@id/guideLine" />

    <!-- 달력 이미지 -->
    <ImageButton
        android:id="@+id/birthdateImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_baseline_edit_calendar_24"
        app:layout_constraintBottom_toBottomOf="@id/birthdateTextView"
        app:layout_constraintEnd_toEndOf="@id/nameEditText"
        app:layout_constraintTop_toTopOf="@id/birthdateTextView" />


    <!-- 혈액형 -->
    <TextView
        android:id="@+id/bloodtypeTextView"
        style="@style/Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="혈액형"
        app:layout_constraintStart_toStartOf="@id/birthdateTextView"
        app:layout_constraintTop_toBottomOf="@+id/birthdateTextView" />

    <!-- Rh 타입 선택하는 RadioGroup 과 RadioButton -->
    <RadioGroup
        android:id="@+id/bloodTypeRadioGroup"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="@+id/bloodtypeTextView"
        app:layout_constraintStart_toStartOf="@+id/guideLine"
        app:layout_constraintTop_toTopOf="@id/bloodtypeTextView">

        <RadioButton
            android:id="@+id/bloodTypePlus"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Rh+" />

        <RadioButton
            android:id="@+id/bloodTypeMinus"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Rh-" />

    </RadioGroup>

    <!-- 실제 혈액형 선택하는 Spinner -->
    <Spinner
        android:id="@+id/bloodTypeSpinner"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="@id/bloodtypeTextView"
        app:layout_constraintEnd_toEndOf="@id/nameEditText"
        app:layout_constraintStart_toEndOf="@+id/bloodTypeRadioGroup"
        app:layout_constraintTop_toTopOf="@id/bloodtypeTextView" />


    <!-- 비상연락처 -->
    <TextView
        android:id="@+id/emergencyContactTextView"
        style="@style/Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="비상 연락처"
        app:layout_constraintStart_toStartOf="@id/nameTextView"
        app:layout_constraintTop_toBottomOf="@+id/bloodtypeTextView" />

    <EditText
        android:id="@+id/emergencyContactEditText"
        style="@style/Value"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="010-0000-0000"
        android:inputType="phone"
        app:layout_constraintBaseline_toBaselineOf="@id/emergencyContactTextView"
        app:layout_constraintEnd_toEndOf="@+id/nameEditText"
        app:layout_constraintStart_toStartOf="@id/guideLine" />

    <!-- 주의사항 -->
    <TextView
        android:id="@+id/warningTextView"
        style="@style/Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="주의사항"
        app:layout_constraintStart_toStartOf="@id/emergencyContactTextView"
        app:layout_constraintTop_toBottomOf="@+id/emergencyContactTextView" />

    <EditText
        android:id="@+id/warningEditText"
        style="@style/Value"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="주의사항"
        app:layout_constraintEnd_toEndOf="@+id/nameEditText"
        app:layout_constraintStart_toStartOf="@id/guideLine"
        app:layout_constraintTop_toBottomOf="@id/warningCheckBox" />

    <!-- 주의사항을 보여줄지 여부를 선택하는 CheckBox -->
    <CheckBox
        android:id="@+id/warningCheckBox"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="end|center_vertical"
        android:text="주의사항 노출"
        app:layout_constraintBottom_toBottomOf="@+id/warningTextView"
        app:layout_constraintEnd_toEndOf="@+id/nameEditText"
        app:layout_constraintStart_toStartOf="@+id/warningEditText"
        app:layout_constraintTop_toTopOf="@+id/warningTextView" />


    <!-- value 는 사용자의 입력값에 따라 길이가 변경됨 : Guideline -->
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideLine"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.4" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/goInputActivityButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="36dp"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@drawable/ic_baseline_edit_24" />

    <androidx.constraintlayout.helper.widget.Layer
        android:id="@+id/birthdateLayer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="birthdateValueTextView,birthdateImageView"
        tools:ignore="MissingConstraints" />


</androidx.constraintlayout.widget.ConstraintLayout>