[내배캠 Android 4기] TIL 0606

오리너구리·2024년 6월 6일
0

TIL

목록 보기
26/48
post-thumbnail

오늘 계획

  • 코드카타 30번
  • 과제 진행
  • 레이아웃 학습

코드카타 30번

⏲️ 공부시간 11 : 20 ~ 12 : 00

오늘은 문자열에 substring 함수를 사용해서 원하는 인덱스의 문자열만 빼내는 문제를 풀었당

문제를 풀면서 어떤 함수가 있는지 하나씩 공부해 볼 수 있어서 좋은 것 같당

오늘 푼 문제/내 풀이/풀이 과정/다른사람풀이가 궁금하다면 링크!
https://velog.io/@orinugoori_art/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-CODEKATA-30-%EA%B0%80%EC%9A%B4%EB%8D%B0-%EA%B8%80%EC%9E%90-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0


과제 진행

수정사항 보완

⏲️ 공부시간 12 : 00 ~ 14 : 00

어제 생각한 보완할 부분

  1. 에러메세지가 뜰 때 혹은 계산식이 너무 길어지거나 할 때, 텍스트가 밑으로 내려가버림

  2. 연산자를 하나밖에 구분못함

    1+2-3 하면 0이 되어야하는데 마지막에 입력한 연산자 기준으로 모든 숫자를 처리해버림!

  1. 연산자를 연속으로 여러개 입력가능함

    +-+- 이렇게도 입력이되어버림.. 연산자를 입력하고 만약에 다른 연산자를 입력하면 그 전에 입력한 연산자는 없어지게 해야할듯..하..

  1. =을 누르면 입력한 식은 사라지고 답만 나오는데 입력했던 식이 위에 작게 표시되어도 괜찮을 것같다.

  2. 답으로 너무 너무 큰숫자나오면( Int 범위 벗어난 숫자) 어떻게 처리할지 결정하기

1. 텍스트가 길어질 때 텍스트 위치 조정

보완 이유 :

텍스트가 2줄로 넘어가게되면 텍스트가 디스플레이 영역을 넘어감

    <TextView
        android:id="@+id/tv_display"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:fontFamily="@font/gpz_video_tape"
        android:gravity="end|bottom"
        android:maxLines="2"
        android:paddingEnd="16dp"
        android:paddingStart="16dp"
        android:paddingBottom="5dp"
        android:paddingTop="5dp"
        android:text="Hello"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="@id/iv_background"
        app:layout_constraintEnd_toEndOf="@id/iv_background"
        app:layout_constraintStart_toStartOf="@id/iv_background"
        app:layout_constraintTop_toTopOf="@id/iv_background"
        app:layout_constraintVertical_bias="0.28"
        app:layout_constraintHeight_percent="0.11"
        app:layout_constraintWidth_percent="0.62">
    </TextView>

변경한 것 :

  • layout_width/height 를 원래 특정 크기로 지정해놨었는데 0dp 로 변경함
  • constraingHeight/Width_percent 로 디스플레이 영역에 맞춰서 텍스트뷰 위치 설정
  • gravity 를 추가해서 글씨를 오른쪽 아래로 정렬함
  • 디스플레이 배경이랑 텍스트 박스의 크기를 거의 비슷하게 설정했기 때문에 padding을 살짝 줬음
  • maxLine을 2 로 설정

2. 연산자 연속 입력 제한

보완 이유 :

연산자를 연속으로 입력할 때 그냥 연산자가 추가되는데 말이 안되는 식이기 때문에 제한하려고함!

