Как создать калькулятор на Java для Android с полным кодом

Калькулятор на Java для Android можно собрать из XML-разметки и одной Activity. В этой инструкции вы создадите экран с кнопками, подключите view binding и добавите четыре арифметических действия.

Пример показывает, как сделать калькулятор с дробями и обработкой ошибок. Код защищает приложение от сбоев при пустом вводе, повторных операторах и делении на ноль. В конце вы запустите приложение на эмуляторе и проверите вычисления.

Создание проекта на Java

Чтобы понять, как создать калькулятор, начните с проекта на Java с XML-разметкой. Экран будет храниться в файле activity_main.xml, а логика кнопок – в MainActivity.java.

  1. Откройте Android Studio и запустите создание нового проекта.
  2. Выберите шаблон, который создает экран на основе Views и XML-разметки. Если мастер предлагает выбрать язык, укажите «Java».
  3. Назовите Activity MainActivity. Имя файла разметки оставьте activity_main.xml.
  4. Завершите создание проекта и дождитесь окончания синхронизации Gradle.

После создания откройте дерево проекта. Найдите MainActivity.java в пакете приложения и файл activity_main.xml в каталоге ресурсов layout. Сохраните значение namespace из Gradle-файла: оно понадобится для импорта класса binding.

Если мастер создал Compose-разметку, пересоздайте проект с шаблоном Views для работы с XML.

Включение view binding

View binding создает класс ActivityMainBinding для файла activity_main.xml. Через него Activity получает ссылки на кнопки и поля без вызовов findViewById().

В файле build.gradle.kts модуля приложения добавьте настройку в существующий блок android:

android {
    buildFeatures {
        viewBinding = true
    }
}

Для файла build.gradle используйте синтаксис Groovy:

android {
    buildFeatures {
        viewBinding true
    }
}

Сохраните файл и выполните синхронизацию Gradle. Остальные параметры блока android, включая namespace, удалять не нужно.

Для view binding не нужна обертка <layout>, как поясняет документация Android.

Элементы экрана калькулятора

Экрану нужны поле текущей операции, поле числа и кнопки ввода. GridLayout раскладывает кнопки по четырем колонкам, поэтому разметка остается компактной.

ЭлементЗачем нужен в калькуляторе
TextViewПоказывает выбранный оператор, результат или сообщение об ошибке.
EditTextПоказывает число, которое пользователь набрал кнопками.
ButtonПередает цифру, точку, оператор или команду сброса.
GridLayoutВыстраивает кнопки в четыре колонки.

Разметка калькулятора в activity_main.xml

Замените содержимое activity_main.xml полной разметкой. Идентификаторы кнопок совпадают с полями класса ActivityMainBinding.

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnCount="4"
    android:padding="16dp"
    android:rowCount="7">

    <TextView
        android:id="@+id/infoTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_columnSpan="4"
        android:layout_columnWeight="1"
        android:gravity="end"
        android:textSize="24sp" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_columnSpan="4"
        android:layout_columnWeight="1"
        android:cursorVisible="false"
        android:focusable="false"
        android:gravity="end"
        android:inputType="numberDecimal|numberSigned"
        android:textSize="30sp" />

    <Button android:id="@+id/buttonSeven" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="7" />
    <Button android:id="@+id/buttonEight" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="8" />
    <Button android:id="@+id/buttonNine" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="9" />
    <Button android:id="@+id/buttonDivide" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="/" />

    <Button android:id="@+id/buttonFour" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="4" />
    <Button android:id="@+id/buttonFive" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="5" />
    <Button android:id="@+id/buttonSix" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="6" />
    <Button android:id="@+id/buttonMultiply" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="*" />

    <Button android:id="@+id/buttonOne" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="1" />
    <Button android:id="@+id/buttonTwo" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="2" />
    <Button android:id="@+id/buttonThree" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="3" />
    <Button android:id="@+id/buttonSubtract" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="-" />

    <Button android:id="@+id/buttonZero" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="0" />
    <Button android:id="@+id/buttonDot" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="." />
    <Button android:id="@+id/buttonClear" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="C" />
    <Button android:id="@+id/buttonAdd" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:text="+" />

    <Button android:id="@+id/buttonEqual" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnSpan="4" android:layout_columnWeight="1" android:text="=" />

