Эта статья - лабораторная работа, предназначенная для введения в android разработку. Главной целью является создание работающего мобильного приложения с базовыми функциями калькулятора.

Теоретическая база

Android Studio

Android Studio - официальная среда разработки android приложений.

Kotlin

Kotlin был выбран в качестве языка программирования, так как он синтаксически менее громоздкий, чем java, но при этом не менее функциональный.

Цели и задачи

  1. Создать простое приложение калькулятор в android studio

  2. Написать интерфейс приложения

  3. Написать код приложения на языке Kotlin

  4. Протестировать приложение

  5. Создать apk-файл приложения

Технические требования

  1. Android Studio

Выполнение лабораторной работы

Получившийся интерфейс
Получившийся интерфейс

Шаг 1. Создание проекта

Создаем новый проект, выбираем empty views activity.

Выбираем имя для нашего проекта, я выбрал MyCalculator, а также в графе language выбираем Kotlin

Далее у нас откроется проект, и мы увидим 2 файла activity_main.xml и MainActivity.kt. В первом мы будем создавать интерфейс для нашего калькулятора (кнопки, их цвет), а во втором их функционал. Выберем activity_main.xml, в верхнем правом углу мы увидим кнопку split, нажимем на нее. Так мы сможем видеть, как наше приложение будет выглядить при каждом изменении кода, а также изменить размер экрана и выбрать модель виртуального устройства, на котором запускать наше приложение.

Также на следующем скрине я уже заменил дефолтный ConstraintLayout на LinearLayout, поскольку для нашего расположения кнопок и полей достаточно и его.

Шаг 2. Создание интерфейса калькулятора

Удалим TextView с "Hello World" и начнем с разделения экрана на 2 части: поле вывода (где будет результат нашего выражения) вместе с полем выражения и, отдельно, поле для кнопок. Чтобы расположить эти поля вертикально добавим свойство вертикальной ориентации к нашему LinearLayout, а также изменить цвет фона на черный:

  android:orientation="vertical"
android:background="@color/black"

Далее разделим наш экран на 2 равных прямоугольника, расположенных друг на друге. Для этого 2 раза вставим:

<LinearLayout
        android:layout_width="match_parent" <!--Ширина как у LinearLayout, в котором он находится-->
        android:layout_height="0dp"  <!--Наши поля будут иметь равную высоту -->
        android:layout_weight="1"
        android:orientation="vertical">  
    </LinearLayout>

Внутри верхнего LinearLayout'а, который мы решили создать для поля выражения и поля ответа добавим два объекта TextView соответсвенно:

        <!--Строка выражения-->
        <TextView
            android:id="@+id/operation" <!-- id объекта, понадобится когда будем писать функционал -->
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:textColor="@color/white" <!-- Белый цвет текста -->
            android:gravity="end" <!-- Текст отображается справа -->
            android:textSize="50sp" <!-- Размер шрифта -->
            android:ellipsize="start"  <!-- Текст обрезается с начала, если не помещается в строку -->
            android:text=""  <!-- Значение для текста -->
            android:singleLine="true" <!-- Текст в 1 строку -->
            />
  
        <!--Строка результата-->
        <TextView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:textColor="@color/white" 
            android:gravity="end"
            android:textSize="30sp"
            android:ellipsize="start"
            android:text=""
            android:singleLine="true"
            />

Теперь во втором LinearLayout'е, предназначенном для кнопок создадим 5 LinearLayout'ов с горизонтальной ориентацией равных по высоте, которые представляют 5 рядов:

  <LinearLayout
            android:orientation="horizontal"
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="0dp">
        </LinearLayout>

В каждом из созданных Layout'ов я еще создам 5 объектов TextView, присвою им id и text, это будут мои кнопки (вот, например, 1 ряд кнопок, остальные ряды делаются также):

           <TextView
                android:text="sqrt"
                android:id="@+id/b_sqrt">
                </TextView>
  
            <TextView
                android:text="log2"  
                android:id="@+id/b_log2">
                </TextView>
  
            <TextView
                android:text="ln"
                android:id="@+id/b_ln">
                </TextView>
  
            <TextView
                android:text="("
                android:id="@+id/b_leftb">
                </TextView>
  
            <TextView
                android:text=")"
                android:id="@+id/b_rightb">
                </TextView>

Далее во всех TextView'ах у вас вылезет ошибка, не пугайтесь, мы просто забыли указать размеры объекта, поскольку я планирую иметь в своём калькуляторе 25 кнопок, поэтому прописывать для каждой кнопки её размеры будет немного громоздко, проще создать style в файле themes.xml, который находится по пути (Вместо MyCalculator - название вашего проекта):

В нашем стиле мы сможем не только определить размеры объекта, но и допольнительно имзенить его внешний вид. Я создам сразу 3 стиля, для разных типов кнопок (Цифры, основные операции и дополнительные).

<!-- Стиль для цифр -->
<style name = "Operations">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:layout_weight">1</item>
        <item name="android:background">#171616</item>
        <item name="android:textSize">20sp</item>
        <item name="android:textColor">#fc4f05</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_margin">0.5dp</item>
    </style>

<!-- Стиль дополнительных операций -->
    <style name = "Operations_Sci">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:layout_weight">1</item>
        <item name="android:background">#171616</item>
        <item name="android:textSize">20sp</item>
        <item name="android:textColor">#999594</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_margin">0.5dp</item>
    </style>
  
<!-- Стиль для основных операций -->
    <style name = "Numbers">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:layout_weight">1</item>
        <item name="android:background">#403e3d</item>
        <item name="android:textSize">20sp</item>
        <item name="android:textColor">#ffffff</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_margin">0.5dp</item>
    </style>

