이전 포스팅에서 아주 길게 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이 나온다면 정수로 표현할 수 있는거죠. 한번 잘되는지 볼까요?
계산과정은 이제 다 완성된것 같아요. 나머지는 다음 포스팅에서 이어서 하도록 하겠습니다.