코틀린 안드로이드 - 계산기2(layout,계산기 기능)

Jamwon·2021년 6월 11일
0

Kotlin_Android

목록 보기
13/30
post-thumbnail

저번에 이어서 layout부분 추가 부분

계산기 계산식이나오는 부분과 다른 drawble layout

layout

button_background_green.xml

drawable 폴더에 파일 만들기
result 버튼의 배경이 초록색인 버튼을 만들어준다.

전에 button_backgroun 코드를 복사해서 붙여넣기고 색깔들을 바꿔준다.

<ripple android:color="@color/greenPress"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item   android:id="@android:id/background">
        <shape android:shape="rectangle">
            <solid android:color="@color/green"/>
            <corners android:radius="100dp"/>
            <stroke android:width="1dp" android:color="@color/greenPress"/>
        </shape>

    </item>

</ripple>

activity_main.xml

            <androidx.appcompat.widget.AppCompatButton
                android:id="@+id/btn_result"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_margin="7dp"
                android:background="@drawable/button_background_green"
                android:onClick="resultButtonClicked"
                android:stateListAnimator="@null"
                android:text="="
                android:textColor="@color/white"
                android:textSize="24sp" />

result 버튼에 id를 부여하고 만들어준 배경을 적용하고 글자색을 변경해준다.

이제 계산식을 나타내는 TextView와 결과를 보여주는 TextView를 만들자

    <View
        android:id="@+id/topLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/keypadTableLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_weight="1" />

    <TextView
        android:id="@+id/txt_expression"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginTop="44dp"
        android:layout_marginEnd="15dp"

        android:gravity="end"
        android:textColor="@color/black"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/txt_result"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginEnd="15dp"
        android:layout_marginBottom="15dp"
        android:gravity="end"

        android:textColor="#aaaaaa"
        android:textSize="20sp"

        app:layout_constraintBottom_toTopOf="@id/keypadTableLayout"
        app:layout_constraintEnd_toEndOf="parent"

        app:layout_constraintStart_toStartOf="parent" />

위와 같이 2개의 textView 선언을해준다 .


이쁘게 잘 만들어진다 !

계산기 기능 만들기

MainActivity.kt

버튼에 연결되어있는 함수들을 작성해본다.
앞에서 모든 버튼들에게 id를 부여하지 않았기 때문에 버튼을 부여하자!

숫자들은 btn_0 btn_1 ...btn_9 이런식으로 달았고
연산자들은 btn_plus , btn_minus, btn_div, btn_mod 이렇게 지정했다.

buttonClicked() 함수

버튼이 여러개기 때문에 when 문을 이용해서 각각 버튼마다 명령을 지정해준다.

    fun buttonClicked(v: View) {
        when (v.id) {
            R.id.btn_0 -> numberButtonClicked("0")
            R.id.btn_1 -> numberButtonClicked("1")
            R.id.btn_2 -> numberButtonClicked("2")
            R.id.btn_3 -> numberButtonClicked("3")
            R.id.btn_4 -> numberButtonClicked("4")
            R.id.btn_5 -> numberButtonClicked("5")
            R.id.btn_6 -> numberButtonClicked("6")
            R.id.btn_7 -> numberButtonClicked("7")
            R.id.btn_8 -> numberButtonClicked("8")
            R.id.btn_9 -> numberButtonClicked("9")
			
            R.id.btn_plus -> operatorButtonClicked("+")
            R.id.btn_minus-> operatorButtonClicked("-")
            R.id.btn_multi -> operatorButtonClicked("X")
            R.id.btn_div -> operatorButtonClicked("/")
            R.id.btn_mod -> operatorButtonClicked("%")
        }
    }

string을(숫자) 인자로 가지는 nubmerButtonClicked() 함수를 만들어 주고 숫자버튼이눌렸을때 숫자를 처리하는 함수가 실행되게 한다.

string을(연산자) 인자로 가지는 operatorButtonClicked()함수를 만들어 주고 연산자 버튼이 눌렸을때 연산자를 처리하는 함수가 실행되게 한다.

class MainActivity : AppCompatActivity() {

    private val expressionTextView: TextView by lazy {
        findViewById<TextView>(R.id.txt_expression)
    }
    private val resultTextView: TextView by lazy {
        findViewById<TextView>(R.id.txt_result)
    }

TextView들을 선언해준다.

numberButtonClicked()