Далее, чтобы присвои конкретному объекту стиль указываем в нем

style="@style/<название стиля>"

Заполнив все ряды кнопкоми и добавив к ним стилей, а также изменив вид кнопки " = ", написав как бы поверх стиля:

android:background="@color/color_equal"
android:textColor="@color/white"

color_equal - кастомный цвет, чтобы его использовать, как показано выше, его необходимо добавить в colors.xml (находится там же, где и themes.xml)

<color name="color_equal">#f7550f</color>

Получаем такой интерфейс:

Теперь мы закончили с интерфейсом нашего калькулятор, пора переходить в MainActivity.kt для того, чтобы наши кнопки заработали.

Шаг 3. Написание кода

На каждом этапе написания кода мы можем запустить наше приложение на виртуальном устройстве, чтобы посмотреть работает ли наш код так, как должен.

Внутри функции onCreate() создадим и привяжем переменные к кнопкам интерфейса с помощью ранее нами указанных id:

//val <имя переменной>: <тип> = findViewById(R.id.<id кнопки>) as Textview
//Например 
val result: TextView = findViewById(R.id.result) as TextView
val operation: TextView = findViewById(R.id.operation) as TextView
//...

Теперь, нам надо сделать так, чтобы при нажатии определенной кнопки, она отражалась у нас в строке выражения. Для этого:

//<имя переменной, привязанной к соотв. кнопке>.setOnClickListener{ operation.append(<символ>)}
// Например
b_cos.setOnClickListener { operation.append("cos(") }
b_pi.setOnClickListener { operation.append("pi") }
b_e.setOnClickListener { operation.append("e") }
b_0.setOnClickListener {operation.append("0")}
//...

То есть, при нажатии на определенную кнопку, её соответсвующее обозначение добавляется к аттрибуту text нашей строки выражения, но ведь у нас есть кнопки, которые выполняют какие-то другие функции, например кнопка back, которая удаляет последний символ в строке выражения будет выглядеть вот так:

//Кнопка "back"
b_back.setOnClickListener {
  val s = operation.text.toString()
  if (s != "") {
    operation.text = s.substring(0,s.length-1)
}
//Здесь мы заменяем текст нашей строки выражения на её подстроку без последнего символа
//Т.е. последний символ как бы обрезается

Другие "особые кнопки":

//Кнопка очищения текущего выражения и ответа
b_clear.setOnClickListener {
  operation.text = ""
  result.text = ""
}

//Поле ответа
//При нажатии на поле ответа, если оно непустое и не содержит ошибки переносит его
//в поле выражения, очищая поле ответа
//(Это позволит нам считать большие выражения постепенно)
result.setOnClickListener {
  val restext = result.text.toString()
  if (restext != "Error" && restext != ""){
    operation.text = restext
    result.text = ""
  }
}
//Пояснение по поводу "Error" ниже 

И, наконец, самое главное. Записать выражение "2+2" мы смогли, но как мы придем к ответу 4? Для этого импортируем библиотеку exp4j:

import net.objecthunter.exp4j.ExpressionBuilder

С её помощью мы сможем считать сложные строчные выражения (где требуется учесть порядок знаков) и находить верный ответ. В случае если строка выражения содержит ошибку в записи, то мы получим строку "Error".

Запрограммируем наш знак равно с помощью функций из этой библиотеки:

b_eq.setOnClickListener{
  val optext = operation.text.toString() //Выражение в формате строки
  if (optext != "") {
    try {
      val expr = ExpressionBuilder(operation.text.toString()).build() //строим выражение
      val res = expr.evaluate() //Находим ответ (число, может быть нецелое)
      val longres = res.toLong() //longres - число в формате long (целочисленное)
      if (longres.toDouble() == res) { //Если число целое, 
        result.text = longres.toString() //То: Отбрасываем ноль после запятой
      } else {
        result.text = res.toString() //Иначе: Сохраняем числа после запятой
      }
    } catch (e: Exception) { //Если выражение записано некорректно 
        result.text = "Error" //В поле ответа пишем 'Error'
    }
  }
}

Шаг 4. Создание apk-файла приложения

Вот мы и создали наш простой калькулятор, теперь можно создать apk файл, чтобы поделиться приложением было проще. Для этого:

После этого, в android studio придет уведомление где находится наш файл apk. После этого можно установить его себе на смартфон, убедиться, что все работает.

Список использованных источников:

  1. Документация Android Studio

  2. Документация Kotlin

  3. exp4j

Комментарии (5)


  1. Rusrst
    09.12.2023 10:03

    Ну все же на compose такое видеть было бы более ожидаемо, кому там эти view уже нужны, если проект делается с 0?


    1. Spinoza0
      09.12.2023 10:03

      Перепишите на Compose)


  1. ALexKud
    09.12.2023 10:03

    Ну не знаю. Описывать интерфейс в коде это долго и нудно. Можно же просто использовать софт где интерфейс просто собирается из готовых компонентов и останется только написать обработчик событий нажатия кнопок.


    1. Justnoob
      09.12.2023 10:03

      Долго и нудно описывается интерфейс устаревших view :)
      А на compose всё описывается быстро удобно и кайфово :)


  1. bsod_keks
    09.12.2023 10:03

    Посмотреть бы kotlin code style было бы неплохо. underscore прям глаз режет.

    Потом, нет исходника. Я во времена студенчества любил "ломать" лабораторные калькуляторы установкой двух точек в десятичных числах. Тут не ясно решено это или нет) ещё приколом любимым было написать число 0004. А тут ещё и кнопка с тремя нулями есть. Тема не раскрыта, но за старания +.