</GridLayout>

Поле ввода не получает фокус, поэтому число вводится кнопками приложения.

После сохранения файла соберите проект. Если поле binding.buttonOne не появляется, проверьте идентификатор в XML и дождитесь завершения сборки.

Подключение binding в MainActivity

Создайте поле binding и вызовите ActivityMainBinding.inflate() в onCreate(). Слушатели кнопок ставьте сразу после setContentView(), когда представления уже созданы.

private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = ActivityMainBinding.inflate(getLayoutInflater());
    setContentView(binding.getRoot());

    setupNumberButtons();
    setupOperatorButtons();
    setupClearButton();
}

Для импорта binding добавьте к namespace модуля окончание .databinding.ActivityMainBinding.

Поля и форматирование результата

Переменная valueOne хранит первое число, valueTwo – второе, а currentAction – выбранный оператор. Значение Double.NaN показывает, что первое число еще не выбрано.

private double valueOne = Double.NaN;
private double valueTwo;
private char currentAction;
private boolean resultShown;
private final DecimalFormat decimalFormat = new DecimalFormat(
        "0.##########",
        DecimalFormatSymbols.getInstance(Locale.US));

Объявите и инициализируйте decimalFormat как поле класса, доступное обработчикам кнопок.

Locale.US закрепляет точку как разделитель дробной части. Шаблон выводит до 10 знаков после точки, а тип double подходит для учебного примера, но не гарантирует точность денежных расчетов.

Обработка цифр и точки

Метод appendInput() добавляет текст нажатой кнопки и блокирует вторую точку.

private void appendInput(String value) {
    if (resultShown) {
        binding.editText.setText("");
        binding.infoTextView.setText("");
        resultShown = false;
    }

    String current = binding.editText.getText().toString();
    if (value.equals(".")) {
        if (current.contains(".")) {
            return;
        }
        if (current.isEmpty()) {
            current = "0";
        }
    }
    binding.editText.setText(current + value);
}

private void setupNumberButtons() {
    View.OnClickListener numberListener = view -> {
        Button button = (Button) view;
        appendInput(button.getText().toString());
    };

    binding.buttonZero.setOnClickListener(numberListener);
    binding.buttonOne.setOnClickListener(numberListener);
    binding.buttonTwo.setOnClickListener(numberListener);
    binding.buttonThree.setOnClickListener(numberListener);
    binding.buttonFour.setOnClickListener(numberListener);
    binding.buttonFive.setOnClickListener(numberListener);
    binding.buttonSix.setOnClickListener(numberListener);
    binding.buttonSeven.setOnClickListener(numberListener);
    binding.buttonEight.setOnClickListener(numberListener);
    binding.buttonNine.setOnClickListener(numberListener);
    binding.buttonDot.setOnClickListener(numberListener);
}

Точка в пустом поле превращается в 0.. После нажатия «=» первая новая цифра очищает старый результат и начинает следующее число.

Операции, равно и сброс

Чтобы разобраться, как написать калькулятор без сбоев, проследите проверку ввода в computeCalculation(). Метод не передает пустую строку в Double.parseDouble(), поэтому повторное нажатие оператора не вызывает ошибку.

Метод setOperator() вычисляет набранную часть выражения и сохраняет следующий оператор.

private boolean computeCalculation() {
    String input = binding.editText.getText().toString();
    if (input.isEmpty()) {
        return !Double.isNaN(valueOne);
    }

    try {
        valueTwo = Double.parseDouble(input);
    } catch (NumberFormatException error) {
        binding.infoTextView.setText("Введите число");
        return false;
    }

    if (Double.isNaN(valueTwo) || Double.isInfinite(valueTwo)) {
        binding.infoTextView.setText("Слишком большое число. Нажмите C");
        return false;
    }

    if (Double.isNaN(valueOne)) {
        valueOne = valueTwo;
        return true;
    }

    if (currentAction == DIVISION && valueTwo == 0.0) {
        binding.infoTextView.setText("На ноль делить нельзя");
        return false;
    }

    double result = valueOne;
    if (currentAction == ADDITION) {
        result = valueOne + valueTwo;
    } else if (currentAction == SUBTRACTION) {
        result = valueOne - valueTwo;
    } else if (currentAction == MULTIPLICATION) {
        result = valueOne * valueTwo;
    } else if (currentAction == DIVISION) {
        result = valueOne / valueTwo;
    }

    if (Double.isNaN(result) || Double.isInfinite(result)) {
        binding.infoTextView.setText("Слишком большой результат. Нажмите C");
        return false;
    }

    valueOne = result;
    binding.editText.setText("");
    return true;
}

