-->

[Android / Java]

TextWatcher 클래스를 이용한 계산기 로직 만들기

 

* 목표 : EditText에 숫자를 입력하면 자동으로 금액이 합산되는 계산기 Class를 만들어보자.

고객의 재정상태를 입력하는 화면

설계는 다음과 같다.

Total Income - Total Expense ( detail expense...+ etc ) = Total Net Income 

먼저 상세 Expense 항목의 값들이 변화하면 그 합을 Total Expense에 나타내고 최종적으로는 Income에서 금액을 빼 Net Income을 나타내려고 한다.

저번 시간에 EditText에 addTextChangedListener() 함수를 이용해서 new TextWatcher Class를 상속받는 것을 보여주었다. 

이번에는 TextWatcher를 상속받는 Class를 생성하여 그 안에 이벤트를 작성하려고 한다.

큰 숫자를 다루기 위해서 BigDecimal Class를 이용했고 해당 로직은 100% 창작으로 이루어져 있다.. (물론 누군가 먼저 만들었을 수도..)

먼저 전체소스를 한 번 보자.

AmountTextFormatter.java

package com.m3s.skylark.converter;

import android.text.Editable;
import android.text.Selection;
import android.text.TextWatcher;
import android.widget.EditText;

import java.math.BigDecimal;
import java.text.DecimalFormat;

import cz.msebera.android.httpclient.util.TextUtils;

public class AmountTextFormatter implements TextWatcher {

    private final String TAG = this.getClass().getSimpleName().trim();

    private BigDecimal preA, A, B;
    private EditText mEditText;
    private EditText mOutputText;
    private String status;
    private String strAmount = ""; // 임시 저장값 (콤마)
    private boolean isEditing = false;

    /**
     * comma 만 적용
     * @param editText input EditText
     */
    public AmountTextFormatter(EditText editText) {
        mEditText = editText;
//        int maxLength = 23;
//        mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
    }

    /**
     * input 값을 output 에 더하거나 빼기
     * @param input
     * @param output
     * @param s
     */
    //for Calculate
    public AmountTextFormatter(EditText input, EditText output, String s) {
        mEditText = input; //input Text
        mOutputText = output; //output Text
        status = s; //Status ('+' = add, '-' = subtract)
//        int maxLength = 23;
//        mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
    }


    @Override
    public void afterTextChanged(Editable s) {
        // TODO Auto-generated method stub
        if (isEditing) return; //이미 했으면 리턴

        Editable e = mEditText.getText();
        Selection.setSelection(e, e.length()); //끝으로 커서이동
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // TODO Auto-generated method stub
        if (isEditing) return; //이미 했으면 리턴
        if (mOutputText != null) {

            boolean preANull = TextUtils.isEmpty(s.toString());

            preA = preANull ? BigDecimal.valueOf(0) : BigDecimal.valueOf(Double.parseDouble(s.toString().replaceAll("[^[0-9\\-]]", ""))); //숫자가 아닌것과 - 표시 제외
        }
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {    //텍스트가 변경될때마다 실행

        if (!s.toString().equals(strAmount)) { // StackOverflow 방지
            if (isEditing) return; //이미 했으면 리턴

            isEditing = true; //포맷팅 과정 시작

            if (mOutputText != null && status != null) { //output 존재한다면 sum 하려는 용도

                A = BigDecimal.valueOf(0); //input
                B = BigDecimal.valueOf(0); //output

                boolean inputNull = TextUtils.isEmpty(s.toString()) || TextUtils.isEmpty(s) || s.toString().equals("") || s.toString().equals("-");
                boolean outputNull = (TextUtils.isEmpty(mOutputText.getText().toString()) || mOutputText.getText().toString().equals("-"));

                //input EditText;
                A = inputNull ? BigDecimal.valueOf(0) : BigDecimal.valueOf(Double.parseDouble(s.toString().replace(",", "")));
                B = outputNull ? BigDecimal.valueOf(0) : BigDecimal.valueOf(Double.parseDouble(mOutputText.getText().toString().replace(",", "")));

                if (status.equals("+")) {
                    B = B.subtract(preA).add(A);  //바뀌기 전 숫자 빼고 Change 된 숫자 더하기
                } else if (status.equals("-")) {
                    B = B.add(preA).subtract(A); //바뀌기 전 숫자 더하고 Change 된 숫자 빼기
                }

                mOutputText.setText(makeStringComma(String.valueOf(B))); //output에 삽입

            }

            strAmount = makeStringComma(s.toString()); //컴마붙이기

            mEditText.setText(strAmount);
            isEditing = false;
        }
    }

    protected String makeStringComma(String inputStr) {    // 천단위 콤마 처리
        if (TextUtils.isEmpty(inputStr))
            return inputStr;

        String str = inputStr.replace(",", ""); //기존 컴마 제거
        BigDecimal bigDecimal = new BigDecimal(str);
        DecimalFormat format = new DecimalFormat("###,###"); //포맷팅

        String returnStr = format.format(bigDecimal);
        return returnStr; //리턴
    }

}

두 개의 생성자를 만들어 하나를 컴마만 적용하고 하나는 합과 차를 구한다.

생성자에 주석 친 부분은 MaxLength를 만들어 적용하는 부분이고 취향 껏 하면 된다.

이 코드의 포인트는 바로 EditText의 입력 변화에 따라 이벤트가 발생하는 것이다.

그렇기 위해 beforeTextChanged() 메서드를 이용해 바뀌기 전의 수를 먼저 구하고, 그 수의 차이를 통해 유저가 원하는 대로 보여준다. 단순히 숫자가 늘어나는 순간뿐 아니라 백스페이스로 숫자를 지울 때도 적절하게 이전 값을 구해 계산한다. 

실제 소스 적용은 다음과 같이 이루어졌다.