val numberButtonList = listOf(
            binding.ibtnSeven,
            binding.ibtnEight,
            binding.ibtnNine,
            binding.ibtnFour,
            binding.ibtnFive,
            binding.ibtnSix,
            binding.ibtnOne,
            binding.ibtnTwo,
            binding.ibtnThree,
            binding.ibtnZero,
        )

        val operatorButtonList = listOf(
            binding.ibtnDot,
            binding.ibtnPlus,
            binding.ibtnMinus,
            binding.ibtnMultiple,
            binding.ibtnDivide,
            binding.ibtnMod
        )

        for (button in numberButtonList) {
            button.setOnClickListener {
                addCurrentInput(button.contentDescription.toString())

            }
        }

        for (button in operatorButtonList){
            button.setOnClickListener {
                addOperatorInput(button.contentDescription.toString())
            }
        }
        
        //중간 생략//
        
        private fun addCurrentInput(letter: String) {
        currentInput += "$letter"
        displayText.text = currentInput
    }

    private fun addOperatorInput(unduplicableLetter: String){
        if( currentInput.isNotEmpty() && "+-*/%.".contains(currentInput.last())){
            currentInput = currentInput.dropLast(1) + unduplicableLetter
        }else{
            currentInput += unduplicableLetter
        }
        displayText.text = currentInput
    }

변경된 점 :

  • buttonList 에 다 들어가있던 계산기 버튼을 숫자버튼과 연산자와 . 같이 중복 입력을 제한하는 버튼으로 분리함
  • 중복입력불가능한 버튼을 누르면 실행할 addOperatorInput 함수를 생성
  • addOperatorInput 은 현재 입력된 텍스트가 있고, 그 중에 연산자와 . 을 마지막에 가지고 있을때는 마지막 글자하나를 지우고 현재 입력한 글자를 입력하게 하고, 그렇지 않으면 그냥 입력한 글자를 입력하게함으로써 중복입력을 제한함

3. 식에 다양한 연산자가 들어갈 때에 올바른 처리

보완 이유 :

1+2-1 이런식으로 식에 연산자가 여러개가 들어가면 답이 2가 되어야하는데

마지막으로 들어간 연산자인 -만 인식하고 1-2-1 로 계산하고 -2를 뱉어버림 도라인가

예쁘기만하고 반쯤 바보인 계산기를 만드는것도 신박하긴 할 수도있겠지만 일단은 고쳐보려고함..

계산기가 멍청한 이유 :

for (i in operators.indices) {
            result = when(operators[i]){
                "+" -> calculator.add(numbers)
                "-" -> calculator.subtract(numbers)
                "*" -> calculator.multiply(numbers)
                "/" -> calculator.divide(numbers)
                "%" -> calculator.mod(numbers)
                else -> {
                    displayText.text = "Error"
                    return
                }
            }
        }

여기 코드가 연산자를 구분해서 어떤 함수를 실행할지 결정하는 부분인데

반복하는 과정에서 numbers (입력받은 식에서 숫자만 저장해논 리스트) 전체를 넣고 계산하고,

다음 연산자를 가면 또 그 연산자에 전체를 넣고 계산해서 넣고 이런식이라 마지막 연산자로만 계산되는 거였음! 내가 널 바보로 만들었구낭~! ㅎㅣ 히 내가 바보는 나야 계산기가 아니라 ~

수정된 코드

Calculator.kt

class Calculator {

    fun add(numbers: List<Number>): Number{
        return if (numbers.isEmpty()) {
            0
        } else {
            val sum = numbers.sumOf { it.toDouble() }

            if (sum % 1.0 == 0.0) {
                sum.toInt()
            } else {
                sum
            }
        }
    }

    fun subtract(numbers: List<Number>): Number {

        var calculateNumber = numbers[0].toDouble()

        for (i in 1 until numbers.size) {
            calculateNumber -= numbers[i].toDouble()
        }

        return if (calculateNumber % 1.0 == 0.0) {
            calculateNumber.toInt()
        } else {
            calculateNumber
        }

    }

    fun multiply(numbers: List<Number>): Number {
        var calculateNumber = numbers[0].toDouble()
        for (i in 1 until numbers.size) {
            calculateNumber *= numbers[i].toDouble()
        }
        return if (calculateNumber % 1.0 == 0.0) {
            calculateNumber.toInt()
        } else {
            calculateNumber
        }
    }

    fun divide(numbers: List<Number>): Number {
        var quotient = numbers[0].toDouble()

        for (i in 1 until numbers.size) {
            quotient /= numbers[i].toDouble()
        }

        return if (quotient % 1.0 == 0.0) {
            quotient.toInt()
        } else {
            quotient
        }
    }

