전체 코드 : https://github.com/ryr0121/AndroidPractice/tree/main/stopwatchApp

 

구현 결과

 

주요 기능

  • 카운트다운
  • 스톱워치 (시작/일시정지/정지)
  • 중간 기록 추가

 

ConstraintLayout으로 view 배치

Dialog / ProgressBar 를 통한 카운트다운 설정 및 현황 파악

// AlertDialog를 이용한 카운트다운 설정창 열기

private fun showCountdownSettingDialog() {
    AlertDialog.Builder(this).apply {
        val dialogBinding = DialogCountdownSettingBinding.inflate(layoutInflater)
        with(dialogBinding.countdownSecondPicker) {
            maxValue = 20
            minValue = 0
            value = countdownSecond
        }
        setView(dialogBinding.root)
        setTitle("카운트다운 설정")
        setPositiveButton("확인") { _, _ ->
            countdownSecond = dialogBinding.countdownSecondPicker.value
            currentCountdownDeciSecond = countdownSecond * 10
            binding.countdownTextView.text = String.format("%02d", countdownSecond)
        }
        setNegativeButton("취소",null)
    }.show()
}
// ProgressBar 추가

<ProgressBar
    android:id="@+id/countdownProgressBar"
    style="@style/Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="30dp"
    app:layout_constraintBottom_toTopOf="@id/timeTextView"
    app:layout_constraintStart_toStartOf="parent" />


// ProgressBar 설정

private fun start() {
    timer = timer(initialDelay = 0, period = 100) {
        if (currentCountdownDeciSecond == 0) {
            ...
            
        } else {
            currentCountdownDeciSecond -= 1
            val sec = currentCountdownDeciSecond/10
            val progress = (currentCountdownDeciSecond/(countdownSecond * 10f)) * 100

            binding.root.post {
				...
                binding.countdownProgressBar.progress = progress.toInt()
            }
        }
        ...
    }
}

 

ScrollViewLinearLayout을 활용한 시간 기록 view 구성

<ScrollView
    android:id="@+id/lapScrollView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginTop="50dp"
    android:padding="16dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/guideline">

    <LinearLayout
        android:id="@+id/lapContainerLinearLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

</ScrollView>

 

코드를 활용한 시간 기록 TextView 생성 및 ScrollView에 추가

private fun lap() {
    if (currentDeciSecond == 0) return
    val container = binding.lapContainerLinearLayout
    TextView(this).apply {
        textSize = 20f
        gravity = Gravity.CENTER

        val min = currentDeciSecond.div(10)/60
        val sec = currentDeciSecond.div(10)%60
        val deciSec = currentDeciSecond%10

        text = "${container.childCount.inc().toString()}. " + String.format(
            "%02d:%02d:%02d",
            min,
            sec,
            deciSec
        )
        setPadding(30)
    }.let { lapTextView ->
        container.addView(lapTextView, 0)
    }
}

 

workThread와 runOnUiThread / post 메소드를 이용한 UI 작업 비동기 처리

private fun start() {
    timer = timer(initialDelay = 0, period = 100) {
        if (currentCountdownDeciSecond == 0) {
        	// UI 업데이트가 아닌 다른 작업의 비동기 처리
            currentDeciSecond += 1

            val min = currentDeciSecond.div(10)/60
            val sec = currentDeciSecond.div(10)%60
            val deciSec = currentDeciSecond%10
            
            // runOnUiThread를 활용한 UI 업데이트 작업 진행
            runOnUiThread {
                binding.timeTextView.text = String.format("%02d:%02d", min, sec)
                binding.tickTextView.text = deciSec.toString()

                binding.countdownGroup.isVisible = false
            }
        } else {
        	// UI 업데이트가 아닌 다른 작업의 비동기 처리
            currentCountdownDeciSecond -= 1
            val sec = currentCountdownDeciSecond/10
            val progress = (currentCountdownDeciSecond/(countdownSecond * 10f)) * 100

			// post 메소드를 활용한 UI 업데이트 작업 진행
            binding.root.post {
                binding.countdownTextView.text = String.format("%02d", sec)
                binding.countdownProgressBar.progress = progress.toInt()
            }
        }
        ...
    }
}

 

ToneGenerator를 활용한 알림음 추가

val toneType = if(currentCountdownDeciSecond == 0) ToneGenerator.TONE_CDMA_HIGH_L else ToneGenerator.TONE_CDMA_ANSWER
ToneGenerator(AudioManager.STREAM_ALARM, ToneGenerator.MAX_VOLUME)
    .startTone(toneType, 100)

'Android' 카테고리의 다른 글

[Android] "계산기 앱" 구현  (0) 2024.06.30
[Android] "응급 의료정보 앱" 구현  (0) 2024.06.30
[Android] "단위 변환기 앱" 구현  (4) 2024.06.26
[Android] "숫자세기 앱" 구현  (7) 2024.06.25

전체 코드 : https://github.com/ryr0121/AndroidPractice/tree/main/calculatorApp

 

구현 결과

 

주요 기능

  • 정수형 숫자의 더하기, 빼기 연산

FlowLayout을 통해 숫자 및 연산자 버튼 배치

<androidx.constraintlayout.helper.widget.Flow
    android:id="@+id/keypadFlow"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHeight_percent="0.7"
    app:layout_constraintVertical_bias="1"
    app:flow_maxElementsWrap="4"
    app:flow_wrapMode="chain"
    android:padding="8dp"
    app:flow_horizontalGap="8dp"
    app:constraint_referenced_ids="button_1, button_2, button_3, button_clear,
    button_4, button_5, button_6, button_plus,
    button_7, button_8, button_9, button_minus,
    button_0, button_equal"
    />

 

layout_constraintHorizontal_weight 값 지정을 통해 한 행에 관하여 얼만큼의 비율로 view를 차지하게 할 것인지를 지정

<Button
    android:id="@+id/button_0"
    style="@style/numberKeypad"
    android:text="0"
    android:onClick="numberClicked"
    app:layout_constraintHorizontal_weight="1"
    tools:ignore="MissingConstraints" />

<Button
    android:id="@+id/button_equal"
    style="@style/operatorKeypad"
    android:text="="
    android:onClick="equalClicked"
    app:layout_constraintHorizontal_weight="3"
    tools:ignore="MissingConstraints" />

 

setOnClickListen 메소드 대신 view 내의 onClick 속성값을 통해 클릭 이벤트 지정 (실습 상으로 해봄)

<Button
    android:id="@+id/button_1"
    style="@style/numberKeypad"
    android:text="1"
    android:onClick="numberClicked"
    tools:ignore="MissingConstraints" />

 

'Android' 카테고리의 다른 글

[Android] "스톱워치 앱" 구현  (0) 2024.07.02
[Android] "응급 의료정보 앱" 구현  (0) 2024.06.30
[Android] "단위 변환기 앱" 구현  (4) 2024.06.26
[Android] "숫자세기 앱" 구현  (7) 2024.06.25

전체 코드 : https://github.com/ryr0121/AndroidPractice/tree/main/sosInfoApp 

 

구현 결과 : 

 

주요 기능

  • 의료정보(이름, 생년월일, 혈액형, 비상 연락처) 조회/입력/수정/삭제
  • 비상 연락처 탭하면 전화 앱으로 연결

ConstraintLayout으로 view 배치

RadioButton, Spinner를 이용한 범위 내 값 선택 구현

// Rh+, Rh- 중 하나를 선택하는 RadioButton 그룹

<RadioGroup
    android:id="@+id/bloodTypeRadioGroup"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    app:layout_constraintBottom_toBottomOf="@id/bloodTypeTextView"
    app:layout_constraintEnd_toStartOf="@id/bloodTypeSpinner"
    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>
// A, B, AB, O형 중 하나를 선택하는 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_constraintTop_toTopOf="@id/bloodTypeTextView" />

 

 

Intent를 이용한 화면 전환 및 전화앱 연결 구현

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

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

        ...

        binding.moveToSosCallLayer.setOnClickListener {
            with(Intent(Intent.ACTION_VIEW)) {
                val phoneNum = binding.sosPhoneValueTextView.text.toString().replace("-","")
                data = Uri.parse("tel:$phoneNum")
                startActivity(this)
            }
        }
    }

 

SharedPreferences를 이용해 의료정보 데이터 관리

  • 데이터 조회
with(getSharedPreferences(USER_INFO, Context.MODE_PRIVATE)) {
    binding.nameValueTextView.text = getString(NAME, "미입력")
    binding.birthValueTextView.text = getString(BIRTH, "미입력")
    binding.bloodTypeValueTextView.text = getString(BLOOD_TYPE, "미입력")
    binding.sosPhoneValueTextView.text = getString(SOS_PHONE, "미입력")
    val notiInfo = getString(NOTI_INFO, "")

    binding.notiInfoTextView.isVisible = notiInfo.isNullOrEmpty().not()
    binding.notiInfoValueTextView.isVisible = notiInfo.isNullOrEmpty().not()

    if(!notiInfo.isNullOrEmpty()) {
        binding.notiInfoValueTextView.text = notiInfo
    }
}
  • 데이터 수정 및 저장
with(getSharedPreferences(USER_INFO, Context.MODE_PRIVATE).edit()) {
    putString(NAME, binding.nameEditText.text.toString())
    putString(BLOOD_TYPE, getBloodType())
    putString(SOS_PHONE, binding.sosPhoneValueTextView.text.toString())
    putString(BIRTH, binding.birthEditText.text.toString())
    putString(NOTI_INFO, getNotiInfo())
    apply()
}
  • 데이터 삭제
with(getSharedPreferences(USER_INFO, Context.MODE_PRIVATE).edit()) {
    clear()
    apply()
    getDataAndUiUpdate() // 데이터 재조회 및 UI 업데이트
}

'Android' 카테고리의 다른 글

[Android] "스톱워치 앱" 구현  (0) 2024.07.02
[Android] "계산기 앱" 구현  (0) 2024.06.30
[Android] "단위 변환기 앱" 구현  (4) 2024.06.26
[Android] "숫자세기 앱" 구현  (7) 2024.06.25

전체 코드 : https://github.com/ryr0121/AndroidPractice/tree/main/unitConversionApp

 

구현 결과

 

주요 기능

  • cm <-> m 간의 값 변환 (초기 상태 : cm단위의 값 입력 시, m의 값으로 변환)
  • 버튼을 통한 변환 단위 교체

ConstraintLayout으로 view 배치

ConstraintLayout은 상대적 제약조건을 통해 배치가 이루어지므로 렌더링 속도 향상 등의 이점 존재

<?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">

   	...

</androidx.constraintlayout.widget.ConstraintLayout>

 

구현을 위해 사용한 컴포넌트는 아래와 같음

각 컴포넌트들에 대해 layout_..._...의 속성값 지정을 통해 상대적인 제약조건을 추가하여 UI 배치 진행

  • EditText : 변환시킬 정수값을 입력받기 위한 컴포넌트
    • hint : 입력값에 대한 정보를 나타내는 텍스트
    • maxLength : 입력값의 최대 길이 지정
    • inputType : 입력값의 타입 지정 (number : 정수 / numberDecimal : 소수를 포함한 숫자)
<EditText
        android:id="@+id/inputEditText"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:gravity="end"
        android:hint="자연수를 입력해주세요"
        android:inputType="numberDecimal"
        android:maxLength="7"
        android:textColorHint="@color/purple"
        android:textSize="20sp"
        android:textStyle="italic"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.4"
        app:layout_constraintHorizontal_bias="0.2" />
  • TextView : 단위가 변환된 값 / 입,출력 값의 단위 표시를 위한 컴포넌트
<TextView
        android:id="@+id/outputTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="end"
        android:text="0"
        android:textColor="@color/black"
        android:textSize="30sp"
        android:layout_marginTop="30dp"
        app:layout_constraintEnd_toEndOf="@+id/inputEditText"
        app:layout_constraintStart_toStartOf="@id/inputEditText"
        app:layout_constraintTop_toBottomOf="@id/inputEditText" />

    <TextView
        android:id="@+id/inputUnitTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="cm"
        android:textSize="20sp"
        android:layout_marginStart="16dp"
        app:layout_constraintStart_toEndOf="@+id/inputEditText"
        app:layout_constraintBaseline_toBaselineOf="@id/inputEditText" />

    <TextView
        android:id="@+id/outputUnitTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="m"
        android:textSize="20sp"
        android:layout_marginStart="16dp"
        app:layout_constraintStart_toEndOf="@+id/outputTextView"
        app:layout_constraintBaseline_toBaselineOf="@id/outputTextView" />
  • ImageButton : 단위 교체를 위한 컴포넌트
    • src 속성값 지정을 통해 버튼에 사용할 이미지 선택
<ImageButton
        android:id="@+id/swapImgButton"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/baseline_swap_vert_24"
        app:layout_constraintStart_toEndOf="@id/inputUnitTextView"
        app:layout_constraintTop_toTopOf="@id/inputEditText"
        app:layout_constraintBottom_toBottomOf="@id/outputTextView"
        android:layout_marginStart="16dp" />

 

MainActivity 내에서 ViewBinding을 통해 findById 메소드 사용의 단점을 보완

(예. 중복된 ID 값으로 인한 문제, ID값 분실, 지정된 xml에서의 뷰 포함 여부 확인 어려움 등)

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

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

        val outputTextView = binding.outputTextView
        val outputUnitTextView = binding.outputUnitTextView
        val inputEditText = binding.inputEditText
        val inputUnitTextView = binding.inputUnitTextView
        val swapImgButton = binding.swapImgButton

        ...
    }

    ...
}

 

EditText의 addTextChangedListener 메소드를 통해 입력값의 변화가 감지될 때마다 관련된 이벤트 처리 가능

inputEditText.addTextChangedListener { text ->
    inputNumber = if (text.isNullOrEmpty()) { 0 } else { text.toString().toInt() }

    if (cmToM) {
        outputTextView.text = inputNumber.times(0.01).toString()
    } else {
        outputTextView.text = inputNumber.times(100).toString()
    }
}

 

onSaveInstanceState 메소드와 onRestoreInstanceState 메소드를 통해, 단위 변환 상태에 대한 '임시 UI 상태 저장 및 복원' 가능
(공식 문서 : https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko)

override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
    outState.putBoolean("cmToM", cmToM)

    super.onSaveInstanceState(outState, outPersistentState)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    cmToM = savedInstanceState.getBoolean("cmToM")
    binding.inputUnitTextView.text = if(cmToM) "cm" else "m"
    binding.outputUnitTextView.text = if(cmToM) "m" else "cm"
    
    super.onRestoreInstanceState(savedInstanceState)
}

'Android' 카테고리의 다른 글

[Android] "스톱워치 앱" 구현  (0) 2024.07.02
[Android] "계산기 앱" 구현  (0) 2024.06.30
[Android] "응급 의료정보 앱" 구현  (0) 2024.06.30
[Android] "숫자세기 앱" 구현  (7) 2024.06.25

전체 코드 : https://github.com/ryr0121/AndroidPractice/tree/main/countingApp

 

구현결과

주요 기능

  • 화면 상단에 카운팅된 숫자 표시
  • '초기화' 버튼으로 숫자를 0으로 변경
  • '+' 버튼으로 숫자를 1씩 증가

 

xml을 통해 UI 구현을 진행했고, LinearLayout을 통해 view를 배치함

LinearLayout은 배치 방향(orientation)의 값에 따라 세로 방향(vertical) 혹은 가로 방향(horizontal)으로 view 배치가 이루어짐

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    ...


</LinearLayout>

 

포함시킨 view는 TextView와 Button임

여러 디바이스에서 동등한 크기로 표시하기 위해 고안된 단위인 "밀도 독립적 픽셀(DP)" 사용

(픽셀 단위를 사용할 경우 디바이스 별로 전체 픽셀이 다르기 때문에 같은 값이 서로 다른 크기로 표현될 수 있기 때문)

id값 지정을 통해 이후 activity에서 버튼 이벤트 발생 시 변경된 숫자값을 표시하는 것이 가능해짐

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

 

'초기화' 버튼과 '+' 버튼을 수평 방향으로 나란히 배치하기 위해 orientation이 horizontal인 LinearLayout을 사용함

id값 지정을 통해 이후 activity에서 사용자의 버튼 클릭 이벤트 처리가 가능해짐

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

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

 

MainActivity 내에서 카운팅 현황을 나타내는 number 변수 선언 및 활용

findViewById 메소드를 활용하여 지정된 id의 view를 찾아와 활용

button의 setOnClickListener를 람다로 정의하여 사용자가 버튼 클릭 시 일어날 내용을 작성 (숫자를 0 혹은 +1 하여 변경)

class MainActivity : AppCompatActivity() {
    private var number = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

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

        resetButton.setOnClickListener {
            number = 0
            numberTextView.text = number.toString()
        }
        plusButton.setOnClickListener {
            number += 1
            numberTextView.text = number.toString()
        }
    }
}

 

 

아요랑 다른데 비슷해,,,비슷한데 다른 늒김,,,,,암튼 첫 구현 도전 성공,,

'Android' 카테고리의 다른 글

[Android] "스톱워치 앱" 구현  (0) 2024.07.02
[Android] "계산기 앱" 구현  (0) 2024.06.30
[Android] "응급 의료정보 앱" 구현  (0) 2024.06.30
[Android] "단위 변환기 앱" 구현  (4) 2024.06.26

+ Recent posts