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

한상욱·2023년 5월 29일
1

들어가며

이전 포스팅에서 아주 길게 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개의 댓글