    fun mod(numbers: List<Number>): Number {
        var calculateNumber = numbers[0].toDouble()
        for (i in 1 until numbers.size) {
            calculateNumber %= numbers[i].toDouble()
        }
        return if (calculateNumber % 1.0 == 0.0) {
            calculateNumber.toInt()
        } else {
            calculateNumber
        }
    }

}

변경된 부분 :

  • 각 연산 함수의 반환 타입을 Stirng → Number로 변경하고
  • return 에 toString() 함수 제거해줌

저렇게 수정한 이유 :

  • 하나의 식에서 다양한 연산자를 복합적으로 계산해야 할 때 for문으로 operator의 인덱스에 맞춰서 순서대로 계산해야하는데 String 타입으로 반환되면 다시 Number 타입으로 반환해줘야 하기 때문에 연산함수는 Number를 반환하고 나중에 최종 결과를 표시 할 때에만 String으로 변환하도록 했음

MainActivity.kt calculateResult 함수부분

    private fun calculateResult() {
        val numbers = stringAnalyze.filterNumbers(currentInput)
        val operators = stringAnalyze.filterOperators(currentInput)

        if (numbers.isEmpty() || operators.isEmpty()){
            displayText.text = "there's no number"
            return
        }

        var calculateResult = listOf(numbers[0])

        for (i in operators.indices) {
            val nextNumber = numbers[i+1]
            val currentNumbers = listOf(calculateResult.last(), nextNumber)

            calculateResult = listOf(
                when (operators[i]) {
                    "+" -> calculator.add(currentNumbers)
                    "-" -> calculator.subtract(currentNumbers)
                    "*" -> calculator.multiply(currentNumbers)
                    "/" -> calculator.divide(currentNumbers)
                    "%" -> calculator.mod(currentNumbers)
                    else -> {
                        displayText.text = "Error"
                        return
                    }
                }

            )
        }

        val result = calculateResult.toString().removeSurrounding("[","]")

            displayText.text = result
            currentInput = result
        }

    }

변경된 부분 :

  • currentNumbersnextNumber 변수를 만들어서 연산함수에 게산하는 순서에 맞는 숫자가 들어갈 수 있게 변경했다.
  • Calculator의 함수들의 반환타입이 String에서 Number로 바뀌었기 때문에 결과를 표시할 때 String으로 변환해주도록 추가했다.

4. 계산식 표시

보완 이유 :

  • 나같이 금붕어 같은 사람이 쓸 때 내가 무슨 식 썼었는지 방금 써놓고 까먹을 수 있으니깐 계산식이 뭐였는지 확인 할 수 있어야 좋을 것 같아서

보완 방향 :

근데 이거 하려면 지금 당장은 Display 크기가 작아서 디스플레이를 다시 그리던가, 계산식만 표시하는 디스플레이 하나를 추가하던가 해야할 것 같음..

디자인을 수정해야된다는건데 일단은 과제로 하는 거기 때문에 과제를 다 하고나면 그 때 수정해야 할 것 같다.

5. 입력가능한 숫자 범위 지정하기.

보완 이유 :

지금 Int와 Double 형의 타입을 사용하고 있는데, 계산기에 Int범위가 넘는숫자를 입력해도 제한하지 않아서 답의 크기가 Int 형 보다 커지면 그냥 Int의 최대값을 표시하기 때문에 조치가 필요함!

보완 방향 :

  • 입력 타입은 Int Double 그대로 할건데 대신 일정 자릿수를 초과하면 더 이상 입력하지 못하게 제한하면서, 메세지로 00자릿수 이상 입력할 수 없습니다. 띄우기
  • 반환타입은 Long으로 바꿔줄까..?

이것도 근데 당장 하지않고 일단 구현할 기능을 다 구현하고 수정해야겠다.

Lv 3

