Пишем калькулятор на Python с помощью Flet
Введение
В современном мире кроссплатформенность стала неотъемлемой частью разработки приложений. Однако, с таким разнообразием фреймворков, выбор подходящего инструмента для достижения этой цели может быть затруднительным.
Чтобы создать привлекательное мобильное приложение, которое будет отлично работать на Android и iOS, обычно требуется значительная доработка существующих инструментов, таких как Kivy или Tkinter. Именно здесь на сцену выходит Flet — фреймворк, который позволяет легко создавать веб-, десктопные и мобильные приложения, используя Flutter, популярный инструмент для создания пользовательских интерфейсов от Google, но на языке Python.
Давайте посмотрим, как создать базовое приложение-калькулятор с помощью Flet, и увидим, насколько простым и эффективным может быть этот фреймворк.
Кто такой этот Flet?
Flet — фреймворк, который позволяет создавать пользовательские интерфейсы непосредственно с помощью инструментария Flutter.
- Он сочетает в себе простоту Python и богатые возможности Flutter по созданию интерфейсов, позволяя быстро разрабатывать кроссплатформенные приложения, не требуя большого опыта работы с фронтендом.
- Для работы с этим инструментом не требуется SDK, а его функционал может быть легко расширен с помощью Flutter SDK.
❕Обратите внимание: для успешного освоения данной темы будет полезно иметь общее представление о некоторых ключевых концепциях фронтенда, таких как блочная модель, макеты Flexbox и Grid, а также о способах позиционирования элементов. Хотя вы можете продолжать работу без глубокого понимания этих тем, всё же настоятельно рекомендуется хотя бы немного с ними ознакомиться.
Давайте наконец создадим наш калькулятор!
Настраиваем окружение
Прежде чем приступить к написанию кода, убедитесь, что на вашей машине установлен Python. Затем выполните следующие шаги, чтобы настроить среду для Flet.
- Устанавливаем Flet, например, при помощи pip. Открываем терминал или командную строку и вводим
pip install flet
- Открываем любимый редактор кода (например, 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, а он может быть вредоносным. В более надёжном приложении лучше использовать другой, более безопасный способ.
🧠Упражнение: Проверьте свои знания
Попробуйте придумать, как бы вы обработали выражение экспоненты '^' так, чтобы при нажатии кнопки оно возвращало нужный результат. Например, если пользователь введет 2^2, на выходе должно получиться 4, 5^5 — 25, а 3^4 — 81. Вы поняли идею.
Расскажите, как вы решили эту задачу в комментариях 👇
Улучшение пользовательского интерфейса
Пока интерфейс у нас выглядит не так красиво, поэтому давайте его обновим и сделаем кнопки более привлекательными, используя следующий код:
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)
👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻
Источник: dev.to