Десктоп приложение как пишется

#статьи

  • 25 июл 2022

  • 0

Знакомимся с библиотекой Tkinter — пишем на Python кросс-платформенный калькулятор, который рассчитывает вес человека.

Иллюстрация: Merry Mary для Skillbox Media

Антон Яценко

Изучает Python, его библиотеки и занимается анализом данных. Любит путешествовать в горах.

Десктопные приложения пишут на разных языках программирования: C++, C#, C, Python и других. Начинающим разработчикам проще всего использовать Python и его библиотеки для работы над графическими интерфейсами.

Одна из таких библиотек — Tkinter. Она входит в стандартный пакет Python и позволяет создавать приложения для Windows, mac OS и Linux. Давайте разберёмся, как устроена эта библиотека, и напишем десктопный калькулятор, помогающий рассчитать вес человека.

GUI (Graphical User Interface) — это графический интерфейс пользователя, оболочка программы, с которой мы взаимодействуем с помощью клавиатуры и мыши. На современных операционных системах почти все программы работают с графическим интерфейсом, и мы каждый день сталкиваемся с GUI: читаем статьи в браузере, набираем текст в редакторе или играем в игры.

Противоположность графическому интерфейсу — командная строка, позволяющая управлять приложением с помощью текстовых команд. Такой интерфейс реализован в терминале macOS и командной строке Windows.

Для работы с GUI в Python есть четыре библиотеки:

  • Tkinter;
  • Kivy;
  • Python QT;
  • wxPython.

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

Tkinter — это удобный интерфейс для работы со средствами Tk. Приложения, созданные на основе этой библиотеки, кросс-платформенные, то есть могут запускаться на разных операционных системах.

Схематично работу с Tkinter можно представить в виде четырёх шагов:

Скриншот: Tkinter

Что здесь происходит:

  • Мы подключаем библиотеку Tkinter с помощью директивы import.
  • Создаём главное окно приложения, в котором будут размещаться все графические элементы.
  • Добавляем виджеты — визуальные элементы, выполняющие определённые действия.
  • Создаём главный цикл событий — он включает в себя все события, происходящие при взаимодействии пользователя с интерфейсом.

Ключевые объекты в работе с Tkinter — виджеты. Это аналоги тегов из HTML, которые позволяют создавать интерактивные и неинтерактивные элементы, например надписи или кнопки. Всего их 18, но чаще всего используют следующие:

  • Button — кнопки;
  • Canvas — «холст», на котором рисуют графические фигуры;
  • Entry — виджет для создания полей ввода;
  • Label — контейнер для размещения текста или изображения;
  • Menu — виджет для создания пунктов меню.

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

Мы напишем калькулятор индекса массы тела. ИМТ — это важный медицинский показатель, который позволяет оценить, есть ли у человека избыточный вес или ожирение. Он рассчитывается по следующей формуле: ​​

Результаты расчётов оценивают с помощью специальной таблицы. У врачей она имеет много градаций, мы же воспользуемся упрощённой версией:

Изображение: Skillbox Media

Писать код на Python лучше всего в специальной IDE, например в PyCharm или Visual Studio Code. Они подсвечивают синтаксис и предлагают продолжение кода — это сильно упрощает работу программиста. Весь код из этой статьи мы писали в Visual Studio Code.

Библиотека Tkinter предустановлена в Python. Поэтому её нужно только импортировать:

import tkinter as tk

Теперь мы можем использовать любые модули из этой библиотеки.


Прежде чем писать код, необходимо ответить на несколько вопросов:

  • Какие данные мы хотим получить от пользователя и в каком виде?
  • Какое событие будет запускать расчёт ИМТ: нажатие кнопки, получение приложением всех необходимых данных или что-то другое?
  • Как будем показывать результат?

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

Схематично графический интерфейс нашего калькулятора будет выглядеть так:

Скриншот: Tkinter / Skillbox Media

Теперь попробуем реализовать интерфейс и работу калькулятора с помощью Python и Tkinter.


После импорта библиотеки в Python загрузим её методы:

from tkinter import *
from tkinter import messagebox

Первая строка позволяет нам загрузить все методы Tkinter и использовать их в коде без ссылки на их наименование. Второй строкой мы явно импортируем метод messagebox, который будем использовать для вывода всплывающего окна с результатом. Это удобно, так как метод потребуется нам несколько раз.

Теперь создадим окно нашего приложения. Для этого воспользуемся модулем Tk. Приложение назовём «Калькулятор индекса массы тела (ИМТ)»:

window = Tk() #Создаём окно приложения.
window.title("Калькулятор индекса массы тела (ИМТ)") #Добавляем название приложения.

После запуска кода ничего не произойдёт. Это не ошибка. На самом деле код выполнился и окно закрылось. Необходимо явно указать, что окно приложения не должно закрываться до тех пор, пока пользователь сам не сделает этого. Для этого к коду добавим функцию window.mainloop (), указывающую на запуск цикла событий:

window.mainloop()

Запустив код, увидим экран приложения:

Скриншот: Tkinter / Skillbox Media

Мы не указали размер окна, поэтому название приложения не помещается в него полностью. Исправим это с помощью метода geometry:

window.geometry('400x300')

Теперь название приложения видно полностью:

Скриншот: Tkinter / Skillbox Media

В окне приложения необходимо разместить несколько элементов с нашего эскиза: два поля ввода информации с подписями и одну кнопку. Важно, чтобы поля не накладывались друг на друга и не уходили за пределы окна. В Tkinter для этого есть несколько методов:

  • pack — используется, когда мы работаем с контейнерами для элементов. Позволяет позиционировать кнопки, надписи или другие элементы внутри контейнеров.
  • place — позволяет позиционировать элементы, указывая точные координаты.
  • grid — размещает элементы по ячейкам условной сетки, разделяющей окно приложения.

Мы воспользуемся комбинацией методов pack и grid. Для начала создадим виджет Frame для размещения надписей, полей ввода и кнопок. Подробное описание работы виджета есть в документации. Мы же используем только два свойства: padx и pady.

Обозначим отступы по вертикали и горизонтали в 10 пикселей для элементов, которые будут расположены внутри Frame:

frame = Frame(
   window, #Обязательный параметр, который указывает окно для размещения Frame.
   padx = 10, #Задаём отступ по горизонтали.
   pady = 10 #Задаём отступ по вертикали.
)
frame.pack(expand=True) #Не забываем позиционировать виджет в окне. Здесь используется метод pack. С помощью свойства expand=True указываем, что Frame заполняет весь контейнер, созданный для него.


В окне приложения нам необходимо добавить три вида виджетов: поле для ввода информации (Entry), текстовые надписи (Label) и кнопку (Button).

Начнём с надписей. Воспользуемся виджетом Label:

height_lb = Label(
   frame,
   text="Введите свой рост (в см)  "
)
height_lb.grid(row=3, column=1)

Мы передаём виджету Label два параметра:

  • frame — используем заготовку виджета Frame, в которой уже настроены отступы по вертикали и горизонтали.
  • text — текст, который должен быть выведен на экран.

Для позиционирования виджета используем метод grid. Укажем, что текст должен располагаться в ячейке с координатами «3-я строка, 1-й столбец». Если запустим код, то увидим там единственный элемент:

Скриншот: Tkinter / Skillbox Media

Сейчас элемент расположен в центре окна, но он займёт правильное положение, когда мы напишем другие элементы.

Добавим вторую надпись о весе аналогичным образом, но при позиционировании в grid укажем следующую, четвёртую строку:

weight_lb = Label(
   frame,
   text="Введите свой вес (в кг)  ",
)
weight_lb.grid(row=4, column=1)

Запускаем код и смотрим на результат:

Скриншот: Tkinter / Skillbox Media

Теперь добавим поля для ввода пользовательской информации, используя виджет Entry:

height_tf = Entry(
   frame, #Используем нашу заготовку с настроенными отступами.
)
height_tf.grid(row=3, column=2)

Для позиционирования мы также воспользовались методом grid. Обратите внимание, что наш элемент должен быть расположен напротив надписи «Введите свой рост (в см)». Поэтому мы используем ячейку в той же строке, но уже во втором столбце. Запустим код и посмотрим на результат:

Скриншот: Tkinter / Skillbox Media

Всё получилось. Остаётся по аналогии добавить поле ввода веса:

weight_tf = Entry(
   frame,
)
weight_tf.grid(row=4, column=2, pady=5)

Посмотрим на результат:

Скриншот: Tkinter / Skillbox Media

Теперь добавим кнопку, которая будет запускать расчёт ИМТ. Сделаем это с помощью виджета Button:

cal_btn = Button(
   frame, #Заготовка с настроенными отступами.
   text='Рассчитать ИМТ', #Надпись на кнопке.
)
cal_btn.grid(row=5, column=2) #Размещаем кнопку в ячейке, расположенной ниже, чем наши надписи, но во втором столбце, то есть под ячейками для ввода информации.

Посмотрим на результат:

Скриншот: Tkinter / Skillbox Media

Теперь в приложении есть все графические элементы. Остаётся лишь написать код, который будет получать информацию из виджетов Entry и рассчитывать индекс массы тела.


Напишем простую функцию и разберём её построчно:

def calculate_bmi(): #Объявляем функцию.
   kg = int(weight_tf.get()) #С помощью метода .get получаем из поля ввода с именем weight_tf значение веса, которое ввёл пользователь и конвертируем в целое число с помощью int().
   m = int(height_tf.get())/100 #С помощью метода .get получаем из поля ввода с именем height_tf значение роста и конвертируем в целое число с помощью int(). Обязательно делим его на 100, так как пользователь вводит рост в сантиметрах, а в формуле для расчёта ИМТ используются метры.
   bmi = kg/(m*m)#Рассчитываем значение индекса массы тела.
   bmi = round(bmi, 1) #Округляем результат до одного знака после запятой.