⏲️ 공부시간 14 : 00 ~ 15 : 30

  • Lv3
    AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스를을 만든 후 클래스간의 관계를 고려하여
    Calculator 클래스와 관계를 맺기
    - 관계를 맺은 후 필요하다면 Calculator 클래스의 내부 코드를 변경하기
    - 나머지 연산자(%) 기능은 제외합니다.
    - Lv2 와 비교하여 어떠한 점이 개선 되었는지 스스로 생각해 봅니다.
    - hint. 클래스의 책임(단일책임원칙)

  • Lv3 에서 해야 할 일 정리

    1. Calculator 클래스안의 함수들을 클래스로 바꿔주기
    2. 클래스로 변환한 클래스들을 Calculator와 관계 맺어주기
    3. 어떤 점이 개선되었는지 생각해보기

일단 기능적으로 변하는건 없고 클래스를 더 세분화해서 하나의 클래스가 하나의 기능을 하도록 정리해주는 단계인 것 같다.

Lv 3 구현

Operation Interface 생성

interface Operation {
    fun apply(numbers:List<Number>):Number
}

Calculator의 클래스에 들어있는 연산 함수들을 분리 시킬건데

연산 함수들은 모두 Number 형태의 리스트를 받아서 Number를 반환하기 때문에 이렇게 인터페이스를 만들었다.

각 연산 별로 클래스 생성

class AddOperation : Operation {

     override fun apply(numbers: List<Number>): Number{

        return if (numbers.isEmpty()) {
            0
        } else {
            val sum = numbers.sumOf { it.toDouble() }

            if (sum % 1.0 == 0.0) {
                sum.toInt()
            } else {
                sum
            }
        }
    }
}

각 연산하는 함수를 새로운 클래스로 각각 생성해서

Operation 인터페이스를 상속? 받아서 apply라는 이름의 함수로 다시 구현해줬다.

Calculator 클래스 수정

open class Calculator {

    private val operations = mapOf<String, Operation>(
        "+" to AddOperation(),
        "-" to SubstractOperation(),
        "*" to MultiplyOperation(),
        "/" to DivideOperation(),
        "%" to ModOperation()
    )

    fun calculate(numbers: List<Number>, operator : String): Number {
        val operation = operations[operator] ?: throw IllegalArgumentException ("invalid operator")
        return operation.apply(numbers)
    }
}

원래 있던 함수들이 다 빠지고 빈 Calculator 클래스에

  • operations 라는 변수를 만들어 각 연산자와 매칭되는 연산클래스들을 맵핑해주고,
  • 숫자리스트와 연산자를 받아서, operations 에 맵핑되어있는 대로 연산을 실행하게 만드는calculate 함수를 만들었다.

MainActivity.kt 의 calculateResult() 수정

private fun calculateResult() {
        val numbers = stringAnalyze.filterNumbers(currentInput)
        val operators = stringAnalyze.filterOperators(currentInput)

        if (numbers.isEmpty() || operators.isEmpty()){
            displayText.text = "there's no number"
            return
        }

        var calculateResult: Number = numbers[0]

        for (i in operators.indices) {
            val nextNumber = numbers[i+1]
            val currentNumbers = listOf(calculateResult, nextNumber)

            calculateResult = calculator.calculate(currentNumbers, operators[i])

        }

        val result = calculateResult.toString().removeSurrounding("[","]")

            displayText.text = result
            currentInput = result
        }

원래 for문 안에서 when으로 연산자를 구분해서 Calculator의 함수를 실행했는데, 그 기능을 Calculator에 넣었기 때문에 그냥 calculator.calculate에 계산할 숫자와, 연산자를 넣어준느 걸로 간단하게 바뀌었다.

Lv 3 을 구현하고 개선된 점

일단 나도 원래의 코드를 만들면서, MainActivity에서

연산자를 구분하는 기능을 넣는게 너무 지저분하고 기능이 분류가 제대로 안되는 것 같아서 불편했는데 어떻게 해결해야할지 골치가 아파서 일단은 자세만 고쳐앉았었는데,

Calculator 에 있던 연산 함수들이 각자의 클래스로 빠지고 그 과정에서 Interface를 사용해서 각각의 연산 클래스를 같은 인터페이스로 구현하니까, 빈 Calculator 클래스에 연산자를 구분해서 계산을 진행하는 코드를 넣으면 딱 맞겠다고 생각했다.

각 연산이 클래스로 따로 파일이 있어서 찾기도 수월한 것 같고,

