Пишем калькулятор на Python с помощью Flet

от автора

Введение

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

Чтобы создать привлекательное мобильное приложение, которое будет отлично работать на Android и iOS, обычно требуется значительная доработка существующих инструментов, таких как Kivy или Tkinter. Именно здесь на сцену выходит Flet — фреймворк, который позволяет легко создавать веб-, десктопные и мобильные приложения, используя Flutter, популярный инструмент для создания пользовательских интерфейсов от Google, но на языке Python.

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

Кто такой этот Flet?

Flet — фреймворк, который позволяет создавать пользовательские интерфейсы непосредственно с помощью инструментария Flutter.

В чём его преимущества?

  • Он сочетает в себе простоту Python и богатые возможности Flutter по созданию интерфейсов, позволяя быстро разрабатывать кроссплатформенные приложения, не требуя большого опыта работы с фронтендом.

  • Для работы с этим инструментом не требуется SDK, а его функционал может быть легко расширен с помощью Flutter SDK.

❕Обратите внимание: для успешного освоения данной темы будет полезно иметь общее представление о некоторых ключевых концепциях фронтенда, таких как блочная модель, макеты Flexbox и Grid, а также о способах позиционирования элементов. Хотя вы можете продолжать работу без глубокого понимания этих тем, всё же настоятельно рекомендуется хотя бы немного с ними ознакомиться.

Давайте наконец создадим наш калькулятор!

Настраиваем окружение

Прежде чем приступить к написанию кода, убедитесь, что на вашей машине установлен Python. Затем выполните следующие шаги, чтобы настроить среду для Flet.

  1. Устанавливаем Flet, например, при помощи pip. Открываем терминал или командную строку и вводим pip install flet

  2. Открываем любимый редактор кода (например, VSCode, Pycharm и т. д.) и создаём новый исполняемый Python-файл.

Давайте сначала проверим, всё ли работает, с помощью самой любимой фразы в сообществе разработчиков «Hello World!».
В нашем Python-файле вводим напишем код:

import flet as ft  def main(page: ft.Page):     page.add(ft.Text(value="Hello, World!"))  ft.app(target=main)

Запускаем и проверяем, всё ли работает. Если видим на экране надпись «Hello, World!», значит, мы готовы перейти к созданию нашего калькулятора.

Создаём макет

Для начала займёмся структурой калькулятора. Используем виджет для создания колонок, которые будут выступать в роли дисплея и кнопок. Дисплей будет отображать текущий ввод, а кнопки позволят на них нажимать взаимодействовать с пользователем.

Напишем такой код:

from flet import (     app, Page, Container, Column, Row,     TextField, colors, border_radius, ElevatedButton, TextAlign, TextStyle )   def main(page: Page):     page.title = "Calculator"     result = TextField(         hint_text='0', text_size=20,         color='white', text_align=TextAlign.RIGHT,         hint_style=TextStyle(             color=colors.WHITE, size=20         ),         read_only=True     )      def button_click(e):         pass      button_row0 = Row(         [             ElevatedButton(text='C',  on_click=button_click),             ElevatedButton(text='^',  on_click=button_click),             ElevatedButton(text='%',  on_click=button_click),             ElevatedButton(text='/',  on_click=button_click),         ]     )     button_row1 = Row(         [             ElevatedButton(text='7',  on_click=button_click),             ElevatedButton(text='8',  on_click=button_click),             ElevatedButton(text='9',  on_click=button_click),             ElevatedButton(text='*',  on_click=button_click),         ]     )     button_row2 = Row(         [             ElevatedButton(text='4',  on_click=button_click),             ElevatedButton(text='5',  on_click=button_click),             ElevatedButton(text='6',  on_click=button_click),             ElevatedButton(text='-',  on_click=button_click),         ]     )     button_row3 = Row(         [             ElevatedButton(text='1',  on_click=button_click),             ElevatedButton(text='2',  on_click=button_click),             ElevatedButton(text='3',  on_click=button_click),             ElevatedButton(text='+',  on_click=button_click),         ]     )     button_row4 = Row(         [             ElevatedButton(text='0',  on_click=button_click),             ElevatedButton(text='.',  on_click=button_click),             ElevatedButton(text='=',  on_click=button_click),         ]     )     container = Container(         width=350, padding=20,         bgcolor=colors. BLACK,         content=Column(             [                 result,                 button_row0, button_row1, button_row2,                 button_row3, button_row4             ]         )     )     page.add(container)   if __name__ == '__main__':     app(target=main)