private void setOperator(char operator) {
    if (!computeCalculation()) {
        return;
    }

    currentAction = operator;
    resultShown = false;
    binding.infoTextView.setText(decimalFormat.format(valueOne) + operator);
    binding.editText.setText("");
}

private void showResult() {
    if (!computeCalculation()) {
        return;
    }

    String result = decimalFormat.format(valueOne);
    binding.infoTextView.setText(result);
    binding.editText.setText(result);
    valueOne = Double.NaN;
    valueTwo = 0.0;
    currentAction = '\0';
    resultShown = true;
}

Проверка деления на ноль выполняется до операции и предотвращает вывод Infinity.

Последовательные вычисления идут слева направо. Выражение 2 + 3 * 4 даст 20, потому что пример не реализует приоритет умножения.

Полный MainActivity.java

Замените содержимое Activity полным классом. Пакет в первой строке замените на пакет своего проекта, а импорт binding приведите к namespace модуля.

package com.example.simplecalculator;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

import com.example.simplecalculator.databinding.ActivityMainBinding;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private double valueOne = Double.NaN;
    private double valueTwo;
    private char currentAction;
    private boolean resultShown;
    private final DecimalFormat decimalFormat = new DecimalFormat(
            "0.##########",
            DecimalFormatSymbols.getInstance(Locale.US));

    private static final char ADDITION = '+';
    private static final char SUBTRACTION = '-';
    private static final char MULTIPLICATION = '*';
    private static final char DIVISION = '/';

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        setupNumberButtons();
        setupOperatorButtons();
        setupClearButton();
    }

    private void appendInput(String value) {
        if (resultShown) {
            binding.editText.setText("");
            binding.infoTextView.setText("");
            resultShown = false;
        }

        String current = binding.editText.getText().toString();
        if (value.equals(".")) {
            if (current.contains(".")) {
                return;
            }
            if (current.isEmpty()) {
                current = "0";
            }
        }
        binding.editText.setText(current + value);
    }

    private void setupNumberButtons() {
        View.OnClickListener numberListener = view -> {
            Button button = (Button) view;
            appendInput(button.getText().toString());
        };

        binding.buttonZero.setOnClickListener(numberListener);
        binding.buttonOne.setOnClickListener(numberListener);
        binding.buttonTwo.setOnClickListener(numberListener);
        binding.buttonThree.setOnClickListener(numberListener);
        binding.buttonFour.setOnClickListener(numberListener);
        binding.buttonFive.setOnClickListener(numberListener);
        binding.buttonSix.setOnClickListener(numberListener);
        binding.buttonSeven.setOnClickListener(numberListener);
        binding.buttonEight.setOnClickListener(numberListener);
        binding.buttonNine.setOnClickListener(numberListener);
        binding.buttonDot.setOnClickListener(numberListener);
    }

    private boolean computeCalculation() {
        String input = binding.editText.getText().toString();
        if (input.isEmpty()) {
            return !Double.isNaN(valueOne);
        }

        try {
            valueTwo = Double.parseDouble(input);
        } catch (NumberFormatException error) {
            binding.infoTextView.setText("Введите число");
            return false;
        }

        if (Double.isNaN(valueTwo) || Double.isInfinite(valueTwo)) {
            binding.infoTextView.setText("Слишком большое число. Нажмите C");
            return false;
        }

        if (Double.isNaN(valueOne)) {
            valueOne = valueTwo;
            return true;
        }

        if (currentAction == DIVISION && valueTwo == 0.0) {
            binding.infoTextView.setText("На ноль делить нельзя");
            return false;
        }

        double result = valueOne;
        if (currentAction == ADDITION) {
            result = valueOne + valueTwo;
        } else if (currentAction == SUBTRACTION) {
            result = valueOne - valueTwo;
        } else if (currentAction == MULTIPLICATION) {
            result = valueOne * valueTwo;
        } else if (currentAction == DIVISION) {
            result = valueOne / valueTwo;
        }

        if (Double.isNaN(result) || Double.isInfinite(result)) {
            binding.infoTextView.setText("Слишком большой результат. Нажмите C");
            return false;
        }

        valueOne = result;
        binding.editText.setText("");
        return true;
    }

    private void setOperator(char operator) {
        if (!computeCalculation()) {
            return;
        }

        currentAction = operator;
        resultShown = false;
        binding.infoTextView.setText(decimalFormat.format(valueOne) + operator);
        binding.editText.setText("");
    }

    private void showResult() {
        if (!computeCalculation()) {
            return;
        }

        String result = decimalFormat.format(valueOne);
        binding.infoTextView.setText(result);
        binding.editText.setText(result);
        valueOne = Double.NaN;
        valueTwo = 0.0;
        currentAction = '\0';
        resultShown = true;
    }

    private void setupOperatorButtons() {
        binding.buttonAdd.setOnClickListener(view -> setOperator(ADDITION));
        binding.buttonSubtract.setOnClickListener(view -> setOperator(SUBTRACTION));
        binding.buttonMultiply.setOnClickListener(view -> setOperator(MULTIPLICATION));
        binding.buttonDivide.setOnClickListener(view -> setOperator(DIVISION));
        binding.buttonEqual.setOnClickListener(view -> showResult());
    }

    private void setupClearButton() {
        binding.buttonClear.setOnClickListener(view -> {
            valueOne = Double.NaN;
            valueTwo = 0.0;
            currentAction = '\0';
            resultShown = false;
            binding.infoTextView.setText("");
            binding.editText.setText("");
        });
    }
}