전 코드에 비해서 코드가 보기 쉬워진 것 같다.

Lv4

⏲️ 공부시간 15 : 30 ~ 16 : 20

  • Lv4
    AddOperation(더하기), SubtractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스들을
    AbstractOperation라는 클래스명으로 만들어 사용하여 추상화하고
    Calculator 클래스의 내부 코드를 변경합니다.
    - Lv3 와 비교해서 어떠한 점이 개선 되었는지 스스로 생각해 봅니다.
    - hint. 클래스간의 결합도, 의존성(의존성역전원칙)

Lv 4 는 AbstractOperation이라는 추상 클래스를 만들어서 추상화 하면 될듯??

Lv 4 구현

추상 클래스 생성

abstract class AbstractOperation {
    protected fun analyzeResult(result : Number) :Number {
        return if (result.toDouble() % 1.0 == 0.0 ) result.toInt() else result
    }

    abstract fun apply (numbers : List<Number>) :Number

}
  • 추상 클래스 파일 AbstractOperation 생성
  • 모든 연산 클래스가 가지고있는 계산 결과가 정수인지 실수인지 판단하는 부분을 analyzeResult 함수로 만들어서 추상클래스에 넣어줌
  • apply 함수를 추상클래스에 넣어줌

연산클래스 수정

class AddOperation : AbstractOperation() {

     override fun apply(numbers: List<Number>): Number{
         val calculatorNumber = numbers.sumOf { it.toDouble() }
         return analyzeResult(calculatorNumber)
     }
}
  • 원래 Interface를 구현했는데, 이번에 생성한 추상클래스를 상속하는걸로 변경함
  • 결과를 실수인지 정수인지 판단하는 부분을 삭제하고 대신 return을 analyzeResult에 계산한 결과를 넣게 변경함

Calculator 클래스 수정

open class Calculator {

    private val operations = mapOf<String, AbstractOperation>(
        "+" to AddOperation(),
        "-" to SubstractOperation(),
        "*" to MultiplyOperation(),
        "/" to DivideOperation(),
        "%" to ModOperation()
    )

    fun calculate(numbers: List<Number>, operator : String): Number {
        val operation = operations[operator] ?: throw IllegalArgumentException ("invalid operator")
        return operation.apply(numbers)
    }
}

연산자와 연산클래스를 맵핑하는 과정에 타입을 추상클래스 이름으로 변경해줌

생성했던 Operation Interface를 삭제해줌

Lv 4를 구현하고 개선된 점

인터페이스를 활용해서 추상화 했을 때는, Interface 안에는 따로 함수 내용을 넣지 않는게 권장되기 때문에 결과값이 정수인지 실수 인지 판단을 각 연산클래스에서 진행했는데,

추상클래스에는 공통되는 함수를 같이 넣어놔도 되는 점을 이용해서

analyzeResult 라는 함수를 만들어서 정수와 실수를 판단해서 Int와 Double 로 결과값을 구분해주는걸 따로 뺐다.

이렇게 하니까 각 연산 클래스의 코드가 간단해지고, 딱 연산만 담당할 수 있게 된 것 같아서 속이 시원해짐.

추가보완

⏲️ 공부시간 17 : 00 ~19 : 20

화면 크기에 대응할 수 있게 UI 수정

보완 이유 :

레이아웃 배치할 때 절대적인 크기로 지정해놓았더니 화면크기가 바뀌면 완전 이상해짐

display 이미지

  <ImageView
        android:id="@+id/iv_display"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scaleType="fitXY"
        android:src="@drawable/iv_display"
        app:layout_constraintBottom_toBottomOf="@+id/iv_background"
        app:layout_constraintEnd_toEndOf="@+id/iv_background"
        app:layout_constraintHeight_percent="0.2"
        app:layout_constraintStart_toStartOf="@+id/iv_background"
        app:layout_constraintTop_toTopOf="@id/iv_background"
        app:layout_constraintVertical_bias="0.3"
        app:layout_constraintWidth_percent="0.8">

    </ImageView>
  • 원래 크기를 지정해놨었는데 0dp로 변경해줌
  • 그리고 scaleType을 fitXY로 지정하고,
  • constraintwidth/height_percent 를 사용해서 크기를 조절해줬다.
  • constraintVertical/horizontal_bias 를 사용해서 위치를 조졍해줌