    private fun numberButtonClicked(number: String) {
        if (isOperator){
            expressionTextView.append(" ")
        }
        isOperator =false

        val expressionText = expressionTextView.text.split(" ")
        if(expressionText.isNotEmpty()&& expressionText.last().length >= 15){
            Toast.makeText(this,"15자리 까지만 사용할수 있습니다.",Toast.LENGTH_SHORT).show()
            return
        } else if (expressionText.last().isEmpty()&& number =="0"){
            Toast.makeText(this,"0은 제일앞에 올 수 없습니다.",Toast.LENGTH_SHORT).show()

        }
        expressionTextView.append(number)
        // TODO resultTextView실시간으로 계산결과를 넣음
        resultTextView.text = calculateExpression()
    }

계산식에서 숫자와 연산자를 공백으로 구분한다. 따라서
split함수를 이용해서 계산식의 string을 공백으로 자른다.

expressionText를 선언.

hasOperator - 계산식에 operator가 있는지 없는지
isOperator - operator를 작성하고있는지 아닌지
변수를 선언한다.

연산자가 선언되고 난뒤에 숫자를 기입하면 한칸 띄워주고 숫자를 추가해준다.

계산식의 숫자가 15자리이상이면 Toast메세지를 출력해준다.

숫자칸이 비어있을때 0이 최초로 입력되면 Toast메세지를 출력해준다.

operatorButtonClicked()

expressionTextView.text = text.dropLast(1)

text의 맨마지막 글자 하나를 drop해준다(없에준다). 숫자를 늘린만큼 사라진다.

이걸로 계산식에 operator가 있는데 다시 operator를 누르면 전의 opertaor를 없에주고 새로운 operator을 추가해준다.

계산식에서 연산자가 이미 쓰인경우는 Toast 메세지를 띄워주고 끝낸다.

뒤 두개가 다 아닌경우에는 textView에 한칸띄고 operator를 입력해준다.

SpannableStringBuilder()

text를 인자로 받는 함수이다. String의 색깔을

setSpan을 통헤서 지정한 text값을 통해 text값의 색을 변경해줄수있다.

val ssb = SpannableStringBuilder(experssionTextView.text)
ssb.setSpan(ForegroundColorSpan(getColor(R.color.green)),
expressionTextView.text.lenght-1, expressionTextView.text.lenght, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

를 입력된 계산식에서 현재 operator 가 입력될때 text의 맨마지막 인 operator의 색깔을 green으로 바꿔준 ssb라는 text를 setSpan하주는 것이다.

operator가 입력이됬기때문에 계산식에 operator가 있다는 isOperator와 hasOperator를 True로 바꿔준다.

calculateExpression()

    private fun calculateExpression():String{
        val expressionTexts = expressionTextView.text.split(" ")

        if(hasOperator.not() || expressionTexts.size != 3){
            return ""
        }
        else if (!expressionTexts[0].isNumber().not() || expressionTexts[2].isNumber().not()){
            return ""
        }
        val exp1 = expressionTexts[0].toBigInteger()
        val exp2 = expressionTexts[2].toBigInteger()
        val op = expressionTexts[1]

        return when(op){
            "+" ->(exp1+exp2).toString()
            "-"->(exp1-exp2).toString()
            "X"->(exp1*exp2).toString()
            "%"-> (exp1%exp2).toString()
            "/"->(exp1/exp2).toString()
            else-> ""
        }
    }

계산식에 있는 숫자와 연산자를 가지고와서 계산을 해준 String 반환해주는 함수

아직 연산자를 입력하지 않았거나 2개의 숫자요소와 1개의 연산자가 없으면 빈 String를 return 해줘서 예외처리를 해준다.

그리고 계산식의 첫번째 요소(숫자)와 세번째 요소(숫자)가 숫자가 아닌경우를 위해서 숫자임을 확인하는 함수를 만든다. kotlin에는 isNumber함수가 없다. 따라서 만든다!!

String.isNumber()

fun String.isNumber():Boolean{
    return try{
        this.toBigInteger()
        true
    }catch (e:NumberFormatException){
        false
    }
}

String을 확장하는 확장함수를 만든다.
앞에 객체가 오고 .함수명 으로 하면 객체를 확장하는 함수를 만들수있다. 오호!

String을 toBigInteger()로 Integer로 만든다(BigInteger는 무한대까지 변환가능)

try catch 문으로 toBigInteger가 성공하면 true를 반환하고 제대로 치환이 안되면 NumberFormatException을 반한하기때문에
NumberFormatException이 발생했을때 false를 반환하게 한다!!

위 함수로 숫자여야되는 값이 숫자가 아닌경우 빈 String를 반환해준다.

return 문제 when을 사용해서 op값에 따라서 계산을 해준 값을 return 해준다. return문에 바로 when문을 이용하는거 매우 좋은거같다.

여기까지하면 숫자와 연산자가 입력이된다
clear x 계산결과 x

clearButton()

    fun clearButtonClicked(v: View) {
        expressionTextView.text=""
        resultTextView.text =""
        isOperator =false
        hasOperator = false
    }

계산식과 결과창을 빈string으로 만들어주고 isOperator와 hasOperator를 false로 바꿔주는 것으로 끝!

resultButtonClicked()

    fun resultButtonClicked(v: View) {
        val expressionTexts = expressionTextView.text.split(" ")
        if (expressionTextView.text.isEmpty() || expressionTexts.size == 1) {
            return
        }
        if (expressionTexts.size != 3 && hasOperator) {
            Toast.makeText(this, "수식을 완성해주세요", Toast.LENGTH_SHORT).show()
            return
        }
        if (expressionTexts[0].isNumber().not() || expressionTexts[2].isNumber().not()) {
            Toast.makeText(this, "오류가 발생했습니다.", Toast.LENGTH_SHORT).show()

            return
        }
        val expressionText = expressionTextView.text.toString()
        val resultText = calculateExpression()

        resultTextView.text =""
        expressionTextView.text = resultText

        isOperator = false
        hasOperator = false 

    }

연산결과를 보여주는 함수 result버튼을 누르면 예외처리로 Toast메세지를 띄워준다.

첫번째 예외처리 - 계산식이 입력되지 않았거나 숫자만 하나 입력됬을때
두번째 예외처리 - 숫자와 연산자만 입력되었을때
세번째 예외처리 - 숫자로 입력받아야할 것들이 숫자가 아닐경우

예외처리가 끝난뒤에는 계산식 textView에는 결과를 넣어주고 결과창은 빈칸으로 바꿔준다.

expressionText와 resultText를 변수로 만들어서 저장한것은 따로 DB에 저장해주기위해서이다.

MainActivity.kt 전체 코드

class MainActivity : AppCompatActivity() {

    private val expressionTextView: TextView by lazy {
        findViewById<TextView>(R.id.txt_expression)
    }
    private val resultTextView: TextView by lazy {
        findViewById<TextView>(R.id.txt_result)
    }

    private var isOperator = false
    private var hasOperator = false

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

    }

    fun buttonClicked(v: View) {
        when (v.id) {
            R.id.btn_0 -> numberButtonClicked("0")
            R.id.btn_1 -> numberButtonClicked("1")
            R.id.btn_2 -> numberButtonClicked("2")
            R.id.btn_3 -> numberButtonClicked("3")
            R.id.btn_4 -> numberButtonClicked("4")
            R.id.btn_5 -> numberButtonClicked("5")
            R.id.btn_6 -> numberButtonClicked("6")
            R.id.btn_7 -> numberButtonClicked("7")
            R.id.btn_8 -> numberButtonClicked("8")
            R.id.btn_9 -> numberButtonClicked("9")

            R.id.btn_plus -> operatorButtonClicked("+")
            R.id.btn_minus -> operatorButtonClicked("-")
            R.id.btn_multi -> operatorButtonClicked("X")
            R.id.btn_div -> operatorButtonClicked("/")
            R.id.btn_mod -> operatorButtonClicked("%")
        }
    }

    private fun numberButtonClicked(number: String) {
        if (isOperator) {
            expressionTextView.append(" ")
        }
        isOperator = false

        val expressionText = expressionTextView.text.split(" ")
        if (expressionText.isNotEmpty() && expressionText.last().length >= 15) {
            Toast.makeText(this, "15자리 까지만 사용할수 있습니다.", Toast.LENGTH_SHORT).show()
            return
        } else if (expressionText.last().isEmpty() && number == "0") {
            Toast.makeText(this, "0은 제일앞에 올 수 없습니다.", Toast.LENGTH_SHORT).show()
            return
        }
        expressionTextView.append(number)
        resultTextView.text = calculateExpression()
    }

    private fun operatorButtonClicked(operator: String) {
        if (expressionTextView.text.isEmpty()) {
            return
        }

        when {
            isOperator -> {
                val text = expressionTextView.text.toString()
                expressionTextView.text = text.dropLast(1) + operator
            }
            hasOperator -> {
                Toast.makeText(this, "연산자는 한번만 사용할 수 있습니다.", Toast.LENGTH_SHORT).show()
                return
            }
            else -> {
                expressionTextView.append(" $operator")
            }

        }
        val ssb = SpannableStringBuilder(expressionTextView.text)
        ssb.setSpan(
            ForegroundColorSpan(getColor(R.color.green)),
            expressionTextView.text.length - 1, expressionTextView.text.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        expressionTextView.text = ssb
        isOperator = true
        hasOperator = true
    }

    fun resultButtonClicked(v: View) {
        val expressionTexts = expressionTextView.text.split(" ")
        if (expressionTextView.text.isEmpty() || expressionTexts.size == 1) {
            return
        }
        if (expressionTexts.size != 3 && hasOperator) {
            Toast.makeText(this, "수식을 완성해주세요", Toast.LENGTH_SHORT).show()
            return
        }
        if (expressionTexts[0].isNumber().not() || expressionTexts[2].isNumber().not()) {
            Toast.makeText(this, "오류가 발생했습니다.", Toast.LENGTH_SHORT).show()

            return
        }
        val expressionText = expressionTextView.text.toString()
        val resultText = calculateExpression()

        resultTextView.text =""
        expressionTextView.text = resultText

        isOperator = false
        hasOperator = false

    }


    private fun calculateExpression(): String {
        val expressionTexts = expressionTextView.text.split(" ")

        if (hasOperator.not() || expressionTexts.size != 3) {
            return ""
        } else if (expressionTexts[0].isNumber().not() || expressionTexts[2].isNumber().not()) {
            return ""
        }
        val exp1 = expressionTexts[0].toBigInteger()
        val exp2 = expressionTexts[2].toBigInteger()
        val op = expressionTexts[1]

        return when (op) {
            "+" -> (exp1 + exp2).toString()
            "-" -> (exp1 - exp2).toString()
            "X" -> (exp1 * exp2).toString()
            "%" -> (exp1 % exp2).toString()
            "/" -> (exp1 / exp2).toString()
            else -> ""
        }
    }

    fun clearButtonClicked(v: View) {
        expressionTextView.text = ""
        resultTextView.text = ""
        isOperator = false
        hasOperator =false
    }

    fun historyButtonClicked(v: View) {
    }
}

fun String.isNumber(): Boolean {
    return try {
        this.toBigInteger()
        true
    } catch (e: NumberFormatException) {
        false
    }
}

계산도 잘되고 예외 처리도 잘된다.

새로 배운 것

Spannable

Spannable은 텍스트에 부분적으로 효과를 적용할때 사용된다.

setSpan( 적용할 효과, 효과의 시작, 효과의 끝, 문자열 추가시 span값 적용 유뮤) 의 인자를 가지고있다.
효과
ForegroundColorSpan- 색깔
UnderlineSpan - 밑줄
AbsoluteSizeSpan - 텍스크 크기

효과지정 시작과끝 - 배열과같이 시작값은 0

마지막 인자 - span이 적용된 좌우측에 문자열이 추가될때 이 span값을 적용할 것인지 아닌지 정한다.

SPAN_EXCLUSIVE_EXCLUSIE : 왼쪽제거, 오른쪽 제거
SPAN_EXCLUSIVE_INCLUSIVE : 왼쪽 제거 오른쪽 포함
위처럼 EXCLUSIVE는 제거 , INCLUSIVE는포함으로 표현횐다.

onClick ViewBind

layout에서 버튼이나 다른 onClick이되는 것들에 대해서

android :onClick="액티비티에 정의해둔 함수"

로 함수를 연결해준다. 액티비티에서 onClickListner를 사용하지 않아도 된다.

Activity에서는

함수명(v View) { 원하는 기능 }

현재 View를 참조해서 함수를 만든다.
버튼이 많은 경우에는 이런방식이 확실히 유용한듯 잘 알아두자

Text Drop

expressionTextView.text = text.dropLast(1)

위와 같이 TextView의 text의 글자를 원하는 만큼 drop할 수 있다.

확장함수

fun String.isNumber():Boolean{ }
이처럼 String객체를 확장하는 함수를 만들어줄 수가있다.
isNumber처럼 없는 기능을 만들어 볼수도 있고 여러가지 쓸수있을거같다.
제대로 활용하려면 많이써봐야 할듯..

잘 만들어졌다.

다음 강의에서는 결과랑 계산식을 저장해서 history 기능을 구현한다고 한다. 화이팅~

profile
한걸음씩 위로 자유롭게

0개의 댓글