После сборки класс binding должен импортироваться без ошибки. Если Android Studio не находит его, проверьте имя XML-файла, идентификаторы элементов и значение namespace.

Запуск и проверка приложения

Запустите приложение на эмуляторе, чтобы проверить вычисления без физического телефона.

  1. Откройте менеджер виртуальных устройств Android Studio и создайте виртуальное устройство.
  2. Выберите профиль телефона и системный образ Android, совместимый с параметром minSdk приложения.
  3. Запустите созданное виртуальное устройство и дождитесь домашнего экрана.
  4. Выберите эмулятор в списке устройств Android Studio и нажмите кнопку запуска приложения.
  5. Дождитесь сборки. На экране эмулятора откроется калькулятор с полем результата и кнопками.

Пошаговое описание создания и запуска виртуальных устройств есть в документации Android Studio.

Для проверки каждого сценария сначала нажимайте «C».

НажатияОжидаемый результат
7 + 5 =12
9 - 4 =5
6 * 3 =18
8 / 2 =4
8 / 0 =Сообщение «На ноль делить нельзя».
1 . . 51.5: вторая точка игнорируется.
. 5 + . 5 =1: начальная точка получает ноль.
7 + + 5 =12, без сбоя.
+ = на пустом экранеПоля остаются пустыми.
1 / 2 = + 1 =Сначала 0.5, затем 1.5.
2 + 3 = 77: новая цифра начинает следующее число.
7 + CПоля очищены, сохраненная операция сброшена.

Если приложение не открывается, исправьте ошибки сборки и проверьте XML. Если кнопки не реагируют, убедитесь, что три метода setup вызываются в onCreate() после setContentView().

Что умеет готовый калькулятор

Приложение выполняет сложение, вычитание, умножение и деление. Общий обработчик принимает цифры и точку, а один метод с параметром обслуживает четыре оператора.

Кнопка «C» очищает поля и сбрасывает состояние Activity.

Проверка ввода защищает от NumberFormatException. Отдельная проверка деления на ноль не дает вывести Infinity.

Калькулятор хранит состояние в полях Activity, поэтому пересоздание экрана сбрасывает вычисление. Для следующей версии добавьте сохранение состояния и вынесите арифметику в отдельный класс.

Комментарии

Оставьте свой комментарий
Пока никто не оставил комментариев