[Flutter] IOS 계산기 클론 - 2. 계산 기능 구현

한상욱·2023년 5월 29일

들어가며

이전 포스팅에서 아주 길게 UI를 완성했습니다. 이번 시간부터 차근차근 기능을 완성해볼게요.

결과값 표시

UI중에서 결과값은 계속 변하니까 Obx를 이용해서 표현해야 됩니다. 그래서 결과값은 Rx를 이용할겁니다.

class CalculatorController extends GetxController {
  RxString _result = '0'.obs;
  
  String get result => _result.value;
}

이제, 결과값이 변하면 result를 통해서 변화를 적용할 수 있습니다.

숫자 입력

초기의 결과창은 0입니다. 하지만, 숫자가 입력되면, 결과값이 0에서 숫자로 대체되고, 그 이후에는 오른쪽에 하나씩 숫자가 더해집니다. 이를 바탕으로 버튼 기능을 만들어볼게요.

  void pushNumberBtn(String value) {
    if (_result.value[0] == '0' && _result.value.length == 1) {
      _result(''); //만약 초기상태라면 빈 문자열로 대체
    }
    _result.value += value;
  }

이렇게 작성한다면, 초기에서는 값이 대체되고, 그 후에는 오른쪽에 계속 추가되는 효과를 낼 수 있습니다. 이제 이 함수를 UI에 전달해야 적용이 됩니다.

			...
          BlackBtn(
            type: BlackBtnType.FOUR,
            onPressed: () => controller.pushNumberBtn('4'),
          ),
          BlackBtn(
            type: BlackBtnType.FIVE,
            onPressed: () => controller.pushNumberBtn('5'),
          ),
          BlackBtn(
            type: BlackBtnType.SIX,
            onPressed: () => controller.pushNumberBtn('6'),
          ),
          ...

문자열을 이용해서 굉장히 간단하게 구현했습니다.

소수점 입력

소수점을 입력하면, 점이 찍히지만, 이미 찍혀있다면 찍히지 않아야겠죠?

  void pushDotBtn() {
    if (_result.value.contains('.')) {
      return;
    }
    _result.value += '.';
  }

위 코드를 통해서 소수점 입력 기능을 구현할 수 있습니다.

			...
          BlackBtn(
            type: BlackBtnType.DOT,
            onPressed: controller.pushDotBtn,
          ),
          ...

연산 버튼 애니메이션

연산 버튼의 기능은 이미 컴포넌트를 제작할 때, 불리언 변수를 변화하면 변할 수 있도록 구성해놨습니다. 조금 생각을 해볼게요. 연산 버튼이 눌린 순간, 결과창에 보이는 숫자는 num1으로 취급하고, 그 이후에 눌린 숫자는 num2로 취급하는 것이 전체적인 과정을 생각하기 편할것 같습니다. 그래서 우리는 여러가지를 추가할거에요.

enum Calculate { PLUS, MINUS, MULTIPLY, DIVIDE, NONE }
//사측연산의 종류를 나타내는 enum 클래스

class CalculatorController extends GetxController {
  RxString _result = '0'.obs;
  num num1 = 0; //연산버튼이 눌리기 전 숫자
  num num2 = 0; //연산버튼이 눌린 후 숫자
  Calculate status = Calculate.NONE; // 초기 상태
  
  // 애니메이션을 위한 불리언 변수 추가
  bool pushCalculateBtn = false; // num1과 num2를 구분하는 경계
  RxBool _pushPlus = false.obs;
  RxBool _pushMinus = false.obs;
  RxBool _pushMultiply = false.obs;
  RxBool _pushDivide = false.obs;

  String get result => _result.value;
  
  // getter 추가
  bool get plus => _pushPlus.value;
  bool get minus => _pushMinus.value;
  bool get multiply => _pushMultiply.value;
  bool get divide => _pushDivide.value;
  ...
}

이 매개변수들을 이용해서 애니메이션 효과를 줄 수 있습니다. 우선, 버튼 클릭 함수를 만들어야겠죠? 버튼마다 각각의 효과를 내기 위해서 4가지 함수를 각각 만들겁니다. 그 중에서 나누기 버튼을 예로 들겠습니다.

  void pushDivideBtn() {
    status = Calculate.DIVIDE;
    pushCalculateBtnProgress(status); // 나누기 버튼 클릭 이벤트
  }