После исполнения этого кода мы увидим макет калькулятора, который пока будет выглядеть не очень хорошо, но это не страшно! Мы улучшим его, добавив некоторые интервалы, скругление контейнера и тему, чтобы наш калькулятор выглядел более аккуратным.

Объяснение кода

Вот мы создали макет, но некоторые из вас не поняли, как мы это сделали. Давайте разбираться!

В первой строке нашего кода мы импортируем необходимые элементы управления. Эти элементы, называемые виджетами Flet, играют ключевую роль в создании пользовательского интерфейса нашего приложения. В данном случае мы импортировали такие важные компоненты, как: app, Page, Container, Column, Row, TextField, colors, border_radius, ElevatedButton, TextAlign, TextStyle.

Некоторые из них не являются полноценными виджетами, например, app, colors, border_radius, TextAlign и TextStyle. Это классы и методы, которые добавляют дополнительные функции в наше приложение.

Например, app позволяет нам запускать наше приложение в автономном режиме, ориентируясь на основной экземпляр. colors дает возможность стилизовать наши элементы управления, поддерживающие атрибуты color и bgcolor, без необходимости определять их имена. А border_radius дает возможность закруглять углы наших контейнеров.

В строке 7 мы определяем основной экземпляр нашего приложения как Page. Страница — это контейнер, в котором располагаются элементы управления View. Мы не будем углубляться в детали View, но можно ознакомиться с этим более подробно на официальном сайте.

Теперь мы установим заголовок нашей страницы, используя атрибут page.title. Этот заголовок будет отображаться в строке заголовка нашего приложения.

В строках с 9 по 16 находится блок result. Он обладает различными свойствами, но в этом проекте мы будем использовать лишь некоторые из них. Мы добавили текст «0», установили его размер равным 20, выбрали белый цвет и выровняли по правому краю. Кроме того, мы сделали этот текст недоступным для изменения. Это означает, что пользователи не смогут изменить его, используя клавиатуру.

В строке 18 мы определили обработчик события button_click. В этой функции мы будем применять логику для работы нашего приложения, превращая его в настоящий калькулятор. Но пока там просто стоит pass в качестве заглушки.

В строках с 21 по 59 мы определили наши строки, используя виджет Row. Виджет Row — это элемент управления, который отображает свои дочерние элементы в горизонтальном порядке, слева направо. Подобно линейному макету в разработке Android или линейным элементам в CSS, элемент управления Row работает так же, выстраивая элементы управления по горизонтальной оси.

Затем мы добавили ElevatedButton, который будет представлять кнопки в пользовательском интерфейсе калькулятора. Обратите внимание, что мы задали ему атрибуты text и _onclick. Атрибут text определяет данные, которые будут отображаться на результатах при нажатии, а атрибут onclick вызывает функцию button_click для обработки событий.

Ещё у нас есть контейнер. Он помогает сделать элемент управления красивым. Можно выбрать цвет фона, интервалы, границы и их радиус. Ещё можно расположить элемент управления с помощью padding, margin и выравнивания.

Контейнер следует концепции бокс-модели, подобно той, что используется в CSS, как показано на рисунке ниже:

Элемент управления Column работает как элемент управления Row. Он упорядочивает свои дочерние элементы, как будто выстраивает их в ряд сверху вниз. Так мы можем удобно расположить кнопки в нужном порядке.

После того как мы определили элементы пользовательского интерфейса, нам необходимо отобразить их в нашем приложении и затем вызвать его. Для этого мы используем метод page.add(), который позволяет нам добавлять и логически упорядочивать элементы пользовательского интерфейса.

Далее следует вызов нашего приложения в автономном режиме, что и было реализовано в строках 74-75.

Добавление функциональности

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

def button_click(e):         if e.control.text == "=":             try:                 result.value = str(eval(result.value))             except Exception:                 result.value = "Error"         elif e.control.text == "C":             result.value = ""         # elif e.control.text == "^":         # logic for powers         # pass         else:             result.value += e.control.text         result.update()

Объяснение кода

Давайте рассмотрим, что происходит в этом коде. Функция button_click предназначена для обработки различных событий нажатия кнопок в нашем приложении-калькуляторе.