Функция готова. Но теперь нам необходимо оценить полученный результат расчёта и вывести сообщение для пользователя.


Дополним нашу функцию calculate_bmi. Воспользуемся условным оператором if, чтобы учесть полученные значения ИМТ, и методом Tkinter messagebox для отображения сообщения во всплывающем окне:

if bmi < 18.5:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует недостаточному весу')
   elif (bmi > 18.5) and (bmi < 24.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует нормальному весу')
   elif (bmi > 24.9) and (bmi < 29.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует избыточному весу')
   else:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует ожирению')

Остаётся последний шаг — наша функция должна запускаться при нажатии на кнопку «Рассчитать ИМТ». Для этого добавим свойство command в виджет Button:

cal_btn = Button(
   frame,
   text='Рассчитать ИМТ',
   command=calculate_bmi #Позволяет запустить событие с функцией при нажатии на кнопку.
)
cal_btn.grid(row=5, column=2)

Запустим код и посмотрим на результат:

Источник: Tkinter / Skillbox Media

Всё работает. Функция получает данные из полей ввода и рассчитывает индекс массы тела, показывая результат на экране.

from tkinter import *
from tkinter import messagebox
 
def calculate_bmi():
   kg = int(weight_tf.get())
   m = int(height_tf.get())/100
   bmi = kg/(m*m)
   bmi = round(bmi, 1)
 
   if bmi < 18.5:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует недостаточному весу')
   elif (bmi > 18.5) and (bmi < 24.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует нормальному весу')
   elif (bmi > 24.9) and (bmi < 29.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует избыточному весу')
   else:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует ожирению')  
 
window = Tk()
window.title('Калькулятор индекса массы тела (ИМТ)')
window.geometry('400x300')
 
 
frame = Frame(
   window,
   padx=10,
   pady=10
)
frame.pack(expand=True)
 
 
height_lb = Label(
   frame,
   text="Введите свой рост (в см)  "
)
height_lb.grid(row=3, column=1)
 
weight_lb = Label(
   frame,
   text="Введите свой вес (в кг)  ",
)
weight_lb.grid(row=4, column=1)
 
height_tf = Entry(
   frame,
)
height_tf.grid(row=3, column=2, pady=5)
 
weight_tf = Entry(
   frame,
)
weight_tf.grid(row=4, column=2, pady=5)
 
cal_btn = Button(
   frame,
   text='Рассчитать ИМТ',
   command=calculate_bmi
)
cal_btn.grid(row=5, column=2)
 
window.mainloop()

Узнать о возможностях Tkinter и особенностях работы с виджетами можно в официальной документации. А если хотите найти больше реальных примеров для практики, советуем две книги:

  • Python GUI Programming with Tkinter. Develop responsive and powerful GUI applications with Tkinter, Алан Мур.
  • Tkinter GUI Programming by Example, Дэвид Лав.

Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.

Участвовать

Время на прочтение
13 мин

Количество просмотров 77K

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

Если вы не знаете Electron, то это по сути веб-браузер (Chromium) в котором работает только ваше веб-приложение… словно настоящая десктопная программа (нет, это не шутка)… это даёт возможность использовать веб-стек и разрабатывать кросс-платформенные десктопные приложения.

Самые новые, хипстерские десктопные приложения в наше время сделаны на Electron, в том числе Slack, VS Code, Atom и GitHub Desktop. Необычайный успех.

Мы писали десктопные программы десятилетиями. С другой стороны, веб только начал развиваться менее 20 лет назад, и на протяжении почти всего этого времени он служил только для доставки документов и анимированных «гифок». Никто не использовал его для создания полноценных приложений, даже самых простых!

Десять лет назад невозможно было себе представить, что стек веб-технологий можно использовать для создания десктопного приложения. Но наступил 2017 год, и много умных людей полагают, что Electron — отличная идея!

Здесь не столько результат превосходства веб-стека для создания приложений (он далёк от такого превосходства, и вряд ли кто-то будет спорить, что веб — это бардак), сколько провал существующих фреймворков для разработки UI на десктопах. Если люди предпочитают отгружать со своими программами целый веб-браузер только для того, чтобы использовать отличные инструменты вроде JavaScript (сарказм) для разработки, что-то пошло совершенно не так.

Так что это за ужасные альтернативы, которые проиграли конкурентную борьбу веб-стеку?

Я решил взглянуть и создать реальное приложение на одной из этих технологий.

Альтернативы Electron

Ели вы не возражаете, что несколько групп разработки будут создавать разные версии приложения под разные ОС, то варианты выглядят примерно так: AppKit для MacOS, WPF для Windows (я не специалист по разработке под конкретные платформы, так что дайте знать, пожалуйста, какие варианты в наши дни более популярны).

Однако реальные конкуренты Electron — это мультиплатформенные фреймворки. Думаю, среди них самыми популярными сегодня являются GTK+, Qt и JavaFX.

GTK+

GTK+ написан на C, но связан со многими другими языками. Этот фреймворк использовался для разработки прекрасной платформы GNOME-3.

Qt

Qt кажется самой популярной альтернативой Electron в дискуссиях, которые попадались мне на глаза… Это библиотека C++, но тоже связанная с другими языками (хотя кажется, что никакие из них не поддерживаются на коммерческой основе, и сложно сказать, насколько они доработаны). Qt вроде бы популярный выбор для встроенных систем.

JavaFX

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

Что бы вы ни думали о JVM, не существует никакой другой платформы (кроме, может быть, самого Electron!), настолько простой для кросс-платформенной разработки. Как только вы создали свой jar, на любой платформе, вы можете распространять его среди пользователей всех ОС — и он просто будет работать.

При большом разнообразии языков, которые сейчас поддерживаются в JVM, выбор языка тоже не должен стать проблемой: определённо найдётся такой, какой вам понравится (в том числе JavaScript, если вы не способны от него отказаться), и вы можете использовать JavaFX с любым языком JVM без особых проблем. В этой статье, кроме Java, я покажу немного кода на Kotlin.

Сам UI создаётся просто кодом (если у вас есть замечательная поддержка IDE от IntelliJ, Eclipse или NetBeans: это всё отличные бесплатные IDE, которые, наверное, превосходят любых конкурентов и, кстати, представляют собой самые лучшие образцы десктопных приложений на Java) или в визуальном конструкторе UI: SceneBuilder (который умеет интегрироваться в IntelliJ) или NetBeans Visual Debugger.

История JavaFX

JavaFX — не новая технология. Она появилась в декабре 2008 года и сильно отличалась от того, что мы видим сегодня. Идея заключалась в создании современного фреймворка UI для замены устаревшего Swing Framework, который являлся официальным фреймворком JVM с конца 90-х.

Oracle чуть не испортила всё с самого начала, приступив к созданию особого, декларативного языка, который предполагалось использования для создания UI приложений. Это не очень хорошо восприняли Java-разработчики, и та инициатива чуть не погубила JavaFX.

Заметив проблему, Oracle решила выпустить JavaFX 2 в 2011 году без собственного особого языка, а вместо этого применив FXML в качестве опции для чистого Java-кода (как мы увидим позже).

Около 2012 года JavaFX обрёл некую популярность, а Oracle приложила значительные усилия для улучшения и популяризации этой платформы. С версии 2.2 фреймворк JavaFX стал достаточно цельным фреймворком, но его по-прежему не включали в стандартную среду выполнения Java (хотя он всегда поставлялся вместе с JDK).

Только с версии JavaFX 8 (изменение версии сделано для соответствия Java  8) он стал частью стандартного рантайма Java.

Сегодня фреймворк JavaFX может и не является крупным игроком в мире UI, но на нём сделано немало реальных приложений, у него есть довольно много связанных библиотек и его портировали на мобильную платформу.

Создание приложения JavaFX

В своём приложении для просмотра логов LogFX, я решил просто использовать Java (потому что там в основном довольно низкоуровневый код, а я хотел сконцентрироваться на скорости и малом размере пакета) и IntelliJ в качестве IDE. Я почти решился писать на Kotlin, но поддержка Java в IntelliJ оказалась настолько хорошей, так что писать на Java (точнее, позволить IntelliJ делать это за меня — это ближе к истине) стало не такой большой проблемой, чтобы оправдать лишние 0,9 МБ в дистрибутиве.

Я решил не использовать FXML (язык описания GUI для JavaFX на основе XML) или визуальный конструктор UI, потому что интерфейс у программы очень простой.

Итак, посмотрим на какой-нибудь код!

Java Hello World

Приложение JavaFX — это просто класс, который расширяет javafx.application.Application и показывает JavaFX Stage.

Вот как пишется Hello World на JavaFX:

 public class JavaFxExample extends Application {

     @Override
     public void start(Stage primaryStage) throws Exception {
         Text helloWorld = new Text("Hello world");
         StackPane root = new StackPane(helloWorld);
         primaryStage.setScene(new Scene(root, 300, 120));
         primaryStage.centerOnScreen();
         primaryStage.show();
     }

     public static void main(String[] args) {
         launch(JavaFxExample.class, args);
     }
 }

src/main/java/main/JavaFxExample.java

На «маке» этот код покажет примерно такое:

FXML+Java Hello World

Если вам трудно писать код для UI и вы предпочитаете использовать язык разметки, вот эквивалент того же кода с FXML:

 <?xml version="1.0" encoding="UTF-8"?>

 <?import javafx.scene.layout.StackPane?>
 <?import javafx.scene.Scene?>
 <?import javafx.scene.text.Text?>
 <Scene xmlns="http://javafx.com/javafx"
        width="300.0" height="120.0">
     <StackPane>
         <Text>Hello world</Text>
     </StackPane>
 </Scene>

src/main/resources/main/Example.fxml

 public class JavaFxExample extends Application {

     @Override
     public void start(Stage primaryStage) throws Exception {
         Scene scene = FXMLLoader.load(getClass().getResource("Example.fxml"));
         primaryStage.setScene(scene);
         primaryStage.centerOnScreen();
         primaryStage.show();
     }

     public static void main(String[] args) {
         launch(JavaFxExample.class, args);
     }
 }

src/main/java/main/JavaFxExample.java

Обратите внимание, что IntelliJ поддерживает FXML и свяжет его содержимое с соответствующим кодом Java и наоборот, подсветит ошибки, сделает автодополнение, справится с импортом, покажет встроенную документацию и так далее, что довольно круто… но как я раньше сказал, решено было не использовать FXML, поскольку задуманный UI был очень простым и довольно динамичным… так что я больше не покажу кода FXML. Если интересно, изучите руководство по FXML.

Hello World на Kotlin+TornadoFX

Прежде чем двигаться дальше, давайте посмотрим, как такой код выглядит на современном языке вроде Kotlin с его собственной библиотекой для написания приложений JavaFX, которая называется TornadoFX:

 class Main : App() {
     override val primaryView = HelloWorld::class
 }

 class HelloWorld : View() {
     override val root = stackpane {
         prefWidth = 300.0
         prefHeight = 120.0
         text("Hello world")
     }
 }

src/main/kotlin/main/app.kt

Многим может показаться привлекательным использовать Kotlin и JavaFX, особенно если вы предпочитаете безопасность типов (в TornadoFX есть приятная функция «типобезопасные таблицы стилей») и если добавить лишние 5 МБ в приложения для вас не проблема.

Даже если подобные накладные расходы кажутся чрезмерными и вы не хотите включать фреймворк в программу (и разбираться со всеми его сложностями и причудами), то Kotlin нормально работает и с чистым JavaFX.

Стили и темы для пользовательского интерфейса JavaFX

Теперь, когда мы разобрались с основами JavaFX, посмотрим, как применить стили для приложений JavaFX.

Также как существуют различные подходы к макетированию, существуют и разные варианты стилей для JavaFX.

Предположим, что мы хотим сделать тёмный фон и белый текст, как показано на скриншоте:

Программные и встроенные стили

Один из вариантов (мучительный, но зато с безопасными типами) — сделать это программным способом:

 root.setBackground(new Background(new BackgroundFill(
         Color.DARKSLATEGRAY, CornerRadii.EMPTY, Insets.EMPTY)));

 helloWorld.setStroke(Color.WHITE);

Более простой программный способ — установить стили в CSS:

 root.setStyle("-fx-background-color: darkslategray");
 helloWorld.setStyle("-fx-stroke: white");

Обратите внимание, что здесь IntelliJ опять обеспечивает автодополнение для значений строк.

Если вы используете FXML:

 <StackPane style="-fx-background-color: darkslategray">
     <Text style="-fx-stroke: white">Hello world</Text>
 </StackPane>

То же самое…

Использование отдельных таблиц стилей

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

Для этого сначала создаём таблицу стилей:

 .root {
     -fx-base: darkslategray;
 }

 Text {
     -fx-stroke: white;
 }

src/main/resources/css/hello.css

Теперь добавляем её в Scene:

 primaryStage.getScene().getStylesheets().add("css/hello.css");

И всё.

Заметьте, что таблицы стилей устанавливают не только фоновый цвет StackPane как darkslategray, но и меняют основной цвет темы.

Это значит, что все управляющие элементы и «фоновые» элементы примут цвет, основанный на этом цвете. Довольно изящная функция, потому что вы можете устанавливать цвета на базе основного цвета. Так вы гарантируете, что если изменить основной цвет, то практически всё будет хорошо выглядеть в новой расцветке.

Например, в нашем случае более подходящим цветом текста станет не белый, а «противоположный» цвет относительно основного цвета темы, чтобы текст всегда оставался читаемым:

 -fx-stroke: ladder(-fx-base, white 49%, black 50%);

Таблицы стилей JavaFX довольно умные, для дополнительной информации см. CSS Reference Guide.

Вот пример простого приложения, где мы поставили кнопку вместо текста. Слева показаны стили по умолчанию, а справа используется таблица стилей, которую мы только что создали:


Слева: стили по умолчанию JavaFX. Справа: кастомные стили, созданные выше

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

Вот как LogFX выглядит в итоге с темой по умолчанию:

Обратите внимание, что для кнопок я использовал иконки FontAwesome. Было довольно просто стилизовать кнопки в CSS. Просто убедитесь в том, чтобы шрифт устанавливался как можно раньше с помощью такой инструкции:

 Font.loadFont( LogFX.class.getResource( "/fonts/fontawesome-webfont.ttf" ).toExternalForm(), 12 );

С кастомными таблицами стилей можно кардинально изменить внешний вид приложения. Например, вот очень зелёная тема в Linux Mint:

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

В завершение хотел бы упомянуть классные эффекты, которые есть в JavaFX… Я хотел сделать начальный экран, который бы хорошо выглядел просто с форматированным текстом.

В JavaFX это делается просто. Вот что у меня получилось (я сделал экран на основе образца GroovyFX):

И вот какая таблица стилей соответствует этому стартовому экрану:

 Text {
     -fx-fill: white;
 }

 #logfx-text-log {
     -fx-font-family: sans-serif;
     -fx-font-weight: 700;
     -fx-font-size: 70;
     -fx-fill: linear-gradient(to top, cyan, dodgerblue);
 }

 #logfx-text-fx {
     -fx-font-family: sans-serif;
     -fx-font-weight: 700;
     -fx-font-size: 86;
     -fx-fill: linear-gradient(to top, cyan, dodgerblue);
     -fx-effect: dropshadow(gaussian, dodgerblue, 15, 0.25, 5, 5);
 }

Здесь возможно создание очень неплохих эффектов. Для дополнительной информации см. руководство.

В следующих разделах обсудим, как менять виды (экраны), перезагружать код на лету (hot reload) и обновлять таблицы стилей во время работы программы.

Дизайн, отладка и перезагрузка кода

Практически невозможно создавать интерфейс пользователя без возможности мгновенно просматривать изменения. Поэтому важной частью любого фреймворка UI является «горячая» перезагрузка кода или некая разновидность конструктора UI.

У JavaFX (и у самой JVM) есть несколько вариантов решения этой проблемы.

SceneBuilder

Первое из них — это SceneBuilder, визуальный конструктор UI, который позволяет создавать FXML, просто перетаскивая компоненты UI.

Его можно интегрировать в любые Java IDE, что упрощает создание новых видов (экранов).

Мне раньше приходилось использовать SceneBuilder для создания форм и тому подобных сложных видов, но я обычно просто набрасывал там что-то по-быстрому, а затем редактировал код вручную для приведения его к конечному виду.

Если вы так сделаете, а потом откроете вид в SceneBuilder, он по-прежнему будет нормально работать, так что можно поочерёдно редактировать код вручную или в SceneBuilder — и просматривать результат.

ScenicView

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

Представьте это как эквивалент инструментов разработчика в браузере.

Для запуска ScenicView со своим приложением просто скачайте jar и передайте параметр -javaagent:/path-to/scenicView.jar в JVM.

ScenicView позволяет изменять и удалять узлы, отслеживать события и читать документацию Javadocs для выбранных элементов.

Горячая перезагрузка кода JVM

Если хотите изменить код приложения, который напрямую не связан с UI, то длоя этого подходит отладчик Java с горячей заменой кода во время работы приложения. Базовая поддержка перезагрузки кода имеется в Oracle JVM и HotSpot. Думаю, что она есть и в OpenJDK JVM.

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

Зато есть расширение HotSpot VM под названием DCEVM (Dynamic Code Evolution VM) с гораздо большей функциональностью: добавление/удаление методов и полей, добавление/удаление классов, изменение значения итоговых переменных и прочее. В другой статье я уже писал о нём и о других способах перезагрузки кода в работающей JVM.

Я использовал это расширение при разработке LogFX — и оно отлично себя проявило. Если не закрыть и заново не открыть окно, то вид не меняется автоматически при перезагрузке кода, но это не такая большая проблема, если менять что-то в Stage… к тому же, если вы хотите изменить только компонент UI, то можно использовать ScenicView или просто вернуться в ScenicBuilder и как угодно поменять дизайн.

Для запуска DCEVM нужно только установить его и сверить номера версий расширения и JVM. После этого приложение запускается с отладчиком — и каждый раз после перекомпиляции в IDE новый код автоматически подгрузится в работающую программу.

В IntelliJ после изменения класса и перекомпиляции вы увидите нечто подобное (Cmd+F9 на «маке»):

Обновление таблиц стилей

JavaFX не обновляет автоматически таблицы стилей. Но для LogFX я хотел сделать такую возможность, чтобы можно было изменять стили — и немедленно наблюдать эффект в приложении.

Поскольку LogFX — программа для просмотра логов, у неё довольно продвинутый FileChangeWatcher, который подходит для просмотра стилей и их перезагрузки.

Но он работает только если стили поставляются из отдельного файла, а не из самого приложения (из jar).

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

Я использовал эту функцию в процессе разработки, и она очень впечатляет. Если вам нужна такая же фича, можно написать собственный диспетчер файлов или скопировать мой (в конце концов, он с открытыми исходниками).

Для выбора таблицы стилей как файла (в отличие от ресурса jar), к сожалению, придётся использовать разный синтаксис под Unix/Mac и Windows. Вот такой метод я применил, чтобы решить проблему:

 private static String toAbsoluteFileUri( File file ) {
     String absolutePath = file.getAbsolutePath();
     if ( File.separatorChar == '\' ) {
         // windows stuff
         return "file:///" + absolutePath.replace( "\", "/" );
     } else {
         return "file:" + absolutePath;
     }
 }

Это работает на Mac, Windows и Linux Mint. Но это только первая из двух проблем, которые возникают на разных ОС (вторая — то, что не отображается иконка в системном трее на Mac, хотя есть уродливое обходное решение этой проблемы). В остальном JavaFX всё абстрагирует довольно хорошо по большей части.

Наконец, когда диспетчер определил изменение в файле с таблицами стилей, вы можете обновить стиль, просто удалив его и немедленно добавив обратно:

 Runnable resetStylesheet = () -> Platform.runLater( () -> {
     scene.getStylesheets().clear();
     scene.getStylesheets().add( stylesheet );
 } );

Такой метод неплохо работает. Но если вы не хотите сами его писать, то ScenicView тоже умеет отслеживать таблицы стилей во внешних файлах (но не внутри jar), и TornadoFX тоже это поддерживает, так что здесь есть варианты.

Заключение

Создание приложения на JavaFX стало довольно приятным опытом. У меня имелась некоторая практика написания JavaFX-приложений для работы несколько лет назад (когда JavaFX находился на ранней стадии развития, что теперь уже осталось в прошлом), так что у меня определённо была некая фора… но я также работал как веб-разработчик и теперь не могу поверить, что кто-то предпочтёт использовать веб-стек вместо такой вменяемой среды как JVM.

Созданное приложение LogFX, на мой взгляд, работает очень хорошо, и оно достигло поставленных целей по скорости работы и быстрому отклику, и в то же время оно хорошо выглядит на всех операционных системах без внесения изменений. Пожалуйста, посмотрите сами и выскажите свой мнение:

curl -sSfL https://jcenter.bintray.com/com/athaydes/logfx/logfx/0.7.0/logfx-0.7.0-all.jar -o logfx.jar

Хотя это полностью функциональное приложение, файл jar весит всего 303 килобайта. Это 0,3 МБ, включая несколько картинок и файл шрифта TTF, и ещё несколько файлов HTML и CSS, помимо файлов классов Java!

Конечно, приложение не включает саму виртуальную машину JVM, но JVM не является частью программы и может использоваться для многих приложений! В Java 9 вы можете вообще создавать нативные исполняемые файлы, включая в них только необходимые части JVM, так что если вашим пользователям не нравится простой jar, то упакуйте его как нативное приложение, как я показывал в предыдущей статье (небольшое нативное приложение JVM займёт примерно 35 МБ или 21 МБ после оптимизации).

Для работы LogFX требуется около 50 МБ RAM (не для самого приложения, а в основном для JavaFX). В этом можно убедиться, запустив программу такой командой:

java -Xmx50m -jar logfx.jar

Это кардинально отличается от приложений Electron, которые обычно жрут 200 МБ уже в момент запуска.

JavaFX не идеальна и есть много областей, которые всё ещё нуждаются в улучшении. Одна из них — распространение и автоматическое обновление программ. Текущее решение, JNLP и Java WebStart, кажется слабо реализованным, хотя имеются альтернативы от сообщества, такие как Getdown и FxLauncher, а если вы хотите правильный нативный инсталлятор, то имеется и коммерческое решение Install4J (кстати, у Install4J есть бесплатные лицензии для проектов open source).

Осталось много вещей насчёт JavaFX, которые у меня не нашлось времени упомянуть в этой и так уже длинной статье, но некоторые из них, я считаю, достойны дополнительного изучения, если вам интересно:

  • Привязывание свойств позволяет легко внедрять реактивные UI.
  • Диаграммы на JavaFX выглядят великолепно.
  • Поддержка 3D в JavaFX.
  • Тестировочные фреймворки для JavaFX: TestFX и Automaton (дисклеймер: я являюсь автором Automaton и работал с командой, которая изначально разработала TestFX).
  • Демо-приложения JavaFX Ensemble показывают бóльшую часть возможностей JavaFX в реальной работе.

Задавались ли вы когда-нибудь вопросом, возможно ли создавать кроссплатформенные настольные приложения на HTML, CSS и JavaScript? С Electron это становится возможным. В этой статье мы рассмотрим основы Electron и напишем простое приложение.

Функциональность нашего приложения будет заключаться в том, что при нажатии определённой клавиши на клавиатуре будет воспроизводиться соответствующий звук.

Прим. перев.  Для создания приложений с использованием Electron не требуется знание таких языков программирования, как C++ и Python, — знания веб-технологий будет достаточно. Если вы не ограничены веб-технологиями, хорошей альтернативой использованию Electron будут GTK+ и Qt: в отличие от «родных» приложений, приложения, написанные с использованием Electron, обладают множеством недостатков, в том числе крайне неэффективно используют свободную оперативную память компьютера. Статью по QT можно посмотреть у нас на сайте.

Electron — фреймворк для кроссплатформенной разработки настольных приложений с использованием Chromium и Node.js.

С его помощью можно легко написать приложение с использованием HTML, CSS и JavaScript, которое «из коробки» будет совместимо с Mac, Windows и Linux.

Другие встроенные особенности включают:

  • Автоматические обновления приложений;
  • Нативные меню и уведомления;
  • Сообщения об ошибках, которые можно отправлять на удалённый сервер;
  • Отладка и профилирование — модуль content Chromium ищет места, где проседает производительность. Вы также можете использовать инструменты разработчика в своём приложении;
  • Быстрое и простое создание установочных пакетов для Windows.

Если вы довольны тем, что предлагает Electron, то давайте займёмся нашим приложением. Однако прежде чем мы начнём, необходимо установить Node.js. Также вам пригодится аккаунт на GitHub, чтобы хранить и обновлять своё приложение. Это делать необязательно, но желательно, так как в наше время важно знать, как работает GitHub.

Принимаемся за работу

Когда вы закончите с подготовкой, откройте терминал и следуйте дальнейшим инструкциям, чтобы клонировать репозиторий Electron Quick Start на ваш компьютер. Именно на основе Electron Quick Start мы и будем писать наше приложение.

# Клонируем репозиторий
git clone https://github.com/electron/electron-quick-start
# Переходим к нему
cd electron-quick-start
# Устанавливаем зависимости и запускаем
npm install && npm start

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

electron

Как было сказано ранее, в своём приложении вы можете использовать инструменты разработчика. Всё, что вы можете делать с инструментами вашего браузера, вы также можете делать и в приложении. Потрясающе!

Архитектура приложения

Теперь давайте взглянем на код и структуру приложения.

electron
Структура самая обычная, очень похожая на ту, что используется при создании веб-страниц. У нас есть:

  • index.html — HTML-страница, отвечает за внешний вид;
  • main.js — создаёт окна и управляет системными событиями;
  • package.json — описание пакета (имя, зависимости и т.д.) для npm;
  • renderer.js — управляет процессами рендеринга.

Возможно, вы задаётесь вопросом: «Что за звери эти процессы рендеринга и основной процесс?» Давайте разбираться.

Что есть процесс?

Когда вы видите слово «процесс», думайте о процессе в операционной системе. По сути, это экземпляр программы, работающей в системе.

Если запустить наше Electron-приложение и заглянуть в Диспетчер Задач в Windows, Мониторинг Активности в macOS или Системный Монитор в Linux, то можно увидеть процессы, связанные с приложением.

electron

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

Допустим, мы хотим написать цикл в процессе рендеринга:

var a = 1;
for ( a = 1; a < 10; a ++) {
 console.log('Это цикл for');
}

Этот код никак не повлияет на основной процесс.

Основной процесс

Этот процесс контролирует происходящее в приложении. В него встроен полноценный Node.js API. Из него создаются процессы рендеринга и открываются диалоговые окна. Также он отвечает за разное взаимодействие с операционной системой, запускает и закрывает приложение.

Файл с этим процессом принято называть main.js, но вы можете дать ему любое имя. Также вы можете менять файл основного процесса через package.json. Чтобы проверить, как это работает, откройте файл package.json, замените строку "main": "main.js" на "main": "mainTest.js" и попробуйте запустить приложение.

Имейте в виду, что основной процесс может быть только один.

Процесс рендеринга

Этот процесс представляет собой окно браузера в вашем приложении. В отличие от основного процесса, процессов рендеринга может быть несколько и каждый из них будет независим от остальных. За счёт этого ошибка в одном из них никак не повлияет на другие. Скажем за это спасибо многопроцессорной архитектуре Chromium. Также эти окна можно спрятать или изменить, так как они работают как HTML-файлы.

Но в Electron у нас также есть доступ к Node.js API. Это значит, что мы можем открывать диалоговые окна и взаимодействовать с операционной системой прочими способами.

Представить происходящее можно следующим образом:

electron

Остаётся один вопрос. Можно ли как-нибудь связать эти процессы?

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

Специально для таких целей существует межпроцессное взаимодействие (IPC). Его можно использовать для передачи сообщений между основным процессом и процессами рендеринга.

Вот мы и разобрались с основами процессов для создания Electron-приложения. Возвращаемся к коду!

Добавим индивидуальности

Поменяем название папки с нашим приложением на более подходящее. Измените название папки с electron-quick-start на hear-me-type-tutorial. Откройте папку заново в текстовом редакторе или IDE. Теперь похимичим с файлом package.json. Он содержит важную информацию о нашем приложении: имя, версию, автора, лицензию и многое другое.

Давайте укажем, кто является автором приложения. Для этого найдите параметр author и замените его значение на своё имя. Должно получиться что-то такое: "author": "Carol Pelu". Также вы можете изменить и другие параметры вроде name и description, которые отвечают за название приложения и его описание соответственно. В итоге должно получиться примерно так:

electron

Помните, что вы всегда можете ввести npm start в терминале, чтобы запустить приложение и посмотреть на внесённые изменения.

Пора идти дальше и добавить в наше приложение функциональность.

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

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

Для этого мы создадим элементы audio со своим id для каждой клавиши. Затем напишем switch-конструкцию, чтобы понять, какая клавиша была нажата. После этого воспроизведём звук, привязанный к этой клавише. Если это звучит сложно — не беспокойтесь, мы разберёмся со всем пошагово.

Скачайте этот архив с нужными нам аудиофайлами. Пора встраивать аудио в наше приложение.

Откройте index.html и внутри <body> создайте новый элемент <div> с классом audio.

Затем внутри этого <div> создайте элемент <audio> с id равным "A", src равным "sounds/A.mp3" и атрибутом preload равным "auto".

Мы используем preload="auto", чтобы сказать приложению, что оно должно загрузить весь аудиофайл после загрузки страницы. Главным файлом нашего приложения является index.html, и все аудио загрузятся после запуска приложения.

В итоге код должен выглядеть так:

<div class="audio">
    <audio id="A" src="sounds/A.mp3" preload="auto"></audio>
</div>

Теперь index.html имеет примерно такой вид:

electron
Сейчас <audio> указывает на неизвестный файл. Давайте создадим папку sounds и поместим туда все аудиофайлы.

Отлично! Теперь нам не хватает только JavaScript-кода.

Создадим новый файл functions.js. Давайте запросим его в файле index.html, чтобы JS-код был готов к использованию, когда приложение будет запущено.

Следуя примеру require(./renderer.js'), добавим строку require('./functions.js') прямо под ней.

Проект теперь должен иметь такой вид:

electron

Отлично! Теперь, когда уже почти всё готово, наступает момент истины.

Откроем functions.js и добавим туда следующий код:

document.onkeydown = function(e) {
    switch (e.keyCode) {
        case 65:
            document.getElementById('A').play();
            break;
        default:
            console.log("Клавиша на обнаружена!");
    }
};

Откройте консоль, убедитесь, что вы находитесь в директории проекта и введите npm start для запуска приложения.

Сделайте звук погромче и нажмите клавишу «А» на клавиатуре.

#крышесносно

electron

JS-код довольно простой. Мы используем событие onkeydown для объекта document, чтобы выяснить, к какому HTML-элементу мы обращаемся. Имейте в виду, что объектом document является главное окно нашего приложения.

В анонимной функции мы используем switch-выражение, которое выясняет Unicode-значение нажатой клавиши. Если это значение правильное, то воспроизводится звук. В противном случае в консоль выводится сообщение: «Клавиша не обнаружена!».

Как вы могли заметить, у нас есть файлы для клавиш от A до Z и от 0 до 9. Поэтому давайте используем и их, чтобы «А» было не так одиноко.

Вернёмся к index.html и создадим элемент <audio> для всех клавиш, к которым у нас есть аудио. Да, можете просто скопипастить:

<audio id="B" src="sounds/B.mp3" preload="auto"></audio>
<audio id="C" src="sounds/C.mp3" preload="auto"></audio>
<audio id="D" src="sounds/D.mp3" preload="auto"></audio>
<audio id="E" src="sounds/E.mp3" preload="auto"></audio>
<audio id="F" src="sounds/F.mp3" preload="auto"></audio>
<audio id="G" src="sounds/G.mp3" preload="auto"></audio>
<audio id="H" src="sounds/H.mp3" preload="auto"></audio>
<audio id="I" src="sounds/I.mp3" preload="auto"></audio>
<audio id="J" src="sounds/J.mp3" preload="auto"></audio>
<audio id="K" src="sounds/K.mp3" preload="auto"></audio>
<audio id="L" src="sounds/L.mp3" preload="auto"></audio>
<audio id="M" src="sounds/M.mp3" preload="auto"></audio>
<audio id="N" src="sounds/N.mp3" preload="auto"></audio>
<audio id="O" src="sounds/O.mp3" preload="auto"></audio>
<audio id="P" src="sounds/P.mp3" preload="auto"></audio>
<audio id="Q" src="sounds/Q.mp3" preload="auto"></audio>
<audio id="R" src="sounds/R.mp3" preload="auto"></audio>
<audio id="S" src="sounds/S.mp3" preload="auto"></audio>
<audio id="T" src="sounds/T.mp3" preload="auto"></audio>
<audio id="U" src="sounds/U.mp3" preload="auto"></audio>
<audio id="V" src="sounds/V.mp3" preload="auto"></audio>
<audio id="W" src="sounds/W.mp3" preload="auto"></audio>
<audio id="X" src="sounds/X.mp3" preload="auto"></audio>
<audio id="Y" src="sounds/Y.mp3" preload="auto"></audio>
<audio id="Z" src="sounds/Z.mp3" preload="auto"></audio>
<audio id="0" src="sounds/0.mp3" preload="auto"></audio>
<audio id="1" src="sounds/1.mp3" preload="auto"></audio>
<audio id="2" src="sounds/2.mp3" preload="auto"></audio>
<audio id="3" src="sounds/3.mp3" preload="auto"></audio>
<audio id="4" src="sounds/4.mp3" preload="auto"></audio>
<audio id="5" src="sounds/5.mp3" preload="auto"></audio>
<audio id="6" src="sounds/6.mp3" preload="auto"></audio>
<audio id="7" src="sounds/7.mp3" preload="auto"></audio>
<audio id="8" src="sounds/8.mp3" preload="auto"></audio>
<audio id="9" src="sounds/9.mp3" preload="auto"></audio>

Потрясающе! Теперь давайте провернём то же самое в functions.js.

Код для каждой клавиши можно найти здесь. Но вы по-прежнему можете просто скопировать:

document.onkeydown = function(e) {
    switch (e.keyCode) {
        case 48:
            document.getElementById('0').play();
            break;
        case 49:
            document.getElementById('1').play();
            break;
        case 50:
            document.getElementById('2').play();
            break;
        case 51:
            document.getElementById('3').play();
            break;
        case 52:
            document.getElementById('4').play();
            break;
        case 53:
            document.getElementById('5').play();
            break;
        case 54:
            document.getElementById('6').play();
            break;
        case 55:
            document.getElementById('7').play();
            break;
        case 56:
            document.getElementById('8').play();
            break;
        case 57:
            document.getElementById('9').play();
            break;
        case 65:
            document.getElementById('A').play();
            break;
        case 66:
            document.getElementById('B').play();
            break;
        case 67:
            document.getElementById('C').play();
            break;
        case 68:
            document.getElementById('D').play();
            break;
        case 69:
            document.getElementById('E').play();
            break;
        case 70:
            document.getElementById('F').play();
            break;
        case 71:
            document.getElementById('G').play();
            break;
        case 72:
            document.getElementById('H').play();
            break;
        case 73:
            document.getElementById('I').play();
            break;
        case 74:
            document.getElementById('J').play();
            break;
        case 75:
            document.getElementById('K').play();
            break;
        case 76:
            document.getElementById('L').play();
            break;
        case 77:
            document.getElementById('M').play();
            break;
        case 78:
            document.getElementById('N').play();
            break;
        case 79:
            document.getElementById('O').play();
            break;
        case 80:
            document.getElementById('P').play();
            break;
        case 81:
            document.getElementById('Q').play();
            break;
        case 82:
            document.getElementById('R').play();
            break;
        case 83:
            document.getElementById('S').play();
            break;
        case 84:
            document.getElementById('T').play();
            break;
        case 85:
            document.getElementById('U').play();
            break;
        case 86:
            document.getElementById('V').play();
            break;
        case 87:
            document.getElementById('W').play();
            break;
        case 88:
            document.getElementById('X').play();
            break;
        case 89:
            document.getElementById('Y').play();
            break;
        case 90:
            document.getElementById('Z').play();
            break;
        default:
            console.log("Key is not found!");    
    }
};

Прим. перев.  Как вы, вероятно, заметили, такая switch-case конструкция выглядит довольно громоздко. А как вы бы оптимизировали этот участок кода? Делитесь своими вариантами в комментариях.

Вот мы и закончили наше приложение! Поздравляем!

electron

Основная функциональность в приложении присутствует, но его ещё можно доработать.

Дополняем приложение

Да, у нас всё работает, но всё равно то тут, то там чего-то не хватает. Например, в index.html вы можете изменить заголовок приложения и содержимое основного окна. Кроме того, у нас нет никакого дизайна, нет красивых цветов и нет картинок с котиками. Включите воображение и попробуйте улучшить внешний вид приложения.

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

Тестируем, тестируем и ещё раз тестируем

Хорошее ПО должно быть тщательно протестировано. Попробуйте нажать каждую клавишу, чтобы увидеть, что произойдёт. В лучшем случае вы услышите звук для каждой клавиши, указанной в коде. Но что если вы нажмёте много клавиш подряд так быстро, как только можете? А что насчёт клавиш вроде Home и NumLock, для которых у нас нет звука?

Если вы свернёте приложение и нажмёте клавишу, вы услышите звук? А если окно приложения неактивно, и вы нажмёте клавишу, то что-нибудь произойдёт?

К сожалению, ответ — нет.

Так происходит из-за архитектуры, на которой построен Electron. Вы можете регистрировать нажатия клавиш внутри приложения как в C#, но не можете этого делать за его пределами. Это выходит за рамки привычных Electron-приложений.

Пройдитесь по коду строка за строкой и попробуйте сделать его нерабочим. Посмотрите, что произойдёт и какие ошибки выбросит Electron. Это упражнение поможет вам разобраться в отладке. Если вы знаете слабые места своего приложения, то вы знаете, как их исправить и сделать приложение лучше.

В файле functions.js было использовано устаревшее событие. Сможете его найти? Когда найдёте, подумайте, как его заменить без изменения функциональности приложения.

Использование устаревшего кода — плохая практика. Это может привести к серьёзным багам, о существовании которых вы могли даже не подозревать. Следите за документацией языка, чтобы знать, какие произошли изменения. Всегда будьте в курсе последних событий.

Перевод статьи «How to Build Your First Desktop App with JavaScript Using Electron»

Да, я пишу десктопные приложения под Windows +190

Программирование, Разработка, Разработка под Windows, Анализ и проектирование систем, Блог компании Инфопульс Украина


Рекомендация: подборка платных и бесплатных курсов Python — https://katalog-kursov.ru/

Здравствуйте, меня зовут Владимир и я

анонимный

разработчик десктопных приложений под Windows. В этом месте все должны сказать «Здравствуй, Владимир!», а кто-то может быть добавит «Молодец, что осознал!». А потом все похлопают. Нет, правда, иногда от чтения Хабра у меня возникают именно такое ощущение, что нормально, нет, даже не «нормально», а допустимо и одобряемо сегодня писать только микросервисы для каких-то стартапов, которые будут по какому-то REST API отдавать данные какому-нибудь фронтенду на Ангуляре, который и будет, наконец, показывать пользователю что-то невероятно полезное, вроде таблицы с аггрегированными отзывами о стрижках пуделей с возможностью посмотреть на гуглокартах где бы в вашем городе можно было сделать именно такую стрижку вашему пуделю (несуществующему). А никаких других программ писать уже нет-нет, никак нельзя! Что за чушь?!

Да, многое сегодня происходит в вебе и на мобильных устройствах, но, знаете ли, далеко не всё. Значительная часть приложений по-прежнему является десктопным софтом. И даже (о, ужас!) не под Mac Os или Linux, а под тот самый богомерзкий Windows. И, знаете ли, софт этот живёт, развивается, поддерживается и является ежедневным рабочим инструментом миллионов людей. И никуда он мигрировать не собирается, потому что есть причины, по которым иногда именно десктопное приложение является лучшим вариантом.

Десктопный софт работает без интернета

Работа пользователя не прервется от падения столба на датацентр Амазона или умелого тракториста в соседнем дворе. Вся мощь криворуких сисадминов провайдера, Великих Правительственных Файрволов, горе-хакеров, облачных сервисов, которые на самом деле ни разу не облачные — всё это бессильно перед ПО, которому не нужен интернет, чтобы работать. Пользователь приходит на работу, открывает свой AutocadMayaПО_по_рассчёту_дырчатости_бубликов — и получаёт свой результат, который принесёт его фирме деньги. А больше ничего и не надо.

Лицензирование десктопного софта просто и понятно

Нет, бывают, конечно оригиналы, которые невероятно удачную модель лицензирования «ваша программа через год превратиться в тыкву» заменяют на ещё более удачную «ваша программа через год откатит все апдейты и превратится в семена тыквы». Но это редко. В основном вы покупаете программу, активируете лицензию — и она работает. Всё, что бы там дальше не стрельнуло в голову её авторам — уж по крайней мере эта версия у вас работать не перестанет! 100% гарантия того, что завтра вы включите компьютер и вот этот вот ярлык запустит то же самое, что работало вчера — не прекрасно ли это? Можете ли вы рассчитывать на такую же гарантию у веб-сайта? Да черта с два — вспоминаем недавнюю историю с некоторым популярным сервисом про кино. Ну так ладно кино, а если бы что-то подобное произошло с ресурсом, на который завязана ваша работа и зарплата?

Десктопный софт выглядит одинаково каждый день

Установив некоторую версию софта, человек может научиться работать в ней быстро и эффективно. Со временем ты изучаешь быстрые клавиши, уже не ищешь ту или иную кнопку, ты знаешь что сейчас произойдёт и сколько времени это займёт. Работа становится предсказуемой. Если менеджер в фирме по установке пластиковых окон уже рассчитал в некоторой специализированной программе 100 окон, то время рассчёта 101-го окна он может вам назвать с точностью до пары секунд. И будет прав. Можем ли мы рассчитывать на что-то похожее с веб-сервисами? Ага, разогнались. Как же меня в своё время бесил Gmail, который к такой элементарной вещи как почта каждые 2 недели придумывал то фильтры, то теги, то категории, то 5 разных видов UI, то чат, то ещё какого-то черта лысого. Просто дай мне мою почту и ничего не меняй! Нет, так нельзя, надо вот сюда рюшечку и сюда иконочку. Ну и ладно, пойду-ка я в Outlook. С десктопным софтом вы сами, по крайней мере, решаете когда именно он будет обновляться и до какой версии.

Десктопный софт доступен для расширения

Частенько у десктопного софта есть система плагинов и есть уже готовые плагины, которые можно скачать и поставить. Ну или есть SDK и можно написать плагин самому. Или заплатить за его разработку. А даже если системы плагинов нет, то всё-равно что-то да есть: есть интерфейс, который можно автоматизировать с помощью чего-то типа AutoIt, есть входные и выходные форматы данных, которые можно парсить, есть в конце-концов, бинарники, которые можно дизассамблировать и что-то подправитьпонятьдобавить. Нет, такое, конечно, по лицензии часто делать нельзя, но если очень надо, вот вопрос жизни и смерти человечества, то это по крайней мере физически возможно. А что с сайтом? Зачастую у нас либо нет вообще ничего, поддающегося расширению, либо есть API, который ограничен ровно настолько, чтобы ничего толком полезного с ним было сделать нельзя. Ну, спасибо большое.

Десктопный софт работает

В десктопной программе мне не нужно рассказывать пользователю, что у него старая версия IE или нет Flash или заблокировна Java — я просто поставлю инсталлятором всё необходимое. Мне не нужно его разрешение на геолокацию или доступ к папке с фотографиями — я пропишу его согласие на это в том лицензионном соглашении, которое все принимают не читая. У меня есть доступ к железу. У меня есть доступ к диску. Я могу написать всё, что угодно и не заниматься героическим решением задач типа «как передать данные из одной вкладки браузера в другую» или «как подписать платёж с помощью аппаратного криптоключа».

С десктопным софтом быстрее начать работать

Это кажется парадоксальным — ведь устанавливаемое ПО нужно скачать и установить, а сайт можно просто открыть в браузере. Но давайте посмотрим, что будет дальше: десктопное ПО запустится по двойному клику на иконке и сразу готово к работе. В то время как сайт, скорее всего, попросит вас зарегистрироваться (нудная процедура, ещё небось и капчу разгадывать заставят), потом пришлёт вам письмо для подтверждения почты, потом попросит авторизоваться. Если мы говорим о платных сервисах, то за десктопную программу нужно заплатить 1 раз, а сайт скорее всего попросит подписаться на регулярные взносы. В итоге скачать пару мегабайт и 2 раза нажать «Next» в инсталляторе получается куда быстрее пробежки по граблям при попытке начать пользоваться модным сайтом.

Десктопный софт работает быстро

Да-да, я знаю что Javascript по бенчмаркам работает уже в 2 раза быстрее ассемблера. Да хоть в 10 раз бенчмарки эти ваши будут показывать — что-то не выходят пока что последние Call of Duty и GTA в браузерах. По-старинке гоняют байтики древним нативным кодом. И чего это они? Не понимают ничего, видимо.

Десктопный софт можно контролировать

Саму инсталяху можно проверить антивирусом. И установленную программу можно. А ещё её можно запустить от юзера с ограниченными правами. Или в виртуалке. Или ограничить файрволом. Данные из неё можно сохранить локально, а можно — на удалённый диск. Можно забекапить. Удалить можно. Что из этого всего можно сделать с веб-сайтом? Вы понятия не имеете, что он сделает с вашими данными, куда сохранит, кому продаст, когда потеряет, почему не забекапит и зачем оставит после удаления вашего аккаунта.

Почему же в окружающей нас хабрареальности (и не только) так мало уделяется внимания десктопному ПО и так много информации о веб- и мобильной разработке?

Всё просто — а никто пока не знает, как это всё правильно писать. У десктопного софта было пару десятилетий чтобы выработать свои правила, подходы и особенности — и в общем, у него это получилось. Кто 15 лет назад писал на Delphi может писать на нём и сейчас и не сказать, чтобы уж как-то сильно всё поменялось. На вопрос «Как в .NET показать месседж-бокс?» ответ был дан давно и с тех пор не менялся. Все основные фреймворки и библиотеки сложились и устаканились и сегодня мало кто рискнёт стукнуть кулаком по столу и заявить что всё вот это ваше WinFormsWPFQtVCL не правильно и сейчас я тут достану шашку и покажу как надо! А в Javascript и Mobile это происходит каждые несколько месяцев. Вот и круги по воде идут от этого.

А теперь я чуть-чуть остыну

Конечно, веб-версии продуктов нужны. И мобильные нужны. И даже у того софта, который я пишу, такие версии имеются. А в десктопной версии, соответственно, и авторизация, и вызовы REST API и даже всякие расшаривания и лайки, будь они неладны. Это всё, однако, не отменяет вышесказанного, поскольку дополнение фунционала рюшечками — это хорошо, а позиционирование рюшечек самих по себе как функционала — совсем наоборот.

Данный материал является вольным переводом статьи:
Danny Markov Creating Your First Desktop App With HTML, JS and Electron

Материал вычитывал: Михаил Синяков

Веб-приложения становятся все более мощными с каждым годом, но остается еще место для классических приложений, обладающих полным доступом к оборудованию компьютера. Сегодня вы можете создать десктопное приложения при помощи хорошо знакомых вам HTML, JS и Node.js, упаковать его в исполняемый файл и пользоваться им на Windows, OS X и Linux.

Существуют два самых популярных проекта с открытым исходным кодом, позволяющих сделать это. Это NW.js и Electron, последний мы и будем рассматривать сегодня. Мы собираемся переписать версию, которую делали на NW.js, так что вы сможете еще и сравнить их между собой.

Программы, которые создаются при помощи Electron это просто веб сайты, которые открываются во встроенном браузере Chromium. В дополнение к стандартным API HTML5 эти сайты могут использовать полный набор модулей Node.js и специальных модулей Electron, которые позволяют получить доступ к операционной системе.

В этом уроке мы создадим простое приложение, которое получает последние статьи с сайта Tutorialzine через RSS и отображает их в виде карусели. Все исходники, вы можете скачать архивом по ссылке.
Распакуйте его содержимое в любую директорию на вашем компьютере.

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

Мы рассмотрим наиболее интересные файлы и то как они работают минутой позже, а пока давайте заглянем под капот.

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

Поскольку приложение Electron это просто Node.js приложение, вам нужно установить npm. Сделать это довольно просто.

Откройте терминал и выполните в директории проекта следующую команду:

npm install

Это создаст папку node_modules, содержащую все необходимые зависимости для приложения. Затем, введите в терминале следующее:

npm start

Приложение должно открыться в новом окне, обратите внимание, что оно имеет только верхнее меню и больше ничего.

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

Как это сделано

Сейчас мы поговорим о наиболее важных файлах, которые используются в любом приложении, написанном при помощи Electron. Давайте начнем с файла package.json, который содержит различную информацию о проекте. Например, версию, список npm зависимостей и другую не менее важную информацию.

package.json


{
  "name": "electron-app",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "dependencies": {
    "pretty-bytes": "^2.0.1"
  },
  "devDependencies": {
    "electron-prebuilt": "^0.35.2"
  },
  "scripts": {
    "start": "electron main.js"
  },
  "author": "",
  "license": "ISC"
}

Если вы уже работали с Node.js, то у вас уже имеется представление как это все работает. Важно отметить команду npm start которая запускает приложение. Когда мы вызываем эту команду в консоли, то просим electron запустить файл main.js. Этот файл содержит маленький скрипт, который открывает окно приложения, определяет некоторые параметры и обработчики событий.

main.js


var app = require('app');  // Модуль управления приложением.
var BrowserWindow = require('browser-window');  // Модуль для создания окна браузера.

// Сохраняем глобальную ссылку на объект Window, если этого не сделать
// окно закроется автоматически как только сработает сборщик мусора JS.
var mainWindow = null;

// Выйти, после того как все окна будут закрыты.
app.on('window-all-closed', function() {
    // В OS X это характерно для приложений и их меню,
    // чтобы оставаться активными, пока пользователь явно не завершит работу 
    // при помощи Cmd + Q
    if (process.platform != 'darwin') {
        app.quit();
    }
});

// Этот метод будет вызван когда Electron закончил
// инициализацию и готов к созданию окна браузера.
app.on('ready', function() {
    // Создаем окно браузера.
    mainWindow = new BrowserWindow({width: 900, height: 600});

    // и загружаем index.html в приложение.
    mainWindow.loadURL('file://' + __dirname + '/index.html');

    // Генерируется когда окно закрыто.
    mainWindow.on('closed', function() {
        // Сброс объекта окна, обычно нужен когда вы храните окна
        // в массиве, это нужно если в вашем приложении множество окон, 
        // в таком случае вы должны удалить соответствующий элемент.
        mainWindow = null;
    });
});

Давайте взглянем на то, что мы делаем в методе ready. Сначала мы определяем окно браузера и устанавливаем его первоначальный размер. Затем мы загружаем в него файл index.html, который работает точно так же, как если бы мы открыли его в браузере.

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

index.html


<!DOCTYPE html>
<html>
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Tutorialzine Electron Experiment</title>

<link rel="stylesheet" href="./css/jquery.flipster.min.css">
<link rel="stylesheet" href="./css/styles.css">

</head>
<body>

<div class="flipster">
<ul>
</ul>
</div>

<p class="stats"></p>

<!-- Правильный способ подключить jQuery в Electron приложении -->
<script>window.$ = window.jQuery = require('./js/jquery.min.js');</script>
<script src="./js/jquery.flipster.min.js"></script>
<script src="./js/script.js"></script>
</body>
</html>

Здесь у нас html-код, ссылки на необходимые стили, js библиотеки и скрипты. Заметили что jQuery подключен странным способом? См. этот issue, чтобы узнать почему подключение происходит именно так.

Наконец, собственно сам JavaScript код нашего приложения. В нем мы подключаемся к RSS ленте, получаем последние статьи и показываем их. Если мы попытаемся провернуть такую операцию в окружении браузера, то ничего не получится. Канал находится на другом домене и получение данных с него запрещено. Однако в Electron такого ограничения нет, мы можем получить необходимую информацию при помощи AJAX-запроса.


$(function(){

    // Отображаем информацию о компьютере используя node-модуль os.

    var os = require('os');
    var prettyBytes = require('pretty-bytes');

    $('.stats').append('Number of cpu cores: <span>' + os.cpus().length + '</span>');
    $('.stats').append('Free memory: <span>' + prettyBytes(os.freemem())+ '</span>');

    // Библиотека UI компонентов Electron. Понадобится нам позже.

    var shell = require('shell');


    // Получаем последние записи с Tutorialzine.

    var ul = $('.flipster ul');

    // Политики безопасности в Electron не применяются, поэтому
    // мы можем отправлять ajax-запросы на другие сайты. Обратимся к Tutorialzine

    $.get('http://feeds.feedburner.com/Tutorialzine', function(response){

        var rss = $(response);

        // Найдем все статьи в RSS потоке:

        rss.find('item').each(function(){
            var item = $(this);

            var content = item.find('encoded').html().split('</a></div>')[0]+'</a></div>';
            var urlRegex = /(http|ftp|https)://[w-_]+(.[w-_]+)+([w-.,@?^=%&:/~+#]*[w-@?^=%&/~+#])?/g;

            // Получим первое изображение из статьи.
            var imageSource = content.match(urlRegex)[1];


            // Создадим li для каждой статьи и добавим в неупорядоченный список.

            var li = $('<li><img /><a target="_blank"></a></li>');

            li.find('a')
                .attr('href', item.find('link').text())
                .text(item.find("title").text());

            li.find('img').attr('src', imageSource);

            li.appendTo(ul);

        });

        // Инициализируем плагин flipster.

        $('.flipster').flipster({
            style: 'carousel'
        });

        // При клике на статью откроем страницу в браузере по умолчанию.
        // В противном случае откроем ее в окне Electron.

        $('.flipster').on('click', 'a', function (e) {

            e.preventDefault();

            // Откроем URL в браузере по умолчанию.

            shell.openExternal(e.target.href);

        });

    });

});

Есть одна классная вещь, в приведенном выше коде, она заключается в том, что в одном файле можно одновременно использовать:

  • JavaScript библиотеки — jQuery и jQuery Flipster для создания карусели.
  • Собственный модули Electron — оболочку, которая предоставляет API для desktop-задач. В нашем случае открытие url в браузере по умолчанию.
  • Node.js модули — Модуль OS для доступа к информации о системе, Pretty Bytes для форматирования.

С их помощью наше приложение готово к работе!

Упаковка и дистрибуция.

Есть еще одна важная вещь, которую нужно сделать чтобы ваше приложение попало к конечному пользователю. Вы должны упаковать его в исполняемый файл, который будут запускать двойным щелчком. Необходимо будет собрать отдельный дистрибутив для каждой из систем: Windows, OS X, Linux. В этом нам поможет Electron Packager.

Вы должны принять во внимание тот факт, что в упакованный файл попадут все ваши ресурсы, все зависимости node.js, а так же уменьшенная копия браузера webkit. В конечном итоге получится файл порядка 50mb. Это довольно много и не практично для простых приложений, как, например, наше, но этот вопрос становится не актуальным, когда речь идет о больших и сложных приложениях.

Заключение

Единственное серьезное отличие от NW.js состоит в том, что в NW.js точкой входа выступает HTML-файл, в то время как в Electron эту роль выполняет JavaScript файл. C Electron вы получаете больше контроля. Вы легко можете построить мульти оконное приложение и организовать обмен данными между ними.

Вот что можно еще почитать по теме:

  • Electron’s Quick Start Guide
  • Electron’s Documentation
  • Apps Built with Electron

Мы работаем с Flutter уже больше трёх лет. За это время создали более 25 проектов с помощью этого фреймворка. Недавно мы решили опробовать возможности новой версии Flutter Web и создали концепт банка. Что из этого вышло, читайте в статье.

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

Вы в блоге Surf, более 12 лет мы разрабатываем мобильные и веб-решения для бизнеса

О чём в этой статье:

  • Коротко о Flutter
  • PWA как альтернативный канал доставки приложения пользователям
  • Как разработать приложения для мобайла, веба и десктопа на 60% дешевле
  • Как Flutter упрощает адаптацию дизайна для разных платформ

Вводная часть: что такое Flutter

Flutter — это кроссплатформенный фреймворк. Кроссплатформа подразумевает, что созданный с её помощью продукт может, используя единую кодовую базу, работать с разными операционными системами. На первом этапе для Flutter это стали iOS и Android. А потом инструмент расширил число ОС, но об этом чуть ниже.

Мир узнал о Flutter в 2018 году. К 2021-му технология потеснила конкурентов и вырвалась в безусловные лидеры среди кроссплатформ.

Google активно развивает Flutter, и в начале 2022 года выпустил третью версию фреймворка. Мы делали подробный обзор обновлений в этой статье. Новая версия стала прорывом во фронтенд-разработке, причём именно в ней создателям удалось добиться нужного уровня стабильности. Это значит, что теперь на одной кодовой базе можно создать приложение и «раскатать» его на разных цифровых платформах: мобильной, веб и десктоп.

Наши Flutter-разработчики провели эксперимент и создали концепт онлайн-банка, в котором опробовали Flutter web на практике. О том, какие потребности бизнеса закрывает новая версия фреймворка, мы и расскажем в статье.

Вызов №1: найти альтернативный канал доставки мобильного приложения пользователям

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

  • банки теряют клиентов и уменьшают число продаж;
  • банки переплачивают за содержание команды iOS-разработки, которая в текущих условиях не приносит прибыли. Если привести приблизительные цифры, только инхаус iOS-команда, в которой обычно около 50 разработчиков, обходится банку примерно в 188 000 000₽ в год.

Как показывает наша практика, выход есть. И ниже мы о нём расскажем.

Решение

Один из вариантов решения — создать прогрессивное веб-приложение. Мы подробно рассказали об его особенностях, плюсах и минусах в статье. PWA (Progressive Web App) — это технология, которая преобразует сайт в приложение, полностью идентичное нативному. Им можно пользоваться как в браузере, так и со смартфона.

Плюсы и минусы PWA

PWA устанавливается через браузер, в котором открыто веб-приложение с поддержкой Progressive Web Applications. На обеих ОС механизм одинаковый, отличие только в том, что iOS работает с Safari, а Android — с Google Chrome.

Установка PWA на iPhone

Flutter оптимален для PWA по нескольким причинам:

  • Гибкость. Часть функций можно переиспользовать и адаптировать под свой коммерческий запрос.
  • Тестирование. Код проверяется один раз и под платформы одновременно.
  • Адаптивность. Проект корректно работает на всех браузерах и ОС.
  • Экономия. Часов на разработку нужно меньше, а значит, сократится штат разработчиков и бюджет.
  • Простая коммуникация. На проекте работает одна команда разработчиков.

Мы убедились на собственном опыте, что Flutter хорошо справляется со сборкой на web. Остаётся только проверить финальную сборку и исправить ошибки, которые могут возникнуть при переходе на веб-платформу.

PWA на Flutter позволяет быстро раскатать веб-версию, не переписывая всё с нуля. В нем будут доступны те же функции, что и в привычном мобильном приложении.

  • Раздел «Карты». Тут отображаются активные карты с разбивкой на общий и разделённый баланс.
  • «Счета и «Цели». Заложена возможность «копилки» и открытых счетов: кредитного и дебетового.
  • «История». Тут показаны ежемесячные траты с разбивкой по категориям.
  • «Автоплатёж», перевод по номеру телефона и оплата по QR-коду — эти функции тоже легко интегрировать.
Все привычные функции банковского приложения можно реализовать в PWA на Flutter

Как PWA поможет банку заменить нативное мобильное приложение: разбираем по шагам

  • Разрабатываем на Flutter PWA приложение. Через 2 месяца запускаем версию для тестирования внутри банка, а через 4 — версию с базовой функциональностью, доступную клиентам.
  • В течение 10 месяцев дорабатываем Flutter-приложение, чтобы в нем была вся реализованная в нативной версии функциональность. После этого можно заменить существующее приложение для Android на кроссплатформенное на Flutter. Пользователи iOS могут применять PWA.
  • Параллельно разрабатываем версию приложения для настольного веба на той же кодовой базе, что и мобильные, так как Flutter это позволяет.
  • Передаем проект в инхаус.

Что в итоге?

  • Проблема с доступностью приложения пользователям iOS будет решена через 4 месяца.
  • Для PWA, Android, настольного веб и iOS используется единая кодовая база: это позволяет синхронизировать релизы и снизить затраты на коммуникацию.

Вызов №2: разработать приложения для мобайла, веба и десктопа на 60% дешевле

Для работы над нативным мобильным приложением банк набирает в штат две команды разработчиков (Android и iOS). Давайте немного посчитаем: в среднем, инхаус-команда крупного банка состоит 50 iOS, 50 Android и 30 frontend-разработчиков. При средней зарплате в 250 000 рублей, налогах и IT-аккредитации, себестоимость команды достигает примерно 487 500 000 в год. Только команда iOS-разработчиков обойдётся собственникам бизнеса в 187 500 000 ежегодно. А если компании нужно реализовать приложение для разных платформ — iOS, Android, web, Windows, MacOS — бюджет и сроки проекта возрастут ещё больше.

Решение

Разработка на Flutter позволяет на едином коде и силами одной команды реализовать проект сразу для всех нужных бизнесу цифровых каналов: PWA, iOS, Android и десктоп. Стоимость работы команды в год составит около 150 000 000 вместо 487 500 000 рублей.

Использование Flutter сокращает стоимость поддержки и развития приложений в 3 раза

Новые мультиплатформенные возможности Flutter пригодятся не только банкам. С их помощью можно быстро реализовать проекты с возможностями таск-менеджера, календаря, аналоги notion для создания заметок и текстовых документов. На Flutter можно создавать стриминговые платформы и профессиональные приложения со сложной функциональностью, например, музыкальные редакторы.

Создатели Flutter абстрагировались от платформенных механизмов создания UI, что позволило сделать его кроссплатформенным.

Владимир Макеев, директор Surf

Как это работает:

  • в мобильной и десктопной версиях: Flutter реализовал собственный фреймворк для вёрстки, который собирается в аппаратной части устройства с помощью специального движка рендеринга.
  • в веб-версии: скачивается движок рендеринга, весом 2.8Мб. Отрисовка происходит так же, как и в мобайле. Движок запускается через WASM — это бинарный формат, который запускается в браузере и участвует в потоковой передаче, декодировании и компиляции. По такому же принципу работает Figma. Он позволяет избежать сложностей при работе с HTML, CSS, несовместимостью браузеров и ОС. Для компиляции веб-приложения используется dart2js — он переводит программу, написанную на языке Dart в язык JavaScript. Подробнее об этом можно прочитать здесь.

В результате получается веб-приложение, у которого под капотом такой же графический движок, что используется для отрисовки Flutter-приложения на Android и iOS. А поскольку методы самого рендеринга идентичны, то и отличий не будет. Пользователь на своей стороне не заметит никаких изменений.

Ещё одна сильная сторона Flutter-приложения — адаптивность. Такое приложение может подстроиться под любое пользовательское окружение и работать на разных ОС и браузерах. Это возможно благодаря тому, что для фреймворка реализован целый набор компиляторов. При компиляции, веб-приложение будет на 100% идентично операционным системам Android и iOS.

Flutter-приложение в браузере и на смартфоне

UI приложения адаптируется под разные устройства: например, при выходе из полноэкранной версии браузера в нижней части экрана появляется таббар, как в привычных мобильных интерфейсах. При этом скорость работы в веб-версии очень высокая.

Вызов №3: UI нужно адаптировать и дорабатывать под каждую платформу отдельно

При традиционной нативной и веб-разработке перед бизнесом встает вопрос не только создания приложения, но и адаптации его дизайна под разные платформы. Это требует ресурсов и дизайнеров, и разработчиков и удлиняет time-to-market проекта.

Решение

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

Мы намеренно создали обезличенный прототип в лаконичном минималистичном стиле, чтобы показать, насколько легко можно создать дизайн в фирменном стиле банка. В систему можно загрузить кастомные шрифты — движок Flutter будет их корректно отображать.

В веб-приложении на Flutter можно с лёгкостью узнать, на какой платформе запущен браузер, в котором исполняется приложение у пользователя прямо сейчас. А также реализовать различный UI, в зависимости от потребности дизайна или запроса от бизнеса.

Нестандартные проблемы требуют нестандартных решений. Мы уже не впервые находим это решение в использовании Flutter для создания больших проектов для наших клиентов. На наш взгляд, именно Flutter наиболее совершенно реализовал концепцию мультиплатформенности, которая не просто оптимизирует процесс разработки и сокращает затраты, но и помогает преодолевать кажущиеся непреодолимыми форс-мажоры.

Евгений Сатуров, руководитель Flutter-отдела в Surf

Реализовав концепт, мы убедились, что Flutter стал настоящим мультиплатформенным решением, которое позволяет:

  • быстро создать PWA, если нужно заменить мобильное приложение;
  • сделать приложение, которое идентично ведёт себя на всех цифровых платформах — iOS, Android, web, Windows, MacOS;
  • легко реализовать кастомный дизайн, который будет адаптироваться к разным платформам, и нужную функциональность;
  • благодаря единой кодовой базе ускорить time to market проекта на 60% и снизить стоимость поддержки и развития приложений в 3 раза.

Объясняю матчасть

Раньше интернет был меньше и тупой школоты в нем было меньше. Потом возникла культура игрофонов, и каждый детсадовец освоил интернетно-фейсбуковый жаргон. Часть серьезных дядек тоже сместилась в сторону игрофонов, но в качестве основного рабочего девайса используют десктопы/ноутбуки. В итоге из платформ нынче мы имеем: iOS, MacOS, Android, Windows, Linux. Почти как в 70-80-е годы, где каждый новый крупный производитель считал своим долгом создать свою собственную архитектуру. Естественно, работать на андроиде-айос крайне неудобно, потому дядьки «моё время дорого стоит» продолжают использовать как минимум ноут в качестве рабочего инструмента.

По ОС-ям чуток разъяснил, теперь переходим к собственно софту. Платформ, которые могли бы работать на всех ОС-ях, можно пересчитать по пальцам. А платформ, которые позволяли бы обеспечить близкий к родному интерфейс на всех ОС-ях, и вовсе не существует. В принципе, можно писать на вебе, электроне, Qt. Сразу приходим к тому, что у Qt поддержка мака и игрофонов так себе. Веб и электрон по сути очень близки, но отдельные модификации морд под игрофон и под десктоп все равно придется делать, потому что ну слишком уж они разные. Для оборачивания веба под каждую платформу ещё есть Cordova, но там тоже ни разу не гладко всё.

Допустим, мы приходим к самому-присамому универсальному варику под все платформы — веб с опциональной кородовой. Но вот беда: жаваскриптное приложение, которое оперирует DOM, да еще и реализуя кучу низкоуровневых часто вызываемых обработок на том же жаваскрипте (React, Vue, Angular) — тормозит, жрет много оперативы, а в итоге жрет аккому школьника, и по сути плохо подходит для игрофона. Как и плохо оно подходит для работы с большими объемами данных на десктопе. Результат — разработка React Native и Flutter.

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

byko3y ★★★★

(05.03.20 00:17:07 MSK)

  • Ссылка

Понравилась статья? Поделить с друзьями:
  • Десантник или дисантник как правильно пишется
  • Десант или дисант как пишется правильно
  • Дерьмище как пишется
  • Дерущие морозы как пишется
  • Дерусь как правильно пишется