본문 바로가기

프로그래밍/Android

[Kotlin] Android Studio 계산기 만들기 (feat. MVVM)

지난 RecyclerView 게시글에 이어서 진짜 계산이 되는 계산기를 만들어보도록 하겠다.

 

https://gummy119.tistory.com/30

 

[Kotlin] Android Studio RecyclerView 사용법 (feat. MVVM)

What is RecyclerView? View를 재활용(recycle)해서 리스트를 보여주는 ViewGroup이다. view들을 담는 컨테이너라고 생각하면 된다. RecyclerView 이전에는 ListView를 사용하여 스크롤되는 리스트를 보여줬다. 하지

gummy119.tistory.com

 

우선 계산기를 만들려면 고려해야 하는 것

 

1. 숫자 없이 연산자가 연속으로 나열되지 않도록 한다.

 

2. 곱셈과 나눗셈을 먼저 처리한 후 나머지 연산자를 처리한다.

 

ViewModel

	//연산 과정을 보여주는 LiveData
	private val _inputExpression = MutableLiveData<String>()
	val inputExpression : LiveData<String>
		get() = _inputExpression
        
	//계산 결과값을 보여주는 LiveData
	private val _calculatedResult = MutableLiveData<String>()
	val calculatedResult : LiveData<String>
		get() = _calculatedResult
	
	//계산 결과가 나온 상태인지 확인하는 LiveData
	private val _isCalculated = MutableLiveData<Boolean>()

	fun init(){
		_isCalculated.value = false
		_calculatedResult.value = "0"
		_inputExpression.value = "0"

	}

 

1. 연산자 연속 나열 금지

연산 과정(_inputExpression)의 마지막이 연산자이고, 새롭게 들어온 입력(input)도 연산자이면 true를 반환하는 방식으로 확인했다.

 

checkedFormat()이 false를 반환하고, 계산 결과(_isCalculated)가 없는 상태인 경우에만 입력이 이뤄지도록 했다.

fun setExpression(input : String){
        if(_isCalculated.value == false && !checkedFormat(_inputExpression, input)){
            when(_inputExpression.value){
                "0" -> _inputExpression.value = input
                else -> _inputExpression.value = _inputExpression.value.plus(input)
            }
        }else{
        /* 계산한 값이 있는 경우엔 계산 결과(_calculatedResult)에 새로운 연산 과정을 추가하고,
        * _isCalculated를 초기화해서 새롭게 계산 시작
        */
            _inputExpression.value = _calculatedResult.value.plus(input)
            _isCalculated.value = false
        }

    }

    private fun checkedFormat(pastExpression : MutableLiveData<String>, input : String) : Boolean{
        return pastExpression.value!!.last().toString().matches("[+\\-x/]".toRegex())
                && input.matches("[+\\-x/]".toRegex())

    }

 

2. 연산 우선순위

연산 과정에서 숫자와 연산자를 나눠서 곱셈과 나눗셈을 먼저 처리한 이후 나머지 연산자를 수행하도록 했다.

private fun evaluateExpression(input: String): String {
	// 1. 숫자와 연산자를 나눠 담음
        val numbersList = input.split(Regex("[+\\-x/]")).map { it.toDouble() }.toMutableList()
        var operators = input.replace(Regex("[0-9.]"), "")
        
	// 2. 연산자가 곱셈이나 나눗셈인 경우 해당 연산을 수행하고 수행한 결과를 numberList에 업데이트
        operators.indices.forEach { i ->
            when (operators[i]) {
                'x' -> numbersList[i] *= numbersList.removeAt(i + 1)
                '/' -> numbersList[i] /= numbersList.removeAt(i + 1)
            }
        }
	// 3. 곱셈, 나눗셈 연산이 끝나면 해당 연산자 삭제
        operators = operators.replace("x", "").replace("/", "")
        
	// 4. 나머지 연산 수행
        operators.indices.forEach { i ->
            when (operators[i]) {
                '+' -> numbersList[i] += numbersList.removeAt(i + 1)
                '-' -> numbersList[i] -= numbersList.removeAt(i + 1)
            }
        }
        return formatNumber(numbersList.last())
    }

 

숫자를 double로 지정해서 소수점 이하 숫자가 없어도 .0의 형태로 나오는게 매우 보기 안좋았다.

 

소수점 이하 숫자가 없는 경우(0인 경우)엔 int로 반환해서 좀 더 깔끔하게 보이도록 했다.

 

private fun formatNumber(number : Double) : String{

        return if (number % 1 == 0.0) {
            number.toInt().toString()
        } else {
            number.toString()
        }
}

 

Fragment

이제 다음과 같이 clickListener에 이벤트를 처리해준다.

calculatorAdapter = CalculatorAdapter(object : CalculatorClickListener{
            override fun onItemClick(number: String) {
                Log.d(TAG, "onItemClick: $number")

                when(number){
                    "DEL" -> viewModel.init()
                    "=" -> viewModel.calculateResult()
                    else -> viewModel.setExpression(number)
                }

            }
        })

 

 

이제 어플리케이션을 수행하면 다음과 같이 연산이 이뤄진다.