Вот краткое описание того, что в ней происходит:

Получение текста кнопки: Когда пользователь нажимает на кнопку, функция получает текст кнопки (например, ‘1’, ‘2’, ‘+’, ‘-‘, ‘C’, ‘=’) через e.control.text. Это позволяет ей определить, с какой именно кнопкой взаимодействовал пользователь.

Очистка дисплея: При нажатии на кнопку ‘C’ ввод калькулятора очищается. Результат обнуляется, а в дисплее устанавливается 0. Калькулятор будет готов к новым расчётам.

Расчёт выражений: Если пользователь нажимает кнопку «=», калькулятор должен рассчитать текущее математическое выражение. Для этого мы используем функции str() и eval(), причем первая преобразует результат в строку, а вторая вычисляет его и выводит на дисплей. Если выражение недействительно, будет сгенерировано исключение, и вместо него появится сообщение «Error».

Остальные кнопки: Для остальных кнопок, таких как числа и операторы, функция добавляет текст кнопки на дисплей (который изначально равен «0» или очищается при нажатии на ‘C’). Она заменяет «0» на значение кнопки, если это возможно, или добавляет его в конец дисплея/

После обработки нажатия кнопки страница обновляется с помощью метода page.update(), чтобы отобразить новый ввод или результат на дисплее калькулятора. Каждый раз, когда вы нажимаете на кнопку и видите значение на дисплее или результат, именно этот метод page.update() срабатывает.

💀Примечание: Функция eval() может быть опасной, так как она выполняет любой код Python, а он может быть вредоносным. В более надёжном приложении лучше использовать другой, более безопасный способ.

Улучшение пользовательского интерфейса

Пока интерфейс у нас выглядит не так красиво, поэтому давайте его обновим и сделаем кнопки более привлекательными, используя следующий код:

button_row0 = Row(     [         ElevatedButton(text='C', expand=1, on_click=button_click,                        bgcolor=colors.RED_ACCENT, color=colors.WHITE),         ElevatedButton(text='^', expand=1, on_click=button_click,                        bgcolor=colors.BLUE_ACCENT_100,                        color=colors.RED_900                        ),         ElevatedButton(text='%', expand=1, on_click=button_click,                        bgcolor=colors.BLUE_ACCENT_100,                        color=colors.RED_900                        ),         ElevatedButton(text='/', expand=1, on_click=button_click,                        bgcolor=colors.BLUE_ACCENT_100,                        color=colors.RED_900                        ),     ] ) button_row1 = Row(     [         ElevatedButton(text='7', expand=1, on_click=button_click),         ElevatedButton(text='8', expand=1, on_click=button_click),         ElevatedButton(text='9', expand=1, on_click=button_click),         ElevatedButton(text='*', expand=1, on_click=button_click,                        bgcolor=colors.BLUE_ACCENT_100,                        color=colors.RED_900                        ),     ] ) button_row2 = Row(     [         ElevatedButton(text='4', expand=1, on_click=button_click),         ElevatedButton(text='5', expand=1, on_click=button_click),         ElevatedButton(text='6', expand=1, on_click=button_click),         ElevatedButton(text='-', expand=1, on_click=button_click,                         bgcolor=colors.BLUE_ACCENT_100                        ),     ] ) button_row3 = Row(     [         ElevatedButton(text='1', expand=1, on_click=button_click),         ElevatedButton(text='2', expand=1, on_click=button_click),         ElevatedButton(text='3', expand=1, on_click=button_click),         ElevatedButton(text='+', expand=1, on_click=button_click,                         bgcolor=colors.BLUE_ACCENT_100,                        color=colors.RED_900),     ] ) button_row4 = Row(     [         ElevatedButton(text='0', expand=1, on_click=button_click),         ElevatedButton(text='.', expand=1, on_click=button_click),         ElevatedButton(             text='=', expand=2, on_click=button_click,             bgcolor=colors.GREEN_ACCENT, color=colors.AMBER         ),     ] )

Что же именно мы изменили?

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

Однако у нас есть альтернативный вариант — атрибут expand. Он позволяет задавать значения только двух типов данных: Boolean и int.

Для обычных кнопок, таких как операторы, числа и кнопка очистки ввода, мы увеличили значение expand на 1, а для кнопки равно — на 2.

