Зная некоторые основы компоновки и такие элементы как TextView, EditText и Button, уже можно составить более менее полноценное приложение. В данном случае мы сделаем простенький калькулятор.
Для этого создадим новый проект и определим в файле activity_main.xml следующий интерфейс:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="8dp"> <!-- поле результата --> <TextView android:id="@+id/resultField" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintHorizontal_weight="1" android:textSize="18sp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/operationField"/> <!-- поле знака операции --> <TextView android:id="@+id/operationField" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintHorizontal_weight="1" android:textSize="18sp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toRightOf="@+id/resultField" /> <!-- поле ввода чисел --> <EditText android:id="@+id/numberField" android:layout_width="0dp" android:layout_height="wrap_content" android:inputType="phone" app:layout_constraintTop_toBottomOf="@+id/resultField" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/> <LinearLayout android:id="@+id/firstButtonPanel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@+id/numberField" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="7" android:id="@+id/n7" /> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="8" android:id="@+id/n8"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="9" android:id="@+id/n9"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="/" android:id="@+id/div"/> </LinearLayout> <LinearLayout android:id="@+id/secondButtonPanel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@+id/firstButtonPanel" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="4" android:id="@+id/n4"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="5" android:id="@+id/n5"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="6" android:id="@+id/n6"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="*" android:id="@+id/mul"/> </LinearLayout> <LinearLayout android:id="@+id/thirdButtonPanel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@+id/secondButtonPanel" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="1" android:id="@+id/n1"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="2" android:tag="num" android:id="@+id/n2"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="3" android:id="@+id/n3"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="-" android:id="@+id/sub" /> </LinearLayout> <LinearLayout android:id="@+id/forthButtonPanel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@+id/thirdButtonPanel" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="0" android:id="@+id/n0"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="," android:id="@+id/comma"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="+" android:id="@+id/add"/> <Button android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="=" android:id="@+id/eq"/> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
В итоге весь интерфейс будет выглядеть следующим образом:
Корневой контейнер компоновки представляет элемент ConstraintLayout . Сверху в нем определены два текстовых поля TextView: одно для вывода результата вычислений и одно для вывода текущего знака операции.
Затем идет элемент EditText, предназначенный для ввода чисел.
И далее расположены четыре элемента LinearLayout с горизонтальными рядами кнопок. Чтобы все кнопки занимали равное пространство внутри контейнера, для них установлены атрибуты
android:layout_weight="1"
и android:layout_width="0dp"
.
Для каждой кнопки установлен идентификатор, чтобы в коде можно было прикрепить к ней определенный обработчик нажатия.
Теперь изменим класс MainActivity:
package com.metanit.calculator; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity { TextView resultField; // текстовое поле для вывода результата EditText numberField; // поле для ввода числа TextView operationField; // текстовое поле для вывода знака операции Double operand = null; // операнд операции String lastOperation = "="; // последняя операция @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // получаем все поля по id из activity_main.xml resultField = findViewById(R.id.resultField); numberField = findViewById(R.id.numberField); operationField = findViewById(R.id.operationField); findViewById(R.id.add).setOnClickListener((view)->onOperationClick("+")); findViewById(R.id.sub).setOnClickListener((view)->onOperationClick("-")); findViewById(R.id.mul).setOnClickListener((view)->onOperationClick("*")); findViewById(R.id.div).setOnClickListener((view)->onOperationClick("/")); findViewById(R.id.eq).setOnClickListener((view)->onOperationClick("=")); findViewById(R.id.n0).setOnClickListener((view)->onNumberClick("0")); findViewById(R.id.n1).setOnClickListener((view)->onNumberClick("1")); findViewById(R.id.n2).setOnClickListener((view)->onNumberClick("2")); findViewById(R.id.n3).setOnClickListener((view)->onNumberClick("3")); findViewById(R.id.n4).setOnClickListener((view)->onNumberClick("4")); findViewById(R.id.n5).setOnClickListener((view)->onNumberClick("5")); findViewById(R.id.n6).setOnClickListener((view)->onNumberClick("6")); findViewById(R.id.n7).setOnClickListener((view)->onNumberClick("7")); findViewById(R.id.n8).setOnClickListener((view)->onNumberClick("8")); findViewById(R.id.n9).setOnClickListener((view)->onNumberClick("9")); findViewById(R.id.comma).setOnClickListener((view)->onNumberClick(",")); } // сохранение состояния @Override protected void onSaveInstanceState(Bundle outState) { outState.putString("OPERATION", lastOperation); if(operand!=null) outState.putDouble("OPERAND", operand); super.onSaveInstanceState(outState); } // получение ранее сохраненного состояния @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); lastOperation = savedInstanceState.getString("OPERATION"); operand= savedInstanceState.getDouble("OPERAND"); resultField.setText(operand.toString()); operationField.setText(lastOperation); } // обработка нажатия на числовую кнопку public void onNumberClick(String number){ numberField.append(number); if(lastOperation.equals("=") && operand!=null){ operand = null; } } // обработка нажатия на кнопку операции public void onOperationClick(String op){ String number = numberField.getText().toString(); // если введенно что-нибудь if(number.length()>0){ number = number.replace(',', '.'); try{ performOperation(Double.valueOf(number), op); }catch (NumberFormatException ex){ numberField.setText(""); } } lastOperation = op; operationField.setText(lastOperation); } private void performOperation(Double number, String operation){ // если операнд ранее не был установлен (при вводе самой первой операции) if(operand ==null){ operand = number; } else{ if(lastOperation.equals("=")){ lastOperation = operation; } switch(lastOperation){ case "=": operand =number; break; case "/": if(number==0){ operand =0.0; } else{ operand /=number; } break; case "*": operand *=number; break; case "+": operand +=number; break; case "-": operand -=number; break; } } resultField.setText(operand.toString().replace('.', ',')); numberField.setText(""); } }
Разберем этот код. Вначале в методе onCreate()
получаем все поля из activity_main.xml, текст которых будет изменяться:
resultField = findViewById(R.id.resultField); numberField = findViewById(R.id.numberField); operationField = findViewById(R.id.operationField);
Далее для каждой кнопки назначаем определенный обработчик нажатия - в зависимости от типа кнопки это либо метод onNumberClick
, в который передается нажатое число,
либо onOperationClick
, в который передается знак операции.
Результат операции будет попадать в переменную operand, которая представляет тип Double, а знак операции - в переменную lastOperation:
Double operand = null; String lastOperation = "=";
Так как при переходе от портретной ориентации к альбомной или наоборот мы можем потерять все введенные данные, то чтобы их не потерять, мы их сохраняем в методе
onSaveInstanceState()
и обратно получаем в методе onRestoreInstanceState()
.
При нажатии на числовую кнопку будет вызываться метод onNumberClick
, в котором добавляем введенную цифру или знак запятой к тексту в поле numberField:
numberField.append(number); if(lastOperation.equals("=") && operand!=null){ operand = null; }
При этом если последняя операция представляла собой получение результата (знак "равно"), то мы сбрасываем переменную operand.
В методе onOperationClick
происходит обработка нажатия на кнопку со знаком операции:
String number = numberField.getText().toString(); if(number.length()>0){ number = number.replace(',', '.'); try{ performOperation(Double.valueOf(number), op); }catch (NumberFormatException ex){ numberField.setText(""); } } lastOperation = op; operationField.setText(lastOperation);
Здесь получаем ранее введенное число и введенную операцию и передаем их в метод performOperation()
. Так как в метод передается не просто строка, а число
Double, то нам надо преобразовать строку в чсло. И поскольку теоретически могут быть введены нечисловые символы, то для отлова исключения, которое может возникнуть при преобразовании
используется конструкция try...catch.
Кроме того, так как разделителем целой и дробной части в Double в java является точка, то нам надо заменить запятую на точку, так как предполагается, что мы используем в качестве разделителя запятую.
А методе performOperation()
выполняем собственно операцию. При вводе первой операции, когда операнд еще не установлен, мы просто устанавливаем операнд:
if(operand ==null){ operand = number; }
При вводе второй и последующих операций применяем предыдущую операцию, знак которой хранится в переменной lastOperation, к операнду operand и второму числу, которое было введено в числовое поле. Полученный результат операции сохраняем в переменной operand.