        ...
        

        totalSaleIncomeTil.getEditText().addTextChangedListener(new AmountTextFormatter(totalSaleIncomeTil.getEditText(), businessTotalNetIncomeEt, "+"));
        totalSaleExpenseTil.getEditText().addTextChangedListener(new AmountTextFormatter(totalSaleExpenseTil.getEditText(), businessTotalNetIncomeEt, "-"));

        totalFamilyIncomeTil.getEditText().addTextChangedListener(new AmountTextFormatter(totalFamilyIncomeTil.getEditText(), familyTotalNetIncomeEt, "+"));
        totalFamilyExpenseTil.getEditText().addTextChangedListener(new AmountTextFormatter(totalFamilyExpenseTil.getEditText(), familyTotalNetIncomeEt, "-"));

        //Last Total Net Income
        businessTotalNetIncomeEt.addTextChangedListener(new AmountTextFormatter(businessTotalNetIncomeEt, TotalNetIncomeEt, "+"));
        familyTotalNetIncomeEt.addTextChangedListener(new AmountTextFormatter(familyTotalNetIncomeEt, TotalNetIncomeEt, "+"));

        //Total Sale Expense (Sum)
        rawMaterialExpenseTil.getEditText().addTextChangedListener(new AmountTextFormatter(rawMaterialExpenseTil.getEditText(), totalSaleExpenseTil.getEditText(), "+"));
        businessBuildingRentingTil.getEditText().addTextChangedListener(new AmountTextFormatter(businessBuildingRentingTil.getEditText(), totalSaleExpenseTil.getEditText(), "+"));
        employeeExpenseTil.getEditText().addTextChangedListener(new AmountTextFormatter(employeeExpenseTil.getEditText(), totalSaleExpenseTil.getEditText(), "+"));
        transportationTil.getEditText().addTextChangedListener(new AmountTextFormatter(transportationTil.getEditText(), totalSaleExpenseTil.getEditText(), "+"));
        lossOfGoodsTil.getEditText().addTextChangedListener(new AmountTextFormatter(lossOfGoodsTil.getEditText(), totalSaleExpenseTil.getEditText(), "+"));
        busOtherExpense1Til.getEditText().addTextChangedListener(new AmountTextFormatter(busOtherExpense1Til.getEditText(), totalSaleExpenseTil.getEditText(), "+"));
        busOtherExpense2Til.getEditText().addTextChangedListener(new AmountTextFormatter(busOtherExpense2Til.getEditText(), totalSaleExpenseTil.getEditText(), "+"));

        //Total Family Expense (Sum)
        costForFoodTil.getEditText().addTextChangedListener(new AmountTextFormatter(costForFoodTil.getEditText(), totalFamilyExpenseTil.getEditText(), "+"));
        houseMaintenanceTil.getEditText().addTextChangedListener(new AmountTextFormatter(houseMaintenanceTil.getEditText(), totalFamilyExpenseTil.getEditText(), "+"));
        electricWaterPhoneTil.getEditText().addTextChangedListener(new AmountTextFormatter(electricWaterPhoneTil.getEditText(), totalFamilyExpenseTil.getEditText(), "+"));
        educationExpenseTil.getEditText().addTextChangedListener(new AmountTextFormatter(educationExpenseTil.getEditText(), totalFamilyExpenseTil.getEditText(), "+"));
        healthyExpenseTil.getEditText().addTextChangedListener(new AmountTextFormatter(healthyExpenseTil.getEditText(), totalFamilyExpenseTil.getEditText(), "+"));
        otherDebtSavingTil.getEditText().addTextChangedListener(new AmountTextFormatter(otherDebtSavingTil.getEditText(), totalFamilyExpenseTil.getEditText(), "+"));
        famOtherExpenseTil.getEditText().addTextChangedListener(new AmountTextFormatter(famOtherExpenseTil.getEditText(), totalFamilyExpenseTil.getEditText(), "+"));

        ...

Material Design의 TextInputLayout 속성을 적용했기 때문에 EditText를 구하는 코드가 조금 길어지긴 했지만..

 

 

+ Recent posts