이 함수는 나누기 버튼 클릭 이벤트가 발동하는 트리거 역활을 합니다.

  void pushCalculateBtnProgress(Calculate type) {
    num1 = num.parse(_result.value);
    initPushCalculateStatus();

    switch (type) {
      case Calculate.PLUS:
        _pushPlus(true);
        break;
      case Calculate.MINUS:
        _pushMinus(true);
        break;
      case Calculate.MULTIPLY:
        _pushMultiply(true);
        break;
      case Calculate.DIVIDE:
        _pushDivide(true);
        break;
      case Calculate.NONE:
        break;
    }
    pushCalculateBtn = true; // num1과 num2를 구분하는 변수
  }

이 함수가 제일 중요합니다. 이 함수를 통해서 연산 버튼이 눌린 상황에서 연산버튼의 매개변수들을 모두 false로 일괄 초기화하고, 어떤 종류의 버튼이 눌렸는지에 따라서, 버튼을 활성화시킬겁니다. 이러면, 계산기의 연산버튼을 그대로 구현할 수 있습니다. 그 후, 지금까지 눌린 숫자를 모두 num1에 저장할거에요. 이제 이 함수는 버튼에 적용시켜야 합니다. 근데, 애니메이션 효과로 인해 UI가 변화하기 때문에 Obx를 이용해야 적용됩니다.

			...
          Obx(
            () => OrangeBtn(
              iconFront: BtnIconType.divide,
              iconBack: BtnIconType.divideReverse,
              isClicked: controller.divide,
              onPressed: controller.pushDivideBtn,
            ),
          ),

연산버튼 클릭 이후 숫자 버튼 클릭

다음 부분을 숫자버튼 입력 함수에 추가해야, 연산버튼의 활성화를 취소할 수 있습니다.

  void pushNumberBtn(String value) {
    if (pushCalculateBtn) { // 연산버튼이 눌렸다면,
      initResultNumber(); //결과창의 값을 초기화하고,
      initPushCalculateStatus(); //연산버튼을 모두 초기화하고,
      pushCalculateBtn = false; // 연산버튼이 눌리지 않았음을 명시.
    }

    if (_result.value[0] == '0' && _result.value.length == 1) {
      _result('');
    }
    _result.value += value;
  }

이제는 연산버튼을 클릭한 이후에 숫자를 입력하면 연산버튼의 활성화가 해제됩니다.

계산하기

이제 계산과정을 마무리할때입니다. = 버튼을 입력하게 되면, 결과창의 숫자는 num2가 되어야 합니다. 그리고 나서 계산을 마무리해야 하죠.

  void calculate() {
    num2 = num.parse(_result.value); //결과창의 숫자는 num2
    initPushCalculateStatus(); //연산버튼 초기화
    switch (status) { //현재 연산의 종류 확인 후 연산
      case Calculate.PLUS:
        _result(doubleToInt((num1 + num2).toDouble()).toString());
        break;
      case Calculate.MINUS:
        _result(doubleToInt((num1 - num2).toDouble()).toString());
        break;
      case Calculate.MULTIPLY:
        _result(doubleToInt((num1 * num2).toDouble()).toString());
        break;
      case Calculate.DIVIDE:
        if (num2 == 0) { //0으로 나누면 오류 표시
          _result.value = '오류';
          return;
        }
        _result(doubleToInt((num1 / num2).toDouble()).toString());
        break;
      case Calculate.NONE: //초기에는 아무런 효과가 없음
        break;
    }
    print(_result.value);
  }

연산과정은 이렇게 표현할 수 있습니다. 하지만, double값이 int가 될 수 있다면, 정수형으로 표현해야 해요. 그래서 doubleToInt라는 함수로 값을 확인할겁니다.

  doubleToInt(double d) {
    if (d != d.round()) {
      return d;
    }
    return d.toInt();
  }

이제는 계산 결과가 .0이 나온다면 정수로 표현할 수 있는거죠. 한번 잘되는지 볼까요?

계산과정은 이제 다 완성된것 같아요. 나머지는 다음 포스팅에서 이어서 하도록 하겠습니다.

profile
자기주도적, 지속 성장하는 개발자의 기록

0개의 댓글