display text의 크기와 위치도 같은 방식으로 상대적으로 바뀌도록 설정해줬다.

근데 버튼 크기랑 배열은 손을 못대겠다…

이게 다 png 파일 넣었을 때 딱 이미지 크기대로가 아니라 대지 영역이 같이 인식돼서 그럼..슈발

맞추기 너무 힘들다.

아무리 찾아봐도.. 빈 공간영역을 같이 안잡는 방법이 안나옴

내가 절대적인 크기를 지정해주면 가능한데, 그러면 화면크기 바뀌는거에 대응이 안되잖아요;;

ㅜㅜㅜ 애초에 저장할 때 잘 저장해야하는건지..??아니면 png 말고 svg로 저장하면 ㄱㅊ은가??

일단 포기…

가로로 된 화면에는 어떻게 하는지도 찾아봤는데,

가로용 액티비티를 따로 작성해주는것같다.

그래서 일단은 세로용 액티비티를 잘 만질 수있으면 그다음에 가로화면을 하던가해야할듯.

지금 당장 세로용도 버튼배열 못해서 난리남..

버튼 누를 때 효과음 변경

보완 이유 :

UI 디자인 한거는 살짝 레트로한 느낌의 계산기인데 소리가 안어울림!

더 어울리는 소리로 바꾸고싶당

효과음 변경한 방법

  1. 기본 효과음 빼기
<ImageButton
        android:id="@+id/ibtn_parenthess"
        android:layout_width="70dp"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:background="@android:color/transparent"
        android:src="@drawable/pressedmotion_parentheses"
        app:layout_constraintStart_toEndOf="@+id/ibtn_mod"
        app:layout_constraintTop_toTopOf="@id/ibtn_mod"
        app:layout_constraintBottom_toBottomOf="@+id/ibtn_mod"
        android:layout_marginStart="5dp"
        android:contentDescription="()"
        android:soundEffectsEnabled="false"
        />

soundEffectsEnabled 를 false로 설정하면 기본 효과음이 빠진다.

  1. res 폴더에 raw 폴더 만들어주고 원하는 소스 추가

  2. MainActivity에 코드 추가

private lateinit var mediaPlayer: MediaPlayer
    
mediaPlayer = MediaPlayer.create(this,R.raw.vintage)

        for (button in numberButtonList) {
            button.setOnClickListener {
                playSound()
                addCurrentInput(button.contentDescription.toString())

            }
        }
        
         private fun playSound(){
        if(mediaPlayer.isPlaying){
            mediaPlayer.stop()
            mediaPlayer.release()
            mediaPlayer = MediaPlayer.create(this, R.raw.vintage)
        }
        mediaPlayer.start()
    }

    override fun onDestroy(){
        super.onDestroy()
        mediaPlayer.release()
    }

    }

mediaplayer 라는 변수를 만들고, 거기에 MediaPlayer라는 애를 가져와서 넣고

playSound() 함수를 만들어줌 근데 나 이거는 그냥 따라한거라 쟤네가 정확히 무슨역할하는지는 안찾아봄..나중에 더 공부할랭

그리고 setOnClickListenr 에 playSound함수를 넣어서 버튼 누르면 소리나게 해줌!

근데 지금 소리 대충 찾아서 넣었더니 이것도 다시 찾아봐야할 것 같다.

잘 안어울리는 거 같음 ;

더 보완 할 점

  1. 오늘 보완 하지 못한 것들
    • 계산 결과 나올 때 계산식 표시
    • 숫자 범위 제한
  2. 괄호 구현
  3. 연산시 우선순위 구현
  4. Main의 calculateResult() 더 단순하게 수정
  5. UI 화면에 맞춰서 맞게 변경되게 더 수정
  6. 앱 킬 때 로딩화면? 도 바꾸고싶당 못생김
  7. 폰트 색상 바꾸기!!
profile
오리너구리입니다

0개의 댓글