sh711 님의 블로그

안드로이드 네이티브 계산기 앱 개발 - 1 본문

Development/Android

안드로이드 네이티브 계산기 앱 개발 - 1

sh711 2025. 3. 2. 22:17

 

1. 환경 세팅

아래 링크에서 android studio를 설치한다.
https://developer.android.com/studio?hl=ko

 

Android 스튜디오 및 앱 도구 다운로드 - Android 개발자  |  Android Studio  |  Android Developers

Android Studio provides app builders with an integrated development environment (IDE) optimized for Android apps. Download Android Studio today.

developer.android.com

참고로 나는 koala 버전을 사용하여 개발을 진행하였다.

 
설치가 완료되면 File -> New -> New Project 를 선택한다

 
Empty  Views Activity를 선택하고 Next 클릭

 
프로젝트 이름은 "calculator"로 하고 언어는 kotlin 호환되는 SDK는 API 29까지로 정했다
Finish 클릭

 
SDK manager에서 NDK Tools 를 설치해준다

 
C:\Users\계정ID\AppData\Local\Android\Sdk\ndk 디렉터리에 다음과 같이 NDK 이 설치된 것을 확인할 수 있다

 
이어서, Gradle Scripts 하위 Local.properties 에서 NDK 경로를 설정해준다

 

2. UI 구현

UI는 iOS 계산기와 비슷하게 만들어보려고 한다.

 
LiveData를 이용해 result TextView의 값이 변하면 observe를 통해 탐지하여 UI를 변화시킬 것이다

 
현재 tvResult는 viewModel.result에 연동되있는 상태이다.

    <data>
        <variable
            name="viewModel"
            type="com.test.calculator.CalculatorViewModel" />
    </data>
	
    ...
    ...
    ...
    
    <TextView
        android:id="@+id/tvResult"
        android:text="@{viewModel.result}"
        android:textColor="@color/white"
        android:textSize="40sp"
        android:textStyle="bold"
        android:gravity="right"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:layout_marginHorizontal="15dp">
    </TextView>

 
ViewModel 클래스

class CalculatorViewModel : ViewModel() {

    private val _result = MutableLiveData<String>("")
    private val _input = MutableLiveData<String>("input")

    val result: LiveData<String> get() = _result
    val input: LiveData<String> get() = _input
    
    }

 
이제 버튼 클릭 시 tvResult 에 변화를 줘보자

// MainActivity

package com.test.calculator

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.hyunho.calc.databinding.ActivityMainBinding


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var calculatorViewModel: CalculatorViewModel

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

        val decorView = window.decorView
        decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                or View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)

        calculatorViewModel = ViewModelProvider(this).get(CalculatorViewModel::class.java)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.viewModel = calculatorViewModel // XML의 viewModel에 ViewModel 객체 연결
        binding.lifecycleOwner = this

        calculatorViewModel.result.observe(this, Observer { newResult ->
            if (newResult.toString().length > 14) {
                Toast.makeText(this, "입력 초과입니다!!!", Toast.LENGTH_SHORT).show()
                calculatorViewModel.deleteResult()
            }
            else {
                binding.tvResult.text = newResult
            }
        })

        val numMap = mapOf(
            binding.num1 to "1",
            binding.num2 to "2",
            binding.num3 to "3",
            binding.num4 to "4",
            binding.num5 to "5",
            binding.num6 to "6",
            binding.num7 to "7",
            binding.num8 to "8",
            binding.num9 to "9",
            binding.num0 to "0",
        )
        numMap.forEach { (button, number) ->
            button.setOnClickListener {
                calculatorViewModel.operand(number)
            }
        }

        val operMap = mapOf(
            binding.plus to "+",
            binding.minus to "-",
            binding.multiply to "x",
            binding.divide to "/",
            binding.mod to "%",
            binding.point to ".",
        )
        operMap.forEach { (button, operation) ->
            button.setOnClickListener {
                calculatorViewModel.operator(operation)
            }
        }

        binding.reverse.setOnClickListener {
            calculatorViewModel.reverse()
        }

        binding.delete.setOnClickListener {
            calculatorViewModel.deleteResult()
        }

        binding.AC.setOnClickListener {
            calculatorViewModel.allClear()
        }

    }
}

 
ViewModel 클래스에 함수 선언

// CalculatorViewModel

package com.test.calculator

import android.content.ContentValues.TAG
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class CalculatorViewModel : ViewModel() {

    private val _result = MutableLiveData<String>("")
    private val _input = MutableLiveData<String>("input")

    val result: LiveData<String> get() = _result
    val input: LiveData<String> get() = _input

    fun operand(number: String) {
        if(_result.value == "0") {
            _result.value = number
        } else {
            _result.value = _result.value + number
        }
    }

    fun operator(op: String) {
        if (_result.value == "0" || _result.value == "")
            if (op == "-") {
                _result.value = "-"
            } else {
                _result.value = ""
            }
        else {
            _result.value = _result.value + op
        }
    }

    fun result(result: String) {
        _result.value = result
    }

    fun deleteResult() {
        _result.value = _result.value?.dropLast(1)
    }

    fun allClear() {
        _result.value = ""
        _input.value = "input"
    }

    fun reverse() {
        val currentInput = _result.value ?: return // 현재 결과값이 없다면 리턴

        val regex = "[-+]?\\d+$".toRegex() // 마지막 숫자와 부호를 찾는 정규식
        val matchResult = regex.find(currentInput)

        if (matchResult != null) {
            val lastNumber = matchResult.value // 마지막 숫자 추출

            // 부호를 반전
            val reversed = if (lastNumber.startsWith("-")) {
                lastNumber.replaceFirst("-", "+")
            } else if (lastNumber.startsWith("+")) {
                lastNumber.replaceFirst("+", "-")
            } else {
                "+$lastNumber"
            }

            // 마지막 숫자만 변경하여 업데이트
            _result.value = currentInput.replace(lastNumber, reversed)
        }
    }
}

 
이제 기본적인 UI와 LiveData는 완성되었다.

 
다음 글에서 JNI 연동 및 네이티브 함수를 통해 연산하는 코드 작성을 해보겠다.

'Development > Android' 카테고리의 다른 글

안드로이드 네이티브 계산기 앱 개발 - 2  (0) 2025.03.03