Теперь о том, что делает атрибут expand. Этот атрибут позволяет элементу управления заполнять свободное пространство в заданном контейнере. Таким образом, кнопки с expand 1 будут иметь одинаковую ширину, а кнопка «равно» расширится на 2, что означает, что ее размер будет равен ширине двух кнопок.

Обратите внимание, что мы добавили цвета и фоновые цвета к некоторым из наших кнопок, чтобы они выделялись на фоне кнопок с цифрами.

И ещё давайте добавим закругление в контейнер сразу поле атрибута padding так:border_radius=border_radius.all(20),

Теперь у вас есть полнофункциональный калькулятор, созданный с помощью Flet! Попробуйте донастроить его по своему вкусу или добавить дополнительные функции. Также можно упаковать его как отдельный APK, AAB для загрузки в Google Play Store или Apple App Store.

Вот полный код:

from flet import (     app, Page, Container, Column, Row,     TextField, colors, border_radius, ElevatedButton, TextAlign, TextStyle ) from flet_core import ThemeMode   def main(page: Page):     page.title = "Calculator"     page.theme_mode = ThemeMode.DARK     page.horizontal_alignment = page.vertical_alignment = 'center'     result = TextField(         hint_text='0', text_size=20,         color='white', text_align=TextAlign.RIGHT,         hint_style=TextStyle(             color=colors.WHITE, size=20         ),         read_only=True     )      def button_click(e):         if e.control.text == "=":             try:                 result.value = str(eval(result.value))             except Exception:                 result.value = "Error"         elif e.control.text == "C":             result.value = ""         # elif e.control.text == "^":         # logic for powers         # pass         else:             result.value += e.control.text         result.update()      button_row0 = Row(         [             ElevatedButton(text='C', expand=1, on_click=button_click,                            bgcolor=colors.RED_ACCENT, color=colors.WHITE),             ElevatedButton(text='^', expand=1, on_click=button_click,                            bgcolor=colors.BLUE_ACCENT_100,                            color=colors.RED_900                            ),             ElevatedButton(text='%', expand=1, on_click=button_click,                            bgcolor=colors.BLUE_ACCENT_100,                            color=colors.RED_900                            ),             ElevatedButton(text='/', expand=1, on_click=button_click,                            bgcolor=colors.BLUE_ACCENT_100,                            color=colors.RED_900                            ),         ]     )     button_row1 = Row(         [             ElevatedButton(text='7', expand=1, on_click=button_click),             ElevatedButton(text='8', expand=1, on_click=button_click),             ElevatedButton(text='9', expand=1, on_click=button_click),             ElevatedButton(text='*', expand=1, on_click=button_click,                            bgcolor=colors.BLUE_ACCENT_100,                            color=colors.RED_900                            ),         ]     )     button_row2 = Row(         [             ElevatedButton(text='4', expand=1, on_click=button_click),             ElevatedButton(text='5', expand=1, on_click=button_click),             ElevatedButton(text='6', expand=1, on_click=button_click),             ElevatedButton(text='-', expand=1, on_click=button_click,                             bgcolor=colors.BLUE_ACCENT_100                            ),         ]     )     button_row3 = Row(         [             ElevatedButton(text='1', expand=1, on_click=button_click),             ElevatedButton(text='2', expand=1, on_click=button_click),             ElevatedButton(text='3', expand=1, on_click=button_click),             ElevatedButton(text='+', expand=1, on_click=button_click,                             bgcolor=colors.BLUE_ACCENT_100,                            color=colors.RED_900),         ]     )     button_row4 = Row(         [             ElevatedButton(text='0', expand=1, on_click=button_click),             ElevatedButton(text='.', expand=1, on_click=button_click),             ElevatedButton(                 text='=', expand=2, on_click=button_click,                 bgcolor=colors.GREEN_ACCENT, color=colors.AMBER             ),         ]     )     container = Container(         width=350, padding=20,         bgcolor=colors.BLACK, border_radius=border_radius.all(20),         content=Column(             [                 result,                 button_row0, button_row1, button_row2,                 button_row3, button_row4             ]         )     )     page.add(container)   if __name__ == '__main__':     app(target=main)

👉 А если тебе интересны и другие полезные материалы по Python и IT, то можешь подписаться на мой канал в tg: PythonTalk 👈


ссылка на оригинал статьи https://habr.com/ru/articles/845578/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *