Русский
Русский
English
Статистика
Реклама

Ui

Recovery mode Баг или фича определителя номера от Тинькова, или как не нужно наполнять базу

02.06.2021 12:04:43 | Автор: admin

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

Все крайне просто: в приложении Тинькофф Банка для iOS есть удобная функция определителя номера. Программа хорошая, работает успешно. По какой базе происходит поиск - не знаю, но по факту определяет номера чаще и точнее, чем многие конкуренты.

Вот что определитель думает про общий номер Альфы...Вот что определитель думает про общий номер Альфы...

У этого замечательного определителя есть, я бы сказал, милая фича: по моим наблюдениям, все и каждый из мне звонивших номеров других банков определялись с пометкой "Мошенники могут использовать номер". При этом, как я говорил, база у программы широкая, и у тех же Альфы или Сбера помечаются не только публично известные номера вроде 8-800-200-00-00, но и все номера офисов и служб.

Комметарии поддержки Тинькофф Банка про такую пометку, в общем-то, разумны: мол, злоумышленники порой звонят, подставляя обратные номера, равные номеру того или иного банка, так что любой звонок с обратным номером того же Альфа-банка потенциально может быть мошенническим. Почему-то другой вариант развития событий, а именно, что это может быть и звонок от настоящего сотрудника Альфы, при этом как-то не учитывается, но, в принципе, технически это понятно: программа-определитель номера не может точно определить, кто звонит ("настоящий" сотрудник банка или мошенник), и, на случай, помечает звонок как "возможно мошеннический". Лучше перебдеть, чем недобдеть, правда же?

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

...а вот так определитель пишет про номер Тинькофф Банка...а вот так определитель пишет про номер Тинькофф Банка

Приведенное выше, конечно, на пост не тянет, но пишу я этот текст ровно ради следующих нескольких слов: на номер самого банка Тинькофф программа не выдает приписку "Мошенники могут использовать номер", а вежливо выводит лишь: "Тинькофф Банк. При подозрении - перезвоните".

P.S. Баг наполнения БД ли это, либо и правда, некая фича, но, признаться, получилось у ребят наприятно отвлекающе. Тем более, что iPhone не позволяет выводить много текста при описании номера, и при звонке в/из Альфы я вижу не "Альфа-Банк. Будьте осторожны", а только начало приведенной выше фразы, т.е. "Мошенники могут и...". Интересно, UI на такой надписи вообще тестировали?

Подробнее..

Figma делаем дизайн компонентов, пригодный для экспорта в код

15.02.2021 08:08:38 | Автор: admin

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

Начнём с простого

Нарисуем лист вью с иконкой, и сгенерируем вёрстку.

Так выглядит примерная структура нашего элемента списка - слева иконка, и далее текст.

Такую структуру будет иметь наш компонент - вертикальный набор элементов, который мы придумали выше.

Так, со структурой разобрались, поняли что нам примерно нужно сделать, теперь приступаем непосредственно к дизайну. Для этого мы возьмём один элемент, и сделаем его на основе компонентов Фигмы и применим к нему Auto layout. Сначала объединим текст и иконку, добавим отступы, сделаем выравнивание по высоте в середине, и по левому краю. Получится так

Далее нам нужно создать два элемента, расположить их друг под другом по высоте, и объединить их Auto layout. В целом всё кажется готовым, но на самом деле, если вы поменяете длинну текста, то элементы не будут гибко подстраиваться друг под друга.

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

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

Запускаем генератор кода.

Открывается плагин с генерацией кода. где мы можем выбрать необходимую нам технологию. Я буду использовать Tailwind 2. Далее выберем нужный нам элемент дизайна, и плагин выдаст нам готовую вёрстку.

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

Так, всё работает, кроме иконок, которые нам нужно копировать как SVG и вставить в наш код. Делается это вот так

Заменяем наши иконки в вёрстке (я вставил прям в разметку, но вы можете и так как вам удобно - по url на пример.).

Получаем результат, который идентичен нашему в Фигме.

Подробнее про Auto layout тут.

Результат тут.

Сложнее. Рисуем карточку товара.

Нашей целью будет сделать так, чтобы при генерации кода, наша карточка была выполнена на основе display: flex; - CSS модели, для построения гибких контейнеров.

Я нарисовал макет, как в прошлом примере, сделал дизайн, распределил блоки, и при помощи Auto layout выровнял всё так, как мне нужно. Сгенерировал код, подправил некоторые нюансы с картинками и иконками, в результате получил готовую карточку товара. Подробнее про Flexbox тут.

Моя сгенерированная разметка доступна по ссылке ниже. Вы можете посмотреть и попробовать сами.https://play.tailwindcss.com/2VhmQJIJDl

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

Подробнее..

Сердце, не познавшее боли разочарования, не знало и радости полёта

18.02.2021 14:23:54 | Автор: admin

The Host (Stephenie Meyer)


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



Думаю, в недалеком будущем No-Code/Low-Code продукты сделают свое дело, и UI/UX и фронтендеры уже не будут знать, что это такое, когда глаз дергается синхронно с кнопкой в веб-версии макета. А что сейчас? Чтобы дизайнеру и фронту было проще ужиться друг с другом, а их совместная работа упростилась, мы придумали Quarkly.



Сооснователи проекта Саша и Артем не понаслышке знакомы со всеми болячками, которые возникают во время коммуникации дизайнера и фронта. Александр фулстек-разработчик, Артем UI/UX. В данном случае, что называется, карты сошлись.


В Quarkly мы стремимся к пресловутому Low-Code и даже No-Code при сборке сайтов и веб-приложений, и когда мы запустим в релиз каталог-маркетплейс готовых компонентов, это станет в целом очень даже возможным.


По состоянию на февраль 2021 года маркетплейса у нас пока нет, но есть весь базовый набор инструментов как для дизайнера, привыкшего делать макеты/прототипы в популярных дизайн-инструментах (Figma, Framer), так и для разработчика.


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




За счет чего Quarkly поможет снять боль


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


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


  1. Дизайнер и фронт работают, как они привыкли каждый это делать дизайнер создает макет в Figma, далее фронт переносит всё в код;
  2. Дизайнер и фронт совместно создают проект в Quarkly.

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



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


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


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


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



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



Наш канал на YouTube, где можно найти полезные мануалы: смотреть


Наш чат в телеграме: https://t.me/quarklyapp

Подробнее..

Радио, погода, время всегда под рукой или история одного решения (железо, софт, интерфейс)

01.04.2021 18:23:17 | Автор: admin

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

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

  • убедиться, что именно твой телефон сейчас сопряжён с колонками;

  • если не твой, необходимо разорвать связь колонок со всеми остальными устройствами и выполнить подключение;

  • запустить какое-то приложение;

  • выбрать какую-то музыку;

  • а если тебе ещё на это устройство позвонят, а если телефон жены быстрее твоего подключится?!...

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

Железо, софт

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

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

Радио, погода, время всегда под рукойРадио, погода, время всегда под рукой

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

Web-приложение, UI/UX

Требуемый функционал мне был хорошо понятен и применив знания по UI/UX я реализовал интерфейс. В интерфейсе отображён необходимый минимум информации: время, погода и несколько радиоканалов. Всё лаконично, просто и надёжно. В любой момент времени можно подойти, ткнуть в кнопку радио, и оно заиграет.

Казалось бы, Цель достигнута, но мне хотелось большего, а именно:

  • сайт должен запускаться не как окно браузера, а как обычное приложение из иконки с рабочего стола;

  • должно быть плавное переключение каналов (кроссфэйд);

  • автоматическое переподключение при потере аудио-потока;

  • отображение активности радио при фактическом проигрывании звука (визуализация);

  • и некоторые другие плюшки.

Далее было несколько познавательных циклов разработки...

Цикл 1.

Web App Manifest дал возможность реализации сайта, как веб приложения со своим значком на рабочем столе устройства.

Web Audio Api позволил делать визуализацию и анализ аудио, микширование нескольких источников и многие другие вещи. При помощи этой технологии я решил основные вопросы связанные с аудио. Каналы переключались плавно (правда для этого пришлось проигрывать сразу несколько потоков в бэкграунде, чтобы иметь возможность в любой момент времени переключиться на любой источник без ожидания предзагрузки). Визуализация показывала наличие реального воспроизведения и к ней же я подключил дополнительный анализ "молчания" потока.

После тестирования выявлено:

  • планшет "засыпает" при отсутствии активности пользователя;

  • после нескольких суток работы приложения в круглосуточном режиме появлялись проблемы с аудио потоком;

  • из-за одновременного воспроизведения нескольких источников в бэкграунде необоснованно использовалось достаточно много трафика;

  • оказалось, что на Mobile Safari есть очень серьёзные ограничения на работу с многопотоковым аудио и на работу с громкостью. На начало 21го года эти ограничения звучат примерно так: допускается одновременное воспроизведение только одного потока, программная регулировка громкости недоступна.

Цикл 2.

Wake Lock Api решил вопрос засыпания устройства.

Пришлось потратить какое-то время на дебаг для поиска причин нестабильной работы через большой промежуток времени, однако всё привело к тому, что проблема кроется не в клиентской программной части, а где-то на уровне браузера-системы. Т.к. сайт заточён в свою песочницу, я сделал определённые выводы и провёл определённый ресёрч в сторону настроек Web Audio Api, работы с каналами, битрейтом, буферами и пр. Скорректировал настройки.

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

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

Добавил адаптивную вёрстку под любое устройство.

После тестирования выявлено:

  • Проблемы воспроизведения потоков через Web Audio Api не пропали. Они стали проявляться значительно реже. Через несколько суток работы браузер мог начать значительно "подвисать" или выключался.

Цикл 3.

Т.к. я так и не смог найти решения для полноценной работы всего функционала построенного на Web Audio Api на mSafari, сделал выдох, перенёс весь функционал работы с аудио через Web Audio Api в отдельную настройку и вернул первоначальную ветвь с простой работой через html5 audio.

Провёл оптимизацию клиентской части. Как инструмент диагностики использовал Lighthouse (pagespeed от google). Если ставить перед собой цель 100 баллов, то можно узнать много полезного используя этот инструмент.

В итоге получилось рабочее веб-приложение, которое отправилось в бой.

После тестирования выявлено:

  • В обычном режиме работы с аудио, приложение работает более стабильно. Решением пользуется вся семья (простота и надёжность сделали своё дело).

Эволюция интерфейса

Эволюция интерфейсаЭволюция интерфейса

Выводы

  1. Идея и реализация были верны, чему я лично очень рад. Принцип "Чем проще - тем лучше" работает практически всегда!

  2. Web Audio Api - классная штука, но если её использовать, то необходимо проработать вопрос совместимости и определиться с режимом работы (короткая сессия, длинная сессия (несколько суток), бесконечная сессия). По моим выводам, на начало 21го года, я бы не стал делать на его основе web-приложений для длинных или бесконечных сессий.

  3. Так же, для длинных и бесконечных сессий связку андроид - хром - веб-приложение необходимо тестировать на целевых устройствах под актуальными версиями ПО. Я использовал в работе устаревший 8ой андроид, возможно мои проблемы со стабильностью работы уже давно решены, но я работал с тем что было на руках.

Что дальше

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

Вишенка

Многие web разработчики не знают как выглядят 100 баллов в Lighthouse. Вот так... :)

Lighthouse. All 100.Lighthouse. All 100.

Сайт проекта

Буду рад, если мой опыт окажется полезным и вам. Всем удач!

Подробнее..

EasyUI действительно easy?

31.05.2021 12:16:05 | Автор: admin

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

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

Раньше создание такого интерфейса вызывало серьёзную головную боль у программистов, но теперь для избавления от неё выпущено большое количество различных фреймворков и библиотек. Казалось бы ура, проблема решена! Однако, теперь перед нами встаёт другой вопрос: какой препарат выбрать пенталгин или панадол?

Вопрос нелёгкий, и решать, в итоге, вам. Я же расскажу о своём лекарстве: библиотеке EasyIU, предназначенной для создания полноценных одностраничных веб-приложений (SPA) и основанной на jQuery, Angular, Vue и React.

Моё знакомство с EasyUI началось около двух лет назад, когда наша команда приступила к разработке софта для системы управления питанием и его интеллектуального распределения между потребителями. Управляющее устройство имело на борту Linux и кроме распределения питания должно было обмениваться данными с различными периферийными устройствами, уметь контролировать их, а также принимать показания от большого количества (до нескольких сотен) датчиков. Правила управления могли изменяться, позволяя пользователю настроить работу всего комплекса для выполнения необходимых задач.

Настройка и мониторинг устройства могли вестись через различные протоколы: ssh, snmp, redfish, BACnet, но основным способом общения с ним был http, то есть всем комплексом можно было управлять через обыкновенный веб-браузер. Это широко используемое решение, и оно не сулило никаких проблем. Однако, дьявол всё же основательно порылся в деталях.

Веб-страница должна была отражать состав комплекса, его внутренние и внешние компоненты и их содержимое в виде дерева разнотипных объектов. Показания датчиков должны были выводиться в виде таблицы (как в Excel'е, безмятежно улыбался заказчик), причём часть данных (показания, состояния датчиков и т.п.) требовалось непрерывно обновлять, а часть могла изменяться пользователем непосредственно в таблице. Для настройки комплекса было необходимо использовать многоуровневое меню и большое количество модальных диалоговых окон.

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

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

Итак, что же такое такое EasyUI?

Как я уже упоминал выше, EasyIU представляет собой набор компонентов пользовательского интерфейса, основанных на jQuery, Angular, Vue и React. Мы использовали библиотеку, базирующуюся на jQuery.

С самого начала мне понравилась возможность создания макета приложения без программирования на javascript. EasyUI для jQuery имеет встроенный парсер, который расширяет HTML-разметку и ассоциирует её с библиотечным кодом. Для этого в классе HTML-элемента достаточно указать наименование компонента, который необходимо применить.

Например, эта разметка создаст на странице приложения пять зон: шапку и подвал высотой 100 пикселей, и разделённую на три колонки среднюю часть. Левая и правая колонки имеют ширину 100 пикселей, а центральная часть имеет серый фон и занимает всё оставшееся место. Кроме этого, высоту шапки и подвала, ширину левой и правой колонок можно изменять мышью во время работы приложения. Для этого EasyUI создаст на странице приложения специальные разделители.

<body class="easyui-layout">  <div data-options="region:'north',title:'North Title',split:true"       style="height:100px;"></div>  <div data-options="region:'south',title:'South Title',split:true"       style="height:100px;"></div>  <div data-options="region:'east',title:'East',split:true"       style="width:100px;"></div>  <div data-options="region:'west',title:'West',split:true"       style="width:100px;"></div>  <div data-options="region:'center',title:'center title'"       style="padding:5px;background:#eee;"></div></body>

Конечно, EasyUI позволяет сделать то же самое при помощи javascript:

$('body').layout({fit: true}).layout('add', {  region: 'north', title: 'North Title', split: true, height: 100}).layout('add', {  region: 'south', title: 'South Title', split: true, height: 100}).layout('add', {  region: 'east', title: 'East Title', split: true, width: 100}).layout('add', {  region: 'west', title: 'West Title', split: true, width: 100}).layout('add', {  region: 'center', title: 'сenter Title', split: true, widht:100,  style: {padding: 5, background: '#eee'}});

В результате EasyUI создаст вот такую страницу:

Пока всё easy, не так ли? Мы можем создать предварительный макет без написания javascript кода, что очень удобно на начальных этапах проектирования приложения.

А что ещё она умеет?

Для беглой оценки возможностей EasyUI, достаточно посмотреть на её конструктор тем:

Здесь показаны не все возможности EasyUI, но для первого впечатления вполне достаточно и этого: разметка (layout), панели (panel), многоуровневое меню (menu, menubutton), вкладки (tab), аккордеоны (accordion), календарь (calendar), таблица (datagrid), набор конфигурационных параметров (propertygrid), список (datalist), дерево (tree), диалоги (dialog), формы (form) и их элементы (validatebox, textbox, passwordbox, maskedbox, combobox, tagbox, numberbox, datetimebox, spinner, slider, filebox, checkbox, radiobutton) и этот перечень далеко не полон. При более глубоком погружении в возможности библиотеки выясняется, что эти компоненты можно расширять и создавать на их основе новые. На сайте проекта есть раздел Extention, на котором представлены некоторые расширения, например, всем известная лента (Ribbon):

Для демонстрации всех компонентов, реализованных EasyUI, на странице проекта есть специальный раздел с демонстрацией их работы.

И снова о дизайне

EasyUI поддерживает изменение стиля оформления пользователем из набора готовых тем прямо во время выполнения приложения. На сайте проекта предлагается собственная коллекция стилей. К сожалению, решение из такого набора не всегда может удовлетворить заказчика. Это, скорее, набор стилей для начала работы, который впоследствии потребует корректировки. Для этого на сайте проекта имеется конструктор тем. Разумеется, всегда можно настроить тему приложения в соответствующей таблице стилей, которые находятся в каталоге themes проекта. Для нашего проекта поддержка возможности изменения темы приложения не требовалась, однако, таблицы стилей были существенно переработаны, чтобы UI соответствовал корпоративному стилю заказчика.

Создание диалога при помощи EasyUI

В качестве примера использования библиотеки в проекте я хочу привести код (под катом) для создания диалога настроек HTTP. Параметром функции открытия диалога является иконка, назначенная тому пункту меню, из которого вызывается диалог.

Код для создания диалога настроек HTTP
(function($) {   $.fn.httpConfDlg = function(icon) {     var title = _("HTTP Configuration"), me;     var succ = _(       "HTTP properties have been changed. " +       "You need to re-connect your browser " +       "according to the new properties."     );     var errcode = "System returned error code %1."     var errset = _(       "Can't set HTTP configuration. " + errcode     );     var errget = _(       "Can't get HTTP configuration. " + errcode     );     var allowed = $.SMR_PRIVILEGE.CHECK(       $.SMR_PRIVILEGE.CHANGE_NETWORK_CONFIGURATION     );     var buttons = [];     if (allowed) {       buttons.push({         okButton: true,         handler: function() {           var ho = $(this.parentElement).api({             fn: $.WAPI.FN_SET_HTTP_PROPERTIES,             param: {               httpPort: parseInt($('#httpPort').textbox('getValue')),               httpsPort: parseInt($('#httpsPort').textbox('getValue')),               forceHttps: $.HpiBool($('#forceHttp')[0].checked)             },             before: function() {               $('body').css('cursor', 'wait');             },             done: function() {               $('body').css('cursor', 'default');               me.dialog('close');             },             error: function(err) {               if (err.RC == $.WAPI.RC_BAD_RESPONSE) {                 $.messager.alert(                   title,                   $.fstr(errset, err.IC),                   'error'                 );                 return false;               } else if (err.RC == 1003) {                 ho.api('drop');                 $.messager.alert(title, succ, 'info', function() {                   $('#sinfo').session('logout');                 });                 return false;               }               return true;             }           });         }       });     }     buttons.push({cancelButton: true});     return this.each(function() {       document.body.appendChild(this);       me = $(this).append(         '<div id="httpSetting" style="padding: 10px 30px">' +         $.fitem('httpPort', _("HTTP port")) +         $.fitem('httpsPort', _("HTTPS port")) +         $.fcheck('forceHttp', _("Force HTTPS for Web Access")) +         '</div>'       );       $('#httpPort').textbox({         type: 'text', width: 60, disabled: !allowed       });       $('#httpsPort').textbox({         type: 'text', width: 60, disabled: !allowed       });       if (!allowed) $('#forceHttp').attr('disabled', 'disabled');         me.mdialog({           title: title,           iconCls: icon,           width: 320,           height: 180,           modal: true,           buttons: buttons,           onOpen: function() {             var ho = $(this).api({               fn: $.WAPI.FN_GET_HTTP_PROPERTIES,               receive: function(res) {                 $('#httpPort').textbox('setValue', res.httpPort);                 $('#httpsPort').textbox('setValue', res.httpsPort);                 if (res.forceHttps == 1) {                   $('#forceHttp').attr('checked', 'checked')                 } else {                   $('#forceHttp').removeAttr('checked')}               },               error: function(err) {                 if (err.RC == $.WAPI.RC_BAD_RESPONSE) {                   $.messager.alert(                     _("HTTP"),                     $.fstr(                       errget,                       err.IC                     ),                   'error'                 );                 me.dialog('close');                 return false;               }               me.dialog('close');               return true;             }           });         }       });     });   }; })(jQuery); 

Поскольку компоненты EasyUI реализованы в виде коллекций jQuery (в нашем случае это $('div').httpConfDlg(http_icon)), инициализация диалога производится через метод this.each().

В начале активируются кнопки диалога: OK и Cancel. Это можно сделать непосредственно при инициализации диалога, но кнопка OK создается только для обеспечения привилегированного доступа. Таким образом, для пользователя, не имеющего достаточных прав для изменения параметров HTTP протокола, диалог будет отображать только кнопку Cancel (Конечно, EasyUI допускает установку и снятие запрета нажатия на кнопки во время инициализации диалога, а также во время его работы кнопка при запрете использования изменяет стиль и не реагирует на нажатия. Однако, для сохранения общего стиля, неиспользуемые кнопки в диалогах нами не отображаются). Обработчик кнопки Cancel по умолчанию закрывает окно диалога без дополнительных действий. У кнопки OK есть обработчик, который выполняет AJAX-запрос. В качестве параметра запросу передаётся JSON структура, содержащая номер функции для бэкенда, набор параметров для самой функции и обработчики результатов выполнения (callback).

Затем родительский элемент, переданный через параметр this, заполняется контентом: двумя полями для указания номеров портов и одного флажка, устанавливающего принудительное использование защищённого протокола. Далее поля активируются как EasyUI textbox компоненты. Если пользователь не имеет привилегий для их изменения, текстовые поля и флажок будут недоступны для изменения.

И наконец, производится активация самого диалога: устанавливаются его размеры, заголовок, иконка, модальный режим, активируются кнопки диалога и их обработчики (указанные явно или по умолчанию). После открытия диалога запускается функция, выполняющая AJAX запрос для получения текущих параметров HTTP. Если при выполнении запроса происходит ошибка, генерируется сообщение об ошибке и диалог закрывается. Если запрос выполнен успешно, текстовые поля и флажок устанавливаются в соответствии с полученными результатами запроса.

При нажатии на кнопку OK вызывается функция-обработчик, которая считывает текущие значения текстовых полей и состояние флажка, формирует соответствующий AJAX-запрос и выполняет его. При любом результате выполнения этого запроса производится закрытие окна диалога (параметр done). В случае возникновения ошибки, в зависимости от её типа, выдаётся сообщение об ошибке. В нашем случае, даже при успешном выполнении запроса происходит ошибка, поскольку изменение настроек протокола приводит к разрыву соединения. Для его восстановления производится закрытие сеанса и вызов процедуры новой регистрации.

Несколько, пояснений к коду.

  • Вызов $.fitem('httpPort', _("HTTP port")) создаёт набор связанных HTML элементов, реализующих типовое для нашего приложения поле ввода с идентификатором httpPort и меткой (label) HTTP port. Функция _() обеспечивает использование языка, указанного пользователем в настройках. Последующий вызов компонента EasyUI $('#httpPort').textbox({type: 'text', width: 60, disabled: !allowed}); регистрирует поле ввода как EasyUI textbox. Вызов $('#httpPort').textbox('setValue', res.httpPort); устанавливает значение для текстового поля в соответствие результату AJAX запроса. И наконец, parseInt($('#httpPort').textbox('getValue')) в обработчике OK-кнопки возвращает текущее значение текстового поля.

  • Компонент mdialog() является нашим собственным расширением от базового компонента EasyUI dialog() для автоматического закрытия диалога при наступлении определённых событий, а также для создания типовых кнопок с обработкой нажатия по умолчанию. В данном случае это кнопка Cancel, которая создаётся короткой инструкцией buttons.push({cancelButton: true});

  • Функция $.messager вызывает окно предупреждения, которое также является компонентом EasyUI, производным от компонента Dialog.

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

EasyUI диалог для настройки HTTPEasyUI диалог для настройки HTTP

Ложка дёгтя

Как видно из приведенного выше примера, для создания диалога требуется довольно много кода. Большое количество диалогов и их компонентов (например, выпадающих списков с одинаковым содержимым) ощутимо раздуло наш проект однообразными велосипедными конструкциями.

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

И всё-таки, почему EasyUI?

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

Для нас критически важно было отображать большой и постоянно обновляемый набор данных как в виде иерархической структуры, так и в виде таблицы. И EasyUI предоставила нам такую возможность. Для этого в составе библиотеки есть специальные компоненты: tree и datagrid.

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

Компонент datagrid отображает данные в табличном формате и предлагает богатую поддержку для выбора, сортировки и группировки данных. Слияние ячеек, многоколоночные заголовки, замороженные столбцы и нижние колонтитулы вот лишь некоторые из его особенностей. Кроме этого, для datagrid есть специальные расширения: datagrid-scrollview, которое манипулирует полным набором данных, сохраняя в DOM-е лишь отображаемую их часть (а это существенно повышает скорость работы приложения), и datagrid-filter позволяющее накладывать фильтры на используемый набор данных.

И tree, и datagrid поддерживают асинхронную загрузку и редактирование данных непосредственно по месту положения элемента, имеют динамически настраиваемое оформление и свободно подключаемые иконки для элементов компонента, а также возможность отображения данных в виде чекбоксов.

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

В завершение хочу ещё раз сказать, что библиотека EasyUI обеспечивает все потребности пользовательского интерфейса, оставаясь при этом лёгкой в использовании. Я думаю, эта библиотека может быть интересна разработчику веб-приложений, ориентированных на управление различными устройствами. В этой нише её использование, на мой взгляд, может быть предпочтительнее Bootstrap-а. По крайней мере, это хорошая альтернатива панадолу.

Подробнее..

Создаем Booking приложение с Webix UI

12.04.2021 16:18:43 | Автор: admin
Webix UIWebix UI

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

Одно из лучших решений предлагает нам команда Webix. Их библиотеку UI компонентов мы и рассмотрим в этой статье.

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

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

Немного о Webix и его возможностях

Webix UI это JavaScript библиотека, которая позволяет создавать отзывчивый дизайн и обеспечивает высокую производительность приложения. Диапазон возможностей представлен UI компонентами различной сложности, от обычной кнопки, до таких комплексных решений, как Report Manager, с помощью которого можно создавать и экспортировать отчеты данных. Помимо самих компонентов, библиотека предоставляет много дополнительных возможностей для работы с ними. Например, механизм обработки событий, методы работы с данными, взаимодействие с сервером, темы для стилизации и многое другое. Обо всем этом и не только можно узнать в документации. А сейчас самое время перейти к практической части.

Источник UI магии

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

Итак, для того, чтобы начать использовать библиотеку, нужно сперва получить необходимые файлы (они же источники webix-магии). Для этого переходим на страницу загрузки, вводим необходимые данные и получаем ссылку на скачивание заветного zip-файла. Скачиваем и распаковываем его. Внутри находятся файлы license.txt, readme.txt и whatsnew.txt, которые могут заинтересовать тех, кто любит глубоко погрузиться в изучение вопроса. Кроме этого, в папке samples можно посмотреть примеры того, какие полезные вещи можно сотворить при помощи Webix UI.

Больше всего нас интересует содержимое папки codebase, а именно, два сакральных файла: webix.js и webix.css. Для того, чтобы Webix-магия начала действовать, нужно включить их в index.html файл будущего проекта:

<!DOCTYPE html><html>    <head>      <title>Webix Booking</title>      <meta charset="utf-8">      <link rel="stylesheet" type="text/css" href="codebase/webix.css">      <script type="text/javascript" src="codebase/webix.js"></script>    </head>    <body><script type="text/javascript">...</script>    </body></html>

Внутри кода мы добавим теги <script>...</script>, где и будем собирать наше приложение.

Впрочем, архив с библиотекой скачивать не обязательно. Необходимые файлы доступны также через CDN. Подключить их можно следующим образом:

<link rel="stylesheet" type="text/css" href="http://personeltest.ru/away/cdn.webix.com/edge/webix.css"><script type="text/javascript" src="http://personeltest.ru/away/cdn.webix.com/edge/webix.js"></script>

Инициализация

Теперь давайте перейдем непосредственно к работе с Webix.

Вся Webix-магия происходит внутри конструктора webix.ui(). Нам нужно убедиться в том, что код начнет выполняться после полной загрузки HTML страницы. Для этого обернем его в webix.ready(function(){}). Выглядит это следующим образом:

webix.ready(function(){webix.ui({    /*код приложения*/});});

После создания index.html файла и подключения необходимых инструментов, самое время перейти к непосредственной разработке нашего Booking приложения. Начнем с интерфейса.

Создаем приложение Booking

Интерфейс нашего приложение будет состоять из следующих частей:

  • Тулбар

  • Форма поиска рейсов

  • Таблица рейсов

  • Форма заказа.

Каждую из частей мы будем разрабатывать и хранить в отдельных файлах, а соберем их в файле index.html. Предлагаю детально ознакомиться с тем, как это реализуется на практике. А начнем мы с построения лейаутов, которые определяют способ расположения элементов на странице.

Лейаут

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

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

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

webix.ui({    rows: [        { template:"Row One" },        { template:"Row Two" }    ]});

Теперь лейаут будет выглядеть следующим образом:

В этом примере с помощью выражения template:"Row One" мы создали простой контейнер, в который можно поместить любой HTML-контент.

В верхний ряд этого лейаута мы поместим Тулбар. В нижнем будут находиться 2 сменяемых модуля:

  • Модуль поиска рейсов

  • Модуль заказа рейсов.

Модуль поиска рейсов нужно разделить на 2 столбца. В левом мы разместим форму поиска, а в правом таблицу данных. Для этого воспользуемся свойством cols:

webix.ui({  rows: [          { template:"Toolbar", height:50},          {              cols:[                { template:"Search Form" },                { template:"Data Table" }              ]          }  ]});

Теперь лейаут будет иметь следующий вид:

Комбинируя таким образом вложенные строки и столбцы, можно добиться необходимого результата. По умолчанию контейнеры заполняют все доступное пространство. Создавая два контейнера, мы получим 2 одинаковых прямоугольных области. Для того, чтобы задать нужные размеры элементов, можно использовать знакомые всем по CSS свойства width и height.

Теперь перейдем к модулю заказа рейсов и также разделим его на 2 столбца. В левом мы разместим форму заказа, а правый столбец оставим пустым. А почему же пустым, спросите вы? Ответ простой: если этого не сделать, форма растянется на весь экран, а это не очень эстетично. Пустой компонент, он же spacer, помогает выравнивать компоненты в интерфейсе. После этого мы можем задать необходимый размер нашей формы, а лишнее пространство будет заполнено этим отсутствующим компонентом, если можно так выразиться.

Здесь мы снова воспользуемся уже знакомым атрибутом cols. Стоит напомнить, что компоненты внутри cols и rows по умолчанию разделены тонкой серой линией. Такой разделитель между формой и спейсером нам ни к чему. Webix позволяет управлять им с помощью свойства type:

webix.ui({  rows: [    { template:"Toolbar", height:50 },    {      type:"clean", //убираем линию-разделитель      cols:[        { template:"Order Form" },        {}      ]    }  ]});

Результат будет следующим:

Мы создали отдельные лейауты для модулей поиска и заказа рейсов. Теперь нужно сделать их сменяемыми. Как вы уже догадались, Webix предусматривает и такую возможность. Реализуется она с помощью multiview компонента.

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

А сейчас наш код будет выглядеть следующим образом:

webix.ui({  rows:[    { template:"Toolbar", height:50 },    {      cells:[        {          id:"flight_search",          cols:[            {template:"Search Form"},            {template:"Data Table"},          ]        },        {          id:"flight_booking",          type:"clean",          cols:[            {template:"Order Form"},            {},          ]        }      ]    }  ]});

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

Тулбар

Тулбар важная часть любого приложения. Зачастую там располагаются инструменты управления и прочие ништяки. В нашем случае тулбар будет иметь динамически сменяемый лейбл.

В файле toolbar.js создаем компонент с помощью следующей строки:

const toolbar = { view:"toolbar" };

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

Как мы видим, тип создаваемого компонента определяется значением свойства view. В нашем случае это toolbar.Давайте стилизуем компонент и добавим лейбл с названием:

{  view:"toolbar"  css:"webix_dark", //стилизация  cols:[    {      id:"label", //указываем id для обращения      view:"label",      label:"Flight Search", //название    }  ]}

Выше мы упоминали, что вставлять компоненты друг в друга необходимо при помощи свойств rows и cols. Поэтому компонент label мы включаем в массив свойства cols компонента toolbar.

С помощью свойства id будет происходит обращение к элементу. Он должен быть уникальным. В нашем случае это нужно для динамической смены лейбла, но к этому мы еще вернемся.

Вот такими нехитрыми маневрами мы создали тулбар нашего приложения.Чтобы использовать компонент в лейауте, нужно сначала подключить файл toolbar.js в index.html и включить переменную в массив свойства rows вместо {template:Toolbar}:

webix.ui({rows: [    toolbar,    {      cells:[        ...      ]    }]});

На странице браузера мы увидим лейаут с интерфейсом тулбара:

Выглядит уже неплохо. Теперь настало время перейти к разработке модуля поиска рейсов. Он состоит из 2 частей:

  • Форма поиска рейсов

  • Таблица рейсов.

Давайте рассмотрим их детально.

Форма поиска рейсов

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

Классическую форму можно создать при помощи элемента <form>, а внутри определить необходимые контролы. При этом нужно оперировать большим количеством тегов и атрибутов. Для обработки же данных и вовсе требуется чуть ли не докторская степень.

Webix предлагает сделать это гораздо проще. Давайте с этим разбираться.

Создадим нашу форму в файле search_form.js с помощью компонента form:

const search_form = { view:form };

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

{  view:form,  ...  elements:[     { /*элемент формы*/ },    { /*элемент формы*/ },    ...  ]  ...}

Как вы видите, определять элементы формы необходимо внутри массива elements. Напомню, что каждый компонент библиотеки описывается с помощью json объекта. Давайте же узнаем, что нам могут предложить разработчики Webix для работы с формами.

Форма Webix имеет множество преимуществ. Она позволяет устанавливать и считывать значения всех входящих в нее полей сразу, а не по одному. Для этого нужно указать свойство name в конфигурации каждого поля. Также вы можете связывать ее с другими компонентами или сохранять данные непосредственно на сервер. Подробнее о работе с формами можно узнать здесь.

А сейчас нужно определиться с элементами, которые мы будем реализовывать. Чтобы выбрать пункты отправления и назначения, необходимо установить селекторы выбора городов. Также мы будем искать рейсы по дате отправления и возвращения. Контрол даты возвращения нужно спрятать и отображать его при необходимости. Реализуем это с помощью радиокнопок One Way и Return. При поиске нужно учитывать количество необходимых билетов. Для этого установим специальный счетчик. Ну и конечно, как же без кнопок управления формой Reset и Search. В итоге наша форма будет иметь следующий вид:

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

Радиокнопки

В самом начале формы нужно определить радиокнопки, с помощью которых мы будем отображать и прятать контрол выбора даты возвращения. Делается это с помощью элемента radio:

{  view:"radio",  label:"Trip",  name:"trip_type",  value:1,  options:[    { id:1, value:"One-Way" },    { id:2, value:"Return" }  ]}

Значения радиокнопок задаем через свойство options. Помимо этого, указываем название компонента через свойство label, а имя, по которому будем получать значение, через name. По умолчанию мы будем искать полеты только в одну сторону, поэтому устанавливаем для value значение именно этой опции.

Селекторы выбора городов

Чтобы указать пункты отправления и назначения, нам понадобятся специальные селекторы. Создадим их с помощью элемента combo:

{    view:"combo",    id:"from",    clear:"replace",    ...    placeholder:"Select departure point",    options:cities_data}

Иногда пользователю необходимо подсказать, что нужно делать с тем или иным контролом. И тут на помощь нам приходит свойство placeholder. Мы указываем нужные действия, а приложение отобразит их в поле необходимого элемента. Когда пользователь ввел или выбрал нужные данные, но потом вдруг передумал, а такое случается довольно часто, мы предоставим ему возможность очистить поле одним кликом. Для этого нужно задать свойство clear в значении replace. Теперь, когда поле будет заполнено, в правой его части появится иконка, при клике по которой введенные ранее данные исчезнут (поле будет очищено).

При клике по селектору должен появиться выпадающий список опций с названиями городов. Давайте это реализуем. Данные для списка опций можно задавать через свойство options . Есть несколько способов загрузить данные в компоненты Webix. Можно задать их в виде массива напрямую, или же хранить в отдельном файле и просто указать необходимый URL, а компонент сам загрузит их.

Но дело в том, что эти же данные нам нужны для нескольких компонентов, а загружать их для каждого по отдельности будет несколько затратно. Webix решает эту проблему с помощью такой сущности, как DataCollection. Внутри этого компонента необходимо всего лишь указать URL, по которому будут загружаться данные. Информация загрузится один раз и будет доступна для многоразового использования. В нашем случае объект с данными хранится в файле ./data/cities.json. Давайте создадим коллекцию и для удобства сохраним ее в переменную:

const cities_data = new webix.DataCollection({  url:"./data/cities.json"});

Теперь данные доступны для использования в наших селекторах и не только.

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

Селекторы выбора дат

Расписание - одна из важнейших составляющих современной жизни. При поиске рейсов обязательно нужно это учитывать. Давайте предоставим пользователю возможность указывать желаемое время вылета при поиске необходимого рейса. Сделаем мы это с помощью такого элемента, как datepicker:

{  view:"datepicker",  name:"departure_date",  ...  format:"%d %M %Y"}

При клике на селектор появится компактный календарь, в котором можно выбрать необходимую дату. Более того, у нас есть возможность задать необходимый формат, в котором выбранная дата будет отображаться в поле селектора. Реализуется это через свойство format.

Зададим такой же селектор для выбора даты обратного рейса, но изначально спрячем его при помощи свойства hidden. Именно этот селекор мы будем отображать при выборе радиокнопки Return, но об этом позже.

Счетчик количества билетов

Представим себе ситуацию, что наш потенциальный пользователь летит в долгожданный отпуск не один, а со всей семьей или компанией друзей. В таком случае ему понадобится не один билет, а некое энное количество. Давайте позволим ему устанавливать необходимое число билетов. Для этого предусмотрен специальный элемент counter. Использовать его очень удобно. С правой и левой стороны находятся иконки со знаками плюс и минус, с помощью которых и происходит увеличение или уменьшение значения. Нужно также установить минимальное значение через свойство min, чтобы пользователь не смог установить меньше 1 билета (согласитесь, это выглядело бы абсурдно). Код элемента будет выглядеть следующим образом:

{ view:"counter", name:"tickets", label:"Tickets", min:1 }

Кнопки поиска и сброса формы

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

Для этого мы предусмотрим соответствующие кнопки Search и Reset. Определяем их с помощью элемента button. Название кнопок указываем через value, а стилизацию добавляем через свойство css. Здесь нужно уточнить, что библиотека предусматривает встроенную стилизацию кнопок. Мы будем использовать класс "webix_primary" для стилизации кнопки Search. Подробнее о стилизации кнопок можно узнать здесь.

Для удобства мы разместим наши кнопки в виде столбцов:

{  cols:[    { view:"button", value:"Reset" },    { view:"button", value:"Search", css:"webix_primary" }  ]}

Интерфейс формы поиска мы создали. Теперь давайте интегрируем его в наш лейаут - вы еще помните о нём? Для этого, как и в примере с тулбаром, нужно включить файл search_form.js в index.html. Интерфейс формы хранится в переменной search_form, которую мы и пропишем в лейауте:

[  toolbar,  {    cells:[      {        id:"flight_search",        cols:[          search_form,          { template:"Data Table" }        ]      }, ...    ]  },]

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

Таблица рейсов

Пользователь ввел данные, нажал кнопку Search и ожидает увидеть результат. А результат будет отображаться в специальной таблице, созданием которой мы сейчас и займемся.

Возможности Webix таблицы сложно переоценить. Ее функционал начинается с отображения, фильтрации и сортировки данных, а заканчивается экспортом в PDF или Excel. Можно написать целую книгу о ее возможностях, продать и заработать баснословные деньги, но в данный момент нас больше интересует то, как сконфигурировать таблицу именно под наши нужды.

В файле datatable.js создаем таблицу рейсов с помощью компонента datatable:

const flight_table = {   view:"datatable",  url:flights_data};

Таблицу нужно заполнить данными о расписании рейсов. В нашем случае они хранятся в файле ./data/flights_data.json. Одно из преимуществ работы с таблицами Webix заключается в том, что можно просто указать путь к файлу или скрипту, а компонент сам сделает запрос, загрузит данные и отобразит их в соответствии с настройками. Проще только вообще ничего не делать.

Путь к данным нужно указывать через свойство url. Для удобства мы сохраним путь в переменную flights_data и присвоим ее нашему свойству.

Данные мы загрузили, теперь нужно их красиво отобразить. Для ооочень ленивых прогеров, Webix предусмотрел такое свойство как autoConfig. Если задать для него значение true, то данные автоматически распределятся в столбцы в соответствии с имеющимися полями.

Но мы еще не настолько разленились, чтобы прибегать к таким хитростям. Тем более, настройка столбцов вручную сулит нам множество увлекательных и полезных возможностей. Давайте взглянем на них более пристально.

Конфигурация столбцов происходит в массиве свойства columns:

columns:[  { /*конфигурации столбца*/ },  { /*конфигурации столбца*/ },  ...]

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

У столбца должно быть название, или по-простому шапка. Шапку мы задаем при помощи свойства header.

Нередко бывают ситуации, когда данные приходят в одном виде, а отобразить их нужно в другом. Было бы нелепо предположить, что разработчики Webix не учли такой вероятности и не решили эту проблему заранее. Поэтому нам доступно свойство format.

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

Давайте рассмотрим пример с датами.

Дело в том, что их значения хранятся и загружаются в виде строк типа "2021-03-26". Как вам должно быть известно, оперировать строчными датами не самое приятное занятие (для некоторых даже болезненное). Исходя из этого, можно сразу при загрузке перевести их в JS Date объекты. С помощью свойства scheme мы можем переопределить все строки указанного поля (данные которого попадут в столбец с таким же id) в соответствующие объекты Date перед их загрузкой в таблицу:

scheme:{  $init:function(obj){    obj.date = webix.Date.strToDate("%Y-%m-%d")(obj.date);  }}

Теперь значения дат попадают в столбец Date в виде объектов, а не строк. Так будет гораздо удобнее фильтровать рейсы при поиске билетов. Но на этом все не заканчивается. Нужно настроить их отображение непосредственно в ячейках столбца. Для этого в конфигурации столбца мы прописываем уже упомянутое свойство format в значении webix.i18n.longDateFormatStr. Теперь полученный объект Date преобразуется в заданное свойством format значение и отображается как 26 March 2021. Вот такая вот магия.

Столбцы, которые отображают названия городов, также заслуживают особого внимания. Дело в том, что данные о пунктах отправления и назначения хранятся и приходят в виде чисел. Зачем такие сложности спросите вы? Ответ простой. Сами названия городов хранятся в отдельной серверной таблице как id - value, а в данных о рейсах вместо названий хранятся только id городов. Чтобы получить название города по его id, нам нужно воспользоваться свойством collection.

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

Так вот, свойство collection отправляет запрос в коллекцию данных cities_data и получает соответствующее название города по его id, который соответствует числовому значению ячейки столбца. Здесь и добавить-то больше нечего. Такие мелочи делают процесс разработки приятным развлечением.

Но развлечения мы оставим на вечер пятницы. Сейчас нужно представить, что потенциальный пользователь ввел данные в форму поиска, нажал кнопку Search и нашел необходимый рейс в таблице (пока нужно только представить, так как реализацией интерактива мы займемся во второй части). Какие дальнейшие действия? Безусловно, нужно как можно быстрее помочь ему забронировать рейс. Как это сделать?

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

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

А мы же продолжаем настраивать таблицу.

Наш пользователь нашел несколько рейсов (10, 100 или больше), которые соответствуют его запросам. Нужно помочь ему найти нужный рейс непосредственно в таблице. Давайте добавим строку поиска и разместим ее над таблицей, чтобы потенциальный пользователь нашел наконец-то свой рейс и с довольным лицом рассказывал своим друзьям и знакомым, какой крутой у нас сервис.

Для этого мы воспользуемся компонентом search:

const search_bar = { view:"search" };

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

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

[   toolbar,   {     cells:[       {         id:"flight_search",         cols:[           search_form,           {              rows:{               search_bar,               flight_table             }           }         ]       }, ...     ]   },]

Уже всё по-взрослому. Результат в браузере будет таким:

Форма заказа

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

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

const order_form = {  view:"form",  elements:[    ...  ]};

Теперь давайте опишем нужные нам элементы.

Поля ввода

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

Давайте воспользуемся элементом text и его преимуществами:

elements:[  { view:"text", name:"first_name", , invalidMessage:"First Name can not be empty" },  { view:"text", name:"last_name", , invalidMessage:"Last Name can not be empty" },  { view:"text", name:"email", , invalidMessage:"Incorrect email address" },  ...]

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

Webix предусматривает такое развитие событий и предоставляет нам готовое решение в виде свойства rules и набора условий для проверки введенных данных. Можно, конечно, и самому написать эти правила, создать несколько функций, прочитать пару книг по регулярным выражениям, сходить на курсы по их применению и вернуться обратно к встроенным правилам, потому что они реально удобны. Все, что нужно сделать, это присвоить правила соответствующим именам (свойствам name) полей формы в массиве rules:

{  elements:[  ],  rules:{    first_name:webix.rules.isNotEmpty, //поле не должно быть пустым    last_name:webix.rules.isNotEmpty,     email:webix.rules.isEmail //значение должно быть в формате email  }}

Если значение поля соответствует установленному правилу, форма разрешит дальнейшие действия. В противном случае, поля подсветятся красным. Но пользователю красное поле ни о чем конкретном не говорит. Он поймет, что сделал что то неправильное, но не поймет, что именно мы от него хотим.

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

{ ..., invalidMessage:"First Name can not be empty", ... }

Закрадывается мысль, что у Webix есть готовые решения на все случаи жизни. Но нужно помнить, что это всего лишь UI библиотека, а настоящая валидация по-хорошему должна происходить на сервере.

Счетчик

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

Для этого используем уже знакомый нам элемент counter. По сути, он такой же, как и в предыдущей форме. Его первоначальное значение будет зависеть от значения счетчика формы поиска и количества свободных мест на выбранном рейсе. С его помощью пользователь сможет изменять количество необходимых билетов, не возвращаясь назад к поиску:

{ view:"counter", id:"tickets_counter", name:"tickets", label:"Tickets", min:1 }

Чекбоксы

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

Для этого мы создадим 2 чекбокса Checked-in Baggage и Food и будем их использовать для того, чтобы повышать цену за билет, если пользователь выбрал эти дополнительные плюшки. Чекбокс реализуется с помощью элемента checkbox:

[  { view:"checkbox", name:"baggage", label:"Checked-in Baggage", checkValue:15 },  { view:"checkbox", name:"food", label:"Food", checkValue:10 }]

Через свойство checkValue мы задаем значение отмеченного чекбокса. В нашем случае за дополнительный багаж и питание пользователю придется доплатить 15 и 10 долларов соответственно.

Радиокнопки

Теперь давайте перейдем к удобствам полета. За это у нас отвечают радиокнопки Economy и Business. Реализуются они с помощью уже знакомого нам элемента radio:

{   view:"radio", name:"class", label:"Class",  options:[    { id:1, value:"Economy" },    { id:2, value:"Business" }  ]}

Мы установим класс Economy по умолчанию, а при переключении на Business стандартная цена будет удваиваться. А что вы хотите, за комфорт нужно платить.

Лейбл

Хороший сервис информативный сервис. Именно поэтому нам нужно отображать актуальную информацию о стоимости заказа. Давайте настроим отображение итоговой цены. Реализуется это с помощью уже знакомого нам компонента label:

{ view:"label", id:"order_price" }

Изначальную цену мы устанавливаем при клике по кнопке Book в таблице рейсов. Она учитывает стоимость билета и их количество. По мере добавления плюшек или изменения количество билетов цена будет пересчитываться автоматически.

Кнопки Go Back и Make Order

Наш пользователь наконец-то определился с заказом и готов его сделать или передумал и хочет вернуться назад к поиску. Давайте реализуем такую возможность. Для этого мы создадим соответствующие кнопки Go Back и Make Order с помощью знакомого нам элемента button:

{   cols:[    { view:"button", value:"Go Back" },    { view:"button", value:"Make Order" }  ] }

Мы создали интерфейс формы заказа рейсов. Давайте поместим его в наш лейаут и посмотрим, что получилось:

[  toolbar,  {    cells:[      {...},      {        id:"flight_booking",        type:"clean",        cols:[           order_form,            {}        ]  }]}]

Результат в браузере:

Мы описали все элементы интерфейса и создали лейаут. Теперь пришло время оживить наше приложения.

Заставляем приложение работать

В первой части мы описали лейаут и внешний вид компонентов интерфейса нашего приложения с помощью Webix UI. Но это только вершина айсберга. Давайте оживим наш проект и сделаем его интерактивным. Раньше мы только предполагали, что будет, если пользователь сделает то или иное. Сейчас пришло время осуществить это на практике, тем более Webix предоставляет нам огромный набор инструментов для реализации задуманного.

Форма поиска

Радиокнопки

Итак, в самом верху формы поиска у нас находятся радиокнопки One-Way, которая установлена по умолчанию, и Return, при клике по которой должен отображаться спрятанный селектор даты возвращения. Как нам это реализовать? А все очень просто, нужно установить специальный обработчик на событие переключения между радиокнопками. Для работы с событиями предусмотрены специальные свойства. Самым универсальным из них является свойство on, с помощью которого можно установить обработчик сразу на несколько событий. Выглядит это следующим образом:

{  view:"radio",  ...  on:{    onChange:function(newv){      if(newv == 2){        $$("return_date").show(); //отображаем селектор даты возвращения      }else{        $$("return_date").hide(); //прячем селектор даты возвращения      }    }  }}

Теперь при переключении между радиокнопками функция будет прятать и отображать селектор даты возвращения. Реализуется это с помощью специальных методов show() и hide(), названия которых говорят сами за себя. Универсальный метод $$(id) позволяет получить доступ к соответствующему компоненту, id которого передан в качестве аргумента.

Селекторы городов

Наш пользователь выбрал Берлин в качестве пункта отправления. После этого он переходит ко второму селектору и видит, что там тоже есть Берлин. Почему же так случилось? Дело в том, что данные о городах загружаются из одной коллекции данных. Но пользователя это не интересует, он не должен иметь возможность искать рейсы из Берлина в Берлин. Давайте это исправим.

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

...{  options:cities_data,  on:{    onShow:function(v){      optionsData("from","to");    }  }}

Снова на помощь приходит свойство on, которое позволяет устанавливать обработчик на необходимые события. В нашем случае это событие onShow. При клике на селектор приложение запустит функцию optionsData(), которая отфильтрует и отобразит список доступных опций компонента, для которого она вызвана. Она имеет следующий вид:

function optionsData(first_id, second_id){  const options = $$(first_id).getList(); //получаем объект значений списка  const exception = $$(second_id).getValue(); //получаем выбранное значение  options.filter(obj => obj.id != exception); //фильтруем список}

Здесь мы используем такие полезные методы Combo, как getList() и getValue(). Первый метод получает список опций одного селектора, а второй установленное значение другого. С помощью метода filter() выпадающего листа функция фильтрует список и исключает из него выбранный город (полученный методом getValue).

Теперь перейдем к кнопкам Reset и Search.

Кнопка Search

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

function lookForFlights(){const vals = $$("search_form").getValues(); //получаем объект со значениями полей формыconst table = $$("flight_table"); //получаем доступ к таблице данныхtable.filter(function(obj){ /*условия для фильтрации*/ });}

Одно из множества преимуществ формы Webix заключается в том, что можно получит значения всех полей сразу в одном объекте. Реализуется это с помощью метода getValues(), вызванного для формы. Полученные значения используются для определения условий фильтрации в методе filter(), вызванного для таблицы данных. Метод перебирает элементы данных таблицы, фильтрует их с помощью условий, полученных из формы, и перестраивает таблицу с учетом изменившихся данных. Можно только представить, сколько времени займет реализация подобного функционала на чистом JS.

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

{ view:"button", value:"Search", ... click: lookForFlights }

Кнопка Reset

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

Для начала нужно создать соответствующий обработчик:

function resetForm(){const form = $$("search_form"); form.clear(); //очищаем формуform.setValues({trip_type:1}); //устанавливаем значения радиокнопки по умолчанию$$("flight_table").filter(); //сбрасываем данные таблицы по умолчанию}

С помощью метода clear(), функция очищает все поля формы сразу. Но у нас есть поле с типом билета (прямой или обратный) значение которого нужно установить по умолчанию. Исправляем этот недочет с помощью метода setValues(). В качестве аргумента, мы передаем объект с нужным значением для этого поля.

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

{ view:"button", value:"Reset", ... click:resetForm }

С формой поиска мы разобрались. Давайте перейдем к таблице рейсов и строке поиска.

Строка поиска

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

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

function searchFlight(){  //получаем значение строки поискаconst value = $$("search").getValue().toLowerCase(); const table = $$("flight_table");  //формируем объект с совпадениямиconst res = table.find(function(obj){ /*условия поиска*/ }); table.clearCss("marker", true);  //убираем предыдущие стилиfor(let i = 0; i < res.length; i++){table.addCss(res[i].id, "marker", true); //вешаем  css класс marker}table.refresh(); //перерисовываем таблицу}

Функция получает значение строки поиска через метод getValue() и сравнивает его с данными таблицы. Для этого используется метод find(), вызванный для таблицы рейсов. Ряды таблицы, значения которых совпали с введенными данными, помечаются с помощью css класса marker (стили которого находятся в нашем css файле). После проделанных действий, нужно обязательно обновить представление с помощью метода refresh(), чтобы заданный css класс подсветил нужные ряды.

Теперь необходимо установить этот обработчик на событие onTimedKeyPress, который будет обрабатывать значения текстового поля при вводе. Делается это при помощи знакомого нам свойства on. Код будет выглядеть следующим образом:

{  view:"search",  id:"search",  ...  on:{    onTimedKeyPress:function(){ //срабатывает при наборе текста      searchFlight(); //анализируем данные таблицы и подсвечиваем совпадения    },    onChange:function(){ //срабатывает при нажатии на иконку очистить      if(!this.getValue()){        $$("flight_table").clearCss("marker"); //убираем подсветку      }    }  }}

Дополнительно, мы установим обработчик на событие onChange. Он будет срабатывать при клике по иконке очистить, которую мы установили с помощью свойства clear, и убирать подсвечивание с рядов.

Таблица рейсов

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

Как вы помните, напротив каждого рейса мы создали кнопку Book, с помощью которой пользователь может перейти к форме заказа. Давайте посмотрим, как организовать переход и какие тут есть дополнительные нюансы.

Давайте начнем со смены модулей.

При клике на кнопку Book приложение спрячет модуль поиска и отобразит модуль с формой заказа. Нужно напомнить, что при создании лейаута, мы использовали компонент multiview. Наши модули находятся в массиве свойства cells, и каждому присвоен соответствующий id:

cells:[  {    id:"flight_search", ...  },  {    id:"flight_booking", ...  }]

Смена multiview модулей реализуется с помощью вызова метода show() для модуля, который нужно отобразить. Доступ к модулю мы получаем через универсальный метод $$(id). В качестве аргумента передаем id нужного модуля:

$$("flight_booking").show(); //отображаем форму регистрации

Эту строку нам нужно включить в тело нашего обработчика. Давайте его создадим:

function bookFlight(id){//получаем количество искомых билетовconst required_tickets = $$("search_form").getValues().tickets;//получаем количество свободных местconst available_places = flight_table.getItem(id).places;//устанавливаем максимальное количество билетов $$("tickets_counter").define("max", available_places); //устанавливаем необходимые значения в поля формы регистрации$$("flight_booking_form").setValues({//количество билетов должно быть меньше или соответствовать свободным местамtickets:required_tickets <= available_places ? required_tickets : available_places,//устанавливаем значение цены билета в скрытый инпутprice:flight_table.getItem(id).price,//устанавливаем "Эконом" класс по умолчаниюclass:1});$$("flight_booking").show(); //отображаем форму регистрации...}

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

Здесь стоит обратить внимание на такой метод для работы с таблицей, как getItem(). С его помощью мы получаем объект значений ряда таблицы, в котором находится кнопка Book. Из этого объекта мы извлекаем цену билета и количество свободных мест. В качестве аргумента передаем id соответствующего ряда.

Как вы помните, при описании тулбара мы определили, что лейбл будет сменяемым. Сменяться он будет при переходе на другой модуль. Реализуется это с помощью методов define() и refresh().

Метод define() позволяет изменить любое свойство компонента, а метод refresh() обновляет его визуальное представление:

$$("label").define("label", "Flight Booking"); //меняем лейбл на тулбаре$$("label").refresh(); //обновляем новый лейбл

Теперь нужно установить обработчик на событие клика по кнопке Book. При создании, мы присвоили ей класс webix_button и сделали это не просто так. Webix предусматривает специальный хендлер onClick, предназначенный для кликов по элементам ячеек таблицы, которые отмечены тем или иным css классом:

{  columns:[  ],  onClick:{    "webix_button":function(e,id){      bookFlight(id);    }  }}

Теперь все работает и пользователь может переходить к форме заказа.

Форма заказа

После клика по кнопке Book пользователь переходит к форме заказа. Выше мы описали, как это реализуется. Теперь давайте разберемся, что происходит непосредственно в форме заказа. И начнем мы с системы подсчета стоимости.

Изначальную цену мы устанавливаем при клике по кнопке Book в таблице рейсов. Она учитывает стоимость билета и их количество. По мере добавления пользователем плюшек или изменения количества билетов цена будет пересчитываться автоматически. Чтобы это реализовать, нам необходимо прибегнуть к старому доброму свойству on, через которое реализуется подписка на события onChange (реагирует на изменение значений пользователем) и onValues (реагирует на установку значений при клике на кнопку Book):

on:{onChange:function(){orderPrice(this.getValues());},onValues:function(){orderPrice(this.getValues());}}

Функция имеет следующий вид:

function orderPrice(vals){//получаем количество билетовconst tickets = vals.tickets; //получаем цену с учетом класса и количества билетовconst price = vals.price*1*vals.class*tickets; //получаем стоимость дополнительного багажаconst baggage = vals.baggage * tickets;//получаем стоимость питания const food = vals.food * tickets; //формируем итоговую суммуconst total = price+baggage+food; //отображаем итоговую сумму$$("order_price").setValue(total+"$"); }

Функция-обработчик этих событий получает в качестве аргумента объект со значениями формы через метод getValues(), анализирует их и устанавливает итоговую стоимость внутри лейбла Price через метод setValue(). В отличие от метода setValues(), которому можно передать объект со значениями всех полей формы, setValue() устанавливает только одно значение элементу, для которого вызван.

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

Теперь пользователь решил сделать заказ, ввел данные и кликнул по кнопке Make Order. Для того, чтобы определить сценарий дальнейших действий, нужно создать соответствующий обработчик:

function makeOrder(){const order_form = $$("flight_booking_form"); //получаем доступ к формеif(order_form.validate()){ //запускаем валидацию формыwebix.alert({ //в случае успешной валидации выводим сообщение о заказеtitle: "Order",text: "We will send you an information to confirm your Order"}).then(function(){goBack(); //очищаем валидацию и возвращаемся к таблице рейсов});}}

Функция запускает валидацию формы при помощи метода validate(). Этот метод анализирует значения полей формы, для которых мы установили правила в свойстве rules. Если значения полей соответствуют правилам, валидация пройдет успешно и метод вернет true. В противном случае метод вернет false, поля с некорректными данными подсветятся красным, а внизу появятся сообщения об ошибках. Напомню, что эти сообщения мы определяем через свойство invalidMessage.

В случае успешной валидации функция выведет сообщение о готовности заказа. Здесь стоит сказать, что Webix имеет несколько методов для вывода сообщений. Мы будем использовать webix.alert(), для которого можно указать действия, которые выполнятся при нажатии на кнопку OK метода (в нашем случаи это функция goBack()):

webix.alert({  title: "Order is successfull",  text: "We will send you an information to confirm your Order"}).then(function(){  goBack();});

Функцию goBack() мы также устанавливаем в качестве обработчика на кнопку Go Back. Она очищает валидацию с помощью метода clearValidation(), изменяет лейбл тулбара, а также возвращает нас в модуль с таблицей рейсов и формой поиска:

function goBack(){const order_form = $$("flight_booking_form"); //получаем доступ к формеconst label = $$("label"); //получаем доступ к лейблу на тулбареorder_form.clearValidation(); //очищаем валидациюlabel.define("label", "Flight Search"); //изменяем значение лейблаlabel.refresh(); //обновляем лейбл$$("flight_search").show(); //отображаем лейаут с таблицей рейсов и формой поиска}

Заключение

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

В этой статье мы детально разобрали, как создать приложение с использованием Webix. Научились создавать лейауты и описывать UI компоненты с помощью json синтаксиса. Косвенно затронули некоторые методы, которые позволяют работать с данными. Как вы могли убедиться, компоненты и методы интуитивно понятны и просты в использовании. На самом деле, это только малая толика всего, что может предложить нам библиотека. Для более детального ознакомления можно перейти к документации, в которой есть подробное описание всех возможностей с наглядными примерами.

Подробнее..

Перевод Положение дел у Windows сколько разношерстных уровней UI в Windows 10?

08.02.2021 14:10:06 | Автор: admin

Все мы слышали байку: если в Windows 10 копнуть достаточно глубоко, можно найти элементы, относящиеся еще ко временам Windows 3.x. Но так ли это на самом деле? В этой статье мы узнаем, сколько уровней пользовательского интерфейса присутствует в Windows и когда они были впервые представлены.

Для этого эксперимента я выбрал последнюю сборку Windows 10 Insider Build (по состоянию на 6 февраля 2021 г.), Windows 10 21301.

Итак, с места в карьер!

Уровень первый: Fluent Design.

Начнем мы с новейшего и прекрасного Fluent Design. Анонсированный в 2017 году и представленный с обновлением Windows 10 1803 Fluent Design представляет собой серьезную переработку Modern Design Language 2 (MDL2), направленную на привнесение таких элементов, как свет, глубина, движение, материальность и масштаб. Также появился эффект подсвечивания и акриловый полупрозрачный фон.

На данный момент большинство встроенных UWP-приложений были обновлены с использованием элементов Fluent такие, как элементы вроде меню "Пуск", "Центра уведомлений" и экрана входа в систему.

Хотя Fluent Design получил высокие оценки, большинство энтузиастов сочли этот шаг слишком незначительным и запоздалым, поскольку новый стиль используется лишь в небольшой части системы.

Уровень 2: Metro.

Если мы немного углубимся в ОС, то увидим элементы, которые не обновлялись со времен Windows 8/8.1.

Некоторые из них явные недоработки, как всплывающее меню громкости, всплывающее меню USB, а также некоторые элементы на экране входа в систему.

Другими элементами Metro, хотя и не такими заметными, являются загрузочный экран (который скоро будет заменен на более новый) и WinRE.

Знаете ли вы, что впервые вращающиеся точки были представлены в Windows 8, билд 7989?

Так, ладно, перейдем к третьему уровню: к элементам Windows 8 Win32.

Как и Windows 10, Windows 8 также страдала от проблем с целостностью (лучше или хуже). Однако в Windows 8 были внесены существенные улучшения в основные пользовательские элементы такие, как проводник Windows и диспетчер задач. Хотя в последующих обновлениях Windows 10 они получат некоторые качественные улучшения, изменения будут минимальными.

Кроме того, важным изменением с выходом Windows 8 стала переработка диалоговых окон передачи файлов.

Некоторые из этих изменений начались в Windows 7, что подводит нас к четвертому уровню: элементам пользовательского интерфейса Windows 7

Windows 7, без сомнения, одна из самых любимых версий Windows всех времен, которую хвалят за большие улучшения по сравнению с Windows Vista. Она принесла много новых функций, которые, хотя и не были столь значительными как те, что предлагает Vista, сделали Windows 7 очень надежной ОС настоящим преемником Windows XP. Однако, одним из самых печально известных изменений, привнесенных Windows 7, является ленточный интерфейс, пришедшим из Office 2007. Paint и Wordpad были первыми приложениями, которые его получили.

Хотя в какой-то момент Microsoft решила отказаться от классического Paint в пользу нового Paint 3D (представленного в Windows 10 Creators Update), после негативной реакции они отменили свое решение.

Другие функции, которые были обновлены в Windows 7 и с тех пор остались прежними: Windows Media Player 12, подключение к удаленному рабочему столу и некоторые диалоговые окна с файлами.

Теперь перейдем к 5-му уровню пользовательского интерфейса: Windows Vista.

Релиз Windows Vista имел огромную важность, принесшим столь необходимую модернизацию платформы. Почти все основы ОС были так или иначе улучшены, от загрузчика до модели драйвера. Однако, как мы все уже знаем, Windows Vista станет одним из худших выпусков Windows за всю историю, с самого начала страдающего от проблем. Однако одной из немногих расхваленных функций был пользовательский интерфейс. В нем были переработаны некоторые основы, которые не обновлялись со времен Windows 95. Одним из главных способствующих факторов этого изменения было введение так называемых мастеров "Aero Wizards", пришедших на смену предыдущему стандарту мастеров, Wizard97.

Другие функции, которые были переработаны в Windows Vista, стали в основном все те же, что и в Windows 10: панель управления, программа поиска, факсы и сканирование Windows.

Кстати о Windows Vista: знали ли вы, что при определенных обстоятельствах Windows 10 возвращается к загрузочному экрану Vista? Это случается, когда ваша видеокарта не поддерживает режим видео, который используется на стандартном экране загрузки.

Теперь перейдем к 6-му уровню: Windows XP.

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

Уровень 7: Windows 2000.

Windows 2000 стала важной вехой в линейке операционных систем Microsoft NT. Это также была ступенька, которая ознаменовала начало перехода к новому унифицированному видению Windows. Однако Windows 2000 по-прежнему оставалась ОС, ориентированной на корпоративный сектор, а это значит, что она принесла много новых функций, разработанных для специалистов.

Консоль MMC стала одной из наиболее значительных дополнений, элементы которого с тех пор практически не изменились.

Еще одна функция, представленная по умолчанию в Windows 2000, установщик Windows, который по-прежнему имеет все тот же значок, когда впервые был представлен!

Еще один элемент пользовательского интерфейса, который не изменился (кроме фирменного стиля, конечно), это winver, дизайн которого был представлен в Windows 2000, билд 1946.

В то время, как Windows 2000 представила множество функций, предназначенных для опытных пользователей, Windows 95, вероятно, является самым знаковым релизом Windows на сегодняшний день. Он установил фундаментальные парадигмы, которые действуют и по сей день: меню "Пуск", контекстные меню, панель задач и корзину. Хотя эти функции, конечно, обновлялись в течение многих лет, некоторые из них остались почти такими же.

Теперь о восьмом уровне: элементы Windows 95/NT 4.0

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

Еще один поразительно похожий элемент это поле "Выполнить".

Еще одним распространенным элементом пользовательского интерфейса, прошедшим проверку временем, является экран свойств папки.

А есть и множество других элементов пользовательского интерфейса, которые не менялись со времен Windows 95. Это ли не пример дизайна, над которым время не властно? Судите сами.

Уровень 9: Windows 3.1 и DOS. В общем-то

Что ж, на самом деле это не "уровень пользовательского интерфейса", так как я не смог найти никаких элементов интерфейса, предшествующих Windows 95 (хотя у меня есть ощущение, что они, безусловно, есть). Однако в Windows 10 есть специфический файл moricons.dll, который содержит множество старых значков времен DOS. Сами поглядите:

Ну вот и все. Как вы, возможно, уже знаете, с выходом "Sun Valley" Microsoft планирует модернизировать пользовательский интерфейс Windows, целью которого является унификация дизайна ОС. Однако, как мы видим, Windows это гигантская операционная система. Увенчаются ли успехом их усилия по созданию единого пользовательского интерфейса? Время покажет.

Подробнее..

Нужно ли дизайнеру интерфейсов понимать вёрстку?

13.02.2021 10:18:35 | Автор: admin

Вы верно поняли, тут речь пойдёт именно о тех людях, которые делают дизайн интерфейсов. Я порой вижу вопросы на тему: Нужно ли понимать дизайнеру вёрстку? и Почему вы делите дизайнеров на Ui, UX, итд?. В этой статье я отвечу на эти вопросы. Маленькая затравка для продолжения - да, эти два вопроса отвечают друг на друга. О том, кто такие дизайнеры интерфейсов, и что они делают в рамках разработки приложений, мы разбирали в прошлой статье.

UPD: Расшифровка того, кто такой Ui/Ux дизайнер по ссылке выше.

Нужно ли уметь верстать?

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

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

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

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

Почему делят дизайнеров на Ui, Ux, продуктовых, рекламных и так далее ?

Если вы читали ответ на предыдущий вопрос, то вы примерно уже догадываетесь о том, почему дизайнеры интерфейсов, это не тоже самое, что дизайнеры печатной продукции, и другие История IT такова, что постоянно нужно развиваться и учиться новому, без этого есть большой шанс стать неактуальным. На заре зарождения веб интерфейсов дизайном занимались люди, которые сайты разрабатывали, была даже такая профессия Веб мастер. Сейчас, если делать так, как делали в начале нулевых - то дизайнер рискует остаться не у дел. Веб мастера переквалифицировались в фронтендеров, с их большим зоопарком фреймворков, технологий по типу Blazor, и и много чего еще Сейчас веб разработчику, для успешной карьеры, мало знать лишь ванильный JS и JQuery. Так и выводим, что дизайнер, который разрабатывает интерфейсы, знает помимо обязательной для всех видов дизайнеров базы - Теории дизайна, цвета, композиции еще и технические детали в виде Системного подхода, верстки, принципов работы клиент-серверных приложений, принципов работы запросов и ответов, структуры JSON объектов. Справедливо будет уточнить, что не очень нужно знать всё это на уровне профессиональной разработки, а хотя бы на уровне базовых принципов и механизмов работы. Этим собственно и делятся дизайнеры между собой - дополнительными, отраслевыми навыками.

Выводы

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

Обращайтесь только к квалифицированным специалистам.

Подробнее..

Перевод В Windows 10 21H2 появится новый параметр в меню питания, изоляция сторонних драйверов и процесс taskbar.dll

29.03.2021 12:16:08 | Автор: admin

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

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

Опция называется Перезапускать приложения после входа в систему. Впервые ее заметил пользователь Twitter @WithinRafael (Рафаэль Ривера), но по умолчанию она не включена в системе. При выборе этого параметра включается функция Перезапустить приложения в приложении Настройки.

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

Помимо новой опции питания, пользователь Twitter @thebookisclosed (Albacore) обнаружил, что Microsoft, похоже, вносит изменения в изоляцию сторонних драйверов в Windows 10 аналогично тому же подходу, который применяется в Windows 10X.

Согласно @thebookisclosed, Microsoft создает новую папку OEMDRIVERS внутри папки Windows, в которой будут храниться сторонние драйверы вместо того, чтобы хранить их в папке System32.

Кроме того, пользователь Twitter Albacore также заметил, что панель задач будет выделена в отдельный процесс. До сих пор панель задач была частью explorer.exe, но после сборки 21343 процесс был перемещен в taskbar.dll.

Сюрреалистично наблюдать, как панель задач перемещают по прошествии десятилетий. Проводник по-прежнему содержит старый код, но в будущем можно будет переключиться на динамически подключаемую библиотеку. По-прежнему есть некоторые странные ошибки, такие как наполовину сломанный интерфейс поля поиска, поясняет @thebookisclosed.


Активировать скрытые возможности на инсайдерских билда можно через ViveTool или mach2.

Подробнее..

Визуализация данных в интерфейсе

09.04.2021 20:23:06 | Автор: admin

Меня зовут Илона, я Senior Experience Designer в EPAM. Я проектирую сложные интерфейсы для зарубежных заказчиков, выступаю с докладами, менторю дизайнеров. В свободное время преподаю проектирование интерфейсов в магистратуре Университета ИТМО и ведуТелеграм-канал о UX-дизайне.

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


Множественные данные, которые, в принципе, можно считать Big Data, представляют из себя набор информации (цифры, процентные соотношения, статусы, параметры, текст и пр.), на основании которой пользователь может делать определённые выводы.

Любимые дизайнерамидашбордырезультат визуализации таких данных.

Dashboard by Ghulam RasoolDashboard by Ghulam Rasool

Но раскидать цветастые графики по экрануещё не значит хорошо визуализировать информацию.

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

В первую очередь вам понадобятся знания основ инфографики.

Инфографика одна из формкоммуникационного дизайна, которая помогает организовать и наглядно показать большие объёмы данных.

Инфографика зародилась в 18 веке с изобретением столбчатой, линейной и круговой диаграмм шотландским экономистом по имени Уильям Плейфей, которые он опубликовал в книгеCommercial and Political Atlas and Statistical Breviary.Его, по моему скромному мнению, гениальные наработки послужили базисом визуализации данных и до сих пор активно используются для отображения информации.

Первая столбчатая диаграмма за авторством Уильяма Плейфея, 1781Первая столбчатая диаграмма за авторством Уильяма Плейфея, 1781

Большое влияние на современную визуализацию данных оказал профессор Йельского университетаЭдвард Тафти, написавший несколько книг об отображении информации, в том числеThe Visual Display of Quantitative Information.Книга содержит множество примеров и я крайне рекоммендую её к прочтению, для расширения кругозора.

Книги Э. Тафти о данныхКниги Э. Тафти о данных

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

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

Косметическое украшение никогда не компенсирует ошибки отображения данных, зато может их исказить.
Э. Тафти

Иллюстрация из книги Э. ТафтиИллюстрация из книги Э. Тафти

Способы визуализации информации

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

  1. Точка индикатор положения данных. Может находиться на оси графика, точке окружности и т.д.

  2. Линия соединяет две точки и отображает тенденцию изменения данных.

  3. Цвет инструмент выделения качества данных (например, хорошо зеленый, плохо красный). Имейте в виду, что около 4% людей в силу физиологических или национальных причин могут трактовать значение цвета по-разному. Например, многие дальтоники видят и красный и зеленый цвета как коричневый.

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

На основе этих простых элементов можно построить множество визуализаций. Перечислю лишь основные и самые распространённые типы в зависимости от целей отображения.

Инструменты визуализации

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

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

  • Солнечные лучи похожа на круговую диаграмму, но бонусом содержит иерархическую структуру (внешнее кольцо дочерние элементы внутреннего).
    Примеры применения: количество продаж за разные кварталы по месяцам, количество жильцов в субьектах по федерациям.

  • Вертикальная (столбчатая), горизонтальная диаграмма/гистограмма сравнение значений. Могут быть сгруппированными и сегментированными. Горизонтальный вариант лучше в случае длинных заголовков.
    Примеры применения: сравнение количества продаж разных товаров по месяцам, сравнение количества сотрудников разного уровня в компании, сравнение количества посетителей на разных сайтах.

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

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

  • Линейный график, диаграмма с областями отображение перспективы изменения данных во времени и предсказания тенденций. Линейный график хорошо отображает несвязанные потоки данных, а диаграмма с областями подходит для отображения частей целого.
    Внимание! Полупрозрачные заливки внахлёст не очень хорошее решение, так как могут путать и создавать впечатление отдельности данных.
    Примеры применения: доходы и расходы в неделю за январь, количество активных пользователей в каждом месяце 2020 года.

  • Временной график линейное отображение событий.
    Примеры применения: отображение опыта работы, план выполнения проекта.

  • Диаграмма Ганта вариация временного графика для отображения нескольких слоев информации.
    Примеры применения: отображение опыта работы в разных областях, план выполнения разных частей проекта.

  • Картограмма отображение этнографической информации.
    Примеры применения: уровень опастности в разных странах по количеству заболевших COVID-19, количество людей в разных штатах.

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

  • Граф отображение связей и структуры данных.
    Примеры применения: связи сущностей в базе данных, связи в рабочем коллективе, визуализация нейросети.

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

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

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

  • Блок-схема, дорожки бассейна визуализация процесса.
    Примеры применения: сценарий работы устройств на конвейере, процесс взаимодействия разных департаментов во время работы над проектом.

    Пример дорожки бассейна. Если убрать департаменты получится блок-схемаПример дорожки бассейна. Если убрать департаменты получится блок-схема
  • Таблица простое отображение большого объёма однородной информации для детального изучения. Убедитесь, что таблицы отображают только нужную информацию, достаточно читаемы и интерактивны. Отличный гайд по оформлению таблиц можно найтитут.

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

Проектирование интерфейса

Теперь, когда мы преисполнились видами визуализации и готовы проектировать интерфейс, необходимо перейти кпотребностям пользователей,как и всегда в UX-дизайне.

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

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

Анализ актуального состояния

Анализом актуального состояния занимаются, к примеру, сотрудники АЭС, производства, аэропорта, тот самый админ в серверной.

Дашборд для анализа актуального состояния нагрузки порта Хобокен, Нью-ДжерсиДашборд для анализа актуального состояния нагрузки порта Хобокен, Нью-Джерси

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

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

Приложение мониторинга на производстве от devvelaПриложение мониторинга на производстве от devvela

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

Анализ статистики

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

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

Дашборд состатистики продажДашборд состатистики продаж

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

Десять советов дизайнеру дашборда

  1. Начните проектирование с анализа потребностей пользователя. Определите цель пользователя и какую информацию он анализирует: актуальную или статистическую.

  2. Подберите способ визуализации данных исходя из того, что пользователь хочет оценить (сравнить объём данных, соотношение, увидеть иерархию и т.д.).

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

  4. Группируйте разные данные в отдельные блоки.

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

  6. Не перегружайте один экран множеством разнородных блоков данных. Человек не очень хорошо справляется с объемом более 57 блоков.

  7. Используйте понятные заголовкидля всехданных и графиков.

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

  9. Будьте осторожны с комбинированием разнородных данных в один график. Лучше протестировать получившуюся визуализацию на понятность.

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

Не очень позитивный, но актуальный и информативный пример дашборда от Johns HopkinsНе очень позитивный, но актуальный и информативный пример дашборда от Johns Hopkins

Больше о проектировании интерфейсов и UX можно почитать в моём телеграм-канале Поясни за UX.

Подробнее..

Литье пластика со встроенной электроникой (IME) что это, и почему это новый тренд

24.05.2021 12:21:25 | Автор: admin
Источник фото: TactoTek, финская компания, которая развивает технологию IMSE (In-Mold Structural Electronics).Источник фото: TactoTek, финская компания, которая развивает технологию IMSE (In-Mold Structural Electronics).

Вот уже несколько лет производители электроники говорят о новой прорывной технологии, которая изменит привычные нам устройства и подход к их проектированию: никаких больше механических кнопок и переключателей, сокращение толщины до 2 мм, снижение веса на 70%, а себестоимости на 30%. Причем речь идет не о будущих серийных устройствах типа экрана с двойным сложением, который недавно представила Samsung, а о технологии производства, которая уже сейчас используется в автомобилях, бытовой технике и IoT-гаджетах. Эта технология называется литье с интегрированной / встроенной электроникой или In-Mold Electronics (IME).

На Хабре эту интересную тему еще почему-то не затрагивали. Исправляем это досадное недоразумение.

Начнем со спойлера и сразу отметим, что литье со встроенной электроникой (IME) это не какая-то кардинально новая технология, а эволюция техпроцессов, которые уже использовались ранее. Появились более эластичные материалы и более качественные токопроводящие чернила, которые выдерживают формовку при высокой температуре, поэтому проводники, которые раньше покрывали печатную плату, теперь можно наносить прямо на пластиковый корпус электронного устройства. В результате мы избавляемся от жесткой печатной платы в формате 2D и переключаемся на пленочные платы и 3D-электронику с резисторами, микросхемами, сенсорами, антеннами и светодиодами, которые покрывают изогнутые формы корпусов самых разных устройств.

А теперь давайте обо всем по порядку. Литье с интегрированной электроникой это одно из направлений литья с декорированием в форме, о котором мы уже рассказывали на Хабре. В англоязычных инженерных публикациях такая технология называется In-Mold Decoration (IMD). Напомним, что корпус декорируется под давлением прямо в пресс-форме или в процессе выдувного формования.

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

Принцип маркировки в форме (In-Mold Labeling, IML) Источник: Maspi S.r.l.

На схеме выше показана суть технологий IMD и IML:

  1. Сначала на тонкопленочный пластик наносят нужный рисунок текст, декор или текстуру (например, лого фирмы-изготовителя или подписи для кнопок). Это делается за счет трафаретной или цифровой печати. Получается так называемая аппликация.

  2. Аппликацию помещают в пресс-форму для литья.

  3. Затем в формовочную машину засыпают сухие гранулы полимера, который в расплавленном виде под давлением подается в пресс-форму за пленкой или перед ней.

  4. Форма заполняется полимером, и печатная этикетка приклеивается к пластику.

  5. На выходе при раскрытии формы мы получаем готовую деталь пластикового корпуса уже со встроенной графикой.

Технология декорирования в форме позволяет качественно реализовать идеи промышленных дизайнеров даже в конструкционно сложных пластмассовых компонентах. Детали получаются долговечными и устойчивыми к царапинам.

На фото выше верхняя панель пульта дистанционного управления, изготовленная с декорированием в форме (IMD). Другие всем знакомые примеры устройств, которые производятся по этой технологии: мультиметры, автомобильные панели, игрушки и мобильные телефоны.

В чем разница между декорированием в форме (IMD) и маркировкой в форме (IML)?

Если графика или текстура внутри пресс-формы наносится не на всю поверхность изделия от края до края, а локально, на отдельный участок, то такой тип декорирования называется маркировка в пресс-форме или In-Mold Labeling (IML). С помощью этой технологии можно добавлять цвет, графические элементы и текстурированные области.

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

И вот теперь мы возвращаемся литью с электроникой (In-Mold Electronics), которая стала логическим продолжением предыдущих двух технологий. Похоже, что первое коммерческое внедрение IME было реализовано в инновационной подвесной консоли для автомобиля Ford в 2012 году. Сегодня же IME применяется в производстве бытовой техники, автомобильных панелей, медицинского оборудования, аэрокосмической и носимой электроники.

Эта технология объединяет декоративную аппликацию из IMD-процесса с функциональной электроникой. Только в этом случае на пленке печатается не декор или текст, а дорожки проводников из токопроводящих серебряных красок, также на пленку можно добавлять резисторы, микросхемы и светодиоды. В результате в процессе формовки получаются пластиковые компоненты небольшой толщины (до 2 мм) с достаточной прочностью, высокой функциональностью, добавлением света, клавиатуры и декоративных элементов.

На схеме ниже показано, как это работает:

Источник изображения: Functional Ink Systems for In Mold Electronics by DuPont

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

  2. Термоформование придает печатным носителям трехмерную форму, которая соответствует форме для литья под давлением.

  3. Литье под давлением.

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

Наметившаяся тенденция использование полиэтилентерефталата (PET), который не очень хорошо поддается формовке, но подходит для конструкций с меньшим радиусом кривизны. Однако ПЭТ не переносит сильного нагрева, поэтому сейчас разрабатываются низкотемпературные припои.

Пока эта технология еще воспринимается потребителями как новаторская, сами дорожки-проводники выглядят как элементы декора. :-) Со временем промдизайнеры и hardware-стартапы наверняка предложат свежие идеи по использованию возможностей такого литься.

Источник фото: аналитический отчет IDTechEx за 2020 год.

На фото выше серийные устройства и прототипы, созданные по технологии In-Mold Electronics.

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

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

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

Емкостное сенсорное управление и пользовательские интерфейсы

Технология емкостного сенсорного управления используется во многих приложениях IME. Такой интерфейс заменяет нажатие физической кнопки на прикосновение пальца. Дорожки на плате находятся под сенсорной точкой на панели и следят за изменением в электростатическом поле. Палец действует как функциональный конденсатор, который участвует в электрической цепи устройства. Например, в автомобиле, такие интерфейсы используются для включения фар, питания или регулировки громкости.

Вот, как американская химическая компания Dupont, один из мировых разработчиков токопроводящих чернил для IME, представляет в своей презентации интерфейсы для автомобилей настоящего и будущего:

Источник изображений: Functional Ink Systems for In Mold Electronics by DuPontИсточник изображений: Functional Ink Systems for In Mold Electronics by DuPontИсточник изображений: Functional Ink Systems for In Mold Electronics by DuPontИсточник изображений: Functional Ink Systems for In Mold Electronics by DuPont

Подводим итоги

Автопром это всего лишь одна из многих сфер применения IME, которые мы уже называли выше. Но давайте на ее примере рассмотрим реальный кейс. Возьмем потолочную консоль в автомобиле, которая была спроектирована с использованием печатной платы и классического пластикового корпуса, состоящего из десятков компонентов и сборных деталей, и сравним ее с консолью финской компании TactoTek, которая сейчас разрабатывает свою технологию in-mold structural electronics (IMSE):

Источник фото: Functional Ink Systems for In Mold Electronics by DuPontИсточник фото: Functional Ink Systems for In Mold Electronics by DuPont

Обычная сборка

Версия IME

Сокращение параметра

Вес

650 г

150 г

77%

Глубина сборки

45 мм

3 мм
(без изгиба)

93%

Механические детали

64 штук

2 штуки

96%

Размер PCBA

10 х 4 см

10 х 3 см

25%

Итого, мы получаем значительное уменьшение веса, габаритов, количества подвижных частей и, как следствие, надежности и себестоимости устройства в целом. Судя по активным разработкам в этой теме и отчетам аналитиков, которые прогнозируют рост внедрений IME, начиная с 20232024 года, на рынке электроники намечается новый тренд переход от печатных плат в коробке к трехмерной структурной электронике. Так что если ваша работа связана hardware, можно присмотреться к этому направлению и успеть попасть на гребень волны. Надеемся, что мы вскоре сможем поделиться с читателями Хабра собственными кейсами по разработкам и запуску производства литой электроники.

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

Подробнее..

Figma плагины для продуктового дизайна. Локальный топчик с видео-инструкцией

25.05.2021 16:13:10 | Автор: admin

Для Figma написан целый легион плагинов навсе случаи жизни. Постоянно появляются рейтинги итоп-листы супер-пупер-мега-лучших. Ноэтот инструмент используют люди разных конфессий, решая разные свои задачки. Рыцари фриланса рисуют лендинги, студийные братья собирают промо-сайты, еретики даже визитки иплакаты пытаются делать вFigma. Идля всех есть свои плагины. Поэтому делать общий топ-100 пустое занятие.

Номожно накидать локальный местечковый топчик для продуктового дизайнера например. Внём небудет плагинов вдухе смотрите, какая любопытная идея или если вдруг вам когда-нибудь понадобится заменить все картинки нафото Николаса Кейджа. Только ежедневные трудяги. яуверен, этот список будет полезен нетолько UI-дизайнерам исочувствующим. Что-то полезное найдут для себя ивсе остальные фанаты Figma.

В конце статьи будет ссылка на видео-инструкцию.

Иерархия библиотек вOzon

Яслужу рядовым разработчиком интерфейсов вOzon, где наша маленькая, ногордая команда занимается внутренними продуктами компании. Всяческие админки иCRM-ки. Впрочем, идоосновного сайта мыдотягиваемся.

Работа внашей команде строится последующей схеме: для каждой Job или User Story мыделаем отдельный файл сэлементарным линейным сценарием. Там мысобираем экраны, пройдя которые пользователь решает свою задачу. Втаких сценариях, конечноже, появляются повторяющиеся паттерны, которые можно переиспользовать вдругих User Story иони достойны переноса вотдельную сценарную библиотеку. Этот подход позволяет нераздувать файлы, держать всю систему под контролем идостаточно свободно вносить правки, затрагивающие множество сценариев.

Сейчас унас вOzon есть несколько типов библиотек:

  • Атомарные токены дизайн-системы.

  • Молекулярные библиотеки сэлементами интерфейса. Изних собираются экраны сценариев.

  • Сценарные библиотеки, вкоторые попадают проектные организмы. Оттуда мыможем централизованно использовать ихвдругих сценариях. Повторюсь, разговор нетолько про сайт ozon.ru компании нашего масштаба имеют огромное количество внутренних продуктов, которые также подчиняются единой дизайн-системе.

Библиотечная иерархияБиблиотечная иерархия

Рабочие лошадки иудобные пони

Нохватит зауми! Вперёд кхит-параду. Ида, тут будут представлены иплатные сокровища, ночто значат деньги, когда дело касается Продукта иПродуктивности.

Master

Community

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

Тут унас типичная ситуация. Форма собрана измолекулярных библиотечных компонентов. Спроектированы разные её состояния. Становится понятно, что она достойна быть добавленной всценарную библиотеку. Если делать это штатными средствами, придётся перенастраивать кучу экранов. Ноунас есть Master.

Копируем одну изформ ивставляем её всценарную библиотеку. Делаем компонент ипубликуем. Запускаем команду Pick Target Component изарсенала плагина Master. Возвращаемся вфайл сценария ивыделяем все наши формы. Запускаем команду Link Objects toTarget Component.

Дапребудет стобой сила, Master!

Design System Organizer

Community

ВнародеDSO. Крутейший комбайн поуправлению стилями икомпонентами. Кроме удобной системы организации стилей попапкам, переименованию идублированию, DSO умеет перелинковывать стили икомпоненты набиблиотечные. Причём непоодному компоненту, амассово. Может экспортировать иимпортировать стили между файлами. Всё это позволяет, достаточно безболезненно переезжать между версиями дизайн-системы, дублировать библиотеки ипроворачивать всякие эксперименты.

Тут унас локальные цветовые итипографские стили. Надо привязать ихктакимже библиотечным. Идём вбиблиотеку изапускаемDSO. Заходим вцветовые стили, выделяем нужную группу ивыбираем извыпадающего меню Set AsTarget. Возвращаемся внаш файл, запускаем DSO ивыбираем Relink Styles. Бум! Стили теперь тянуться изкомандной библиотеки. Такимже образом перелинковываем другие стили или компоненты.

Style Organizer

Community

Этот штепсель помогает неударять вгрязь лицом перед разработчиками. Проверяем, откуда тянутся стили. Ловим нестильные шейпы итексты. Чиним вручном или автоматическом режиме. Соответствуем высоким стандартам разработки Ozon иублажаем личного беса-перфекциониста.

Вот тут унас типичная ситуация. Наглазок всё впорядке, нотакли это? Стартуем Style Organizer. Ивидим, что один изчёрных квадратов имеет локальный стиль, второй библиотечный, атретий вообще без стиля. Можно пробежаться посписку ошибок вручном режиме, сшивая стили, аможно нажать Auto Fix Color иStyle Organizer будет действоватьсам.

Similayer

Community

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

Давайте выделим слово Zen синего цвета, вне зависимости отстиля текста. Выделяем один синий Zen изапускаем Similayer. Выбираем параметры Fill Style иText Characters.

Удобненько!

Instance Finder

Community

Ищет ивыделяет только инстансы. Зато делает это шустрее Similayer иработает повсему документу, анепоодной странице.

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

Select Layers

Community

Ещё один незаменимыйвыделятель. Чаще всего ловлю им слои по названию (он поддерживает частичное вхождение запроса). В отличииотSimilayer, SelectLayersне требует выделять образец для поиска соответствия и, что крайне важно, может искать только в выделенном.

Вот тут унас мега-вариант инпута. Выделяем поля пароля изапускаем плагин. Пишем название нужного слоя ивуаля! Можем поменять иконку, например.

Sorter

Community

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

Если попытаться пронумеровать экраны, нерасставив ихвпанели слоёв, получим хаос. Нуок. Выделяем все экраны, запускаем команду Sort Position ипосле этого уже штатный Rename Selection.

Порядочек.

Quantizer

Community

Расставляет элементы всетку сзаданным количеством колонок иотступами. Сильно помогает вработе над вариантами.

Выделяем, что нужно, истартуем Quantizer. Сделаем колонки сотступами по40px.

Кстати, вызнали, что можно таскать объекты всетке закруглые маркеры вцентре? Аменять отступ, хватаясь замаркеры между? Если делать это сShift, шаг будет кратен вашему Nudge Amount.

Swap

Community

Плагин, достойный горячей клавиши. Меняет между собой два любых объекта. Выставляет покоординатам верхнего левого угла.

Выделяем два объекта, запускаем команду Swap и, собственно, всё.

Layer Counter

Community

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

Если снять галку Include Nested Layers, увидим только количество выделенных фреймов.

Retextifier

Community

Может массово заменять текст ввыделенных блоках. Ноесть досадный недочёт при работе вWindows лишний перевод строки, скоторым можно бороться внешними средствами, заменив символ \n на\r или прогнав текст через сам плагин.

Замечательный инструмент, если умеешь импользоваться иутебя macOS.

НаmacOS: копируем текст, например, колонку изGoogle-таблицы. Выделяем целевые текстовые слои изапускаем плагин. Вставляем текст комбинацией Cmd+Shift+V.

Если увас Windows, действуем немного сложнее. Сначала вставляем скопированный текст вфайл Figma. Выделяем его, запускаем Retextifier, копируем итутже вставляем текст, жмём Change. Копируем изменённый текст, далее действуем как наmacOS. Надеюсь, автор плагина что-то придумает поповоду этого недоразумения.

Copy and Paste Text

Community

Вотличие отпредыдущего, этот плагин вставляет вовсе выделенные слои одинаковый текст избуфера. Безусловно полезно.

Копируем нужный текст, выделяем целевые слои, запускаем команду Paste Text

Find & Replace

Community

Поиск изамена потекстовым слоям споддержкой регулярных выражений (Regex). Для тех, кто умеет врегулярки ипонимает, как это круто.

Сейчас мыпоменяем местами буквы ицифры. Запускаем плагин, включаем поддержку Regex ипишем регулярное выражение. Найти (.*)-(.*) изаменить на$2-$1.

Мистика!

Nisa Text Splitter

Community

Разрезает текстовый блок настроки, сортирует, сшивает, расставляет буллиты имногое другое. Рекомендую.

Давайте расставим фильмы похронологии. Запускаем Nisa Text Splitter ирежем наш блок настрочки. Сортируем поалфавиту Sort byalphabet исшиваем обратно водин блок Join text. Внутри плагина ещё много другой годноты. Например, сразу делать Auto Layout изнарезанного текста.

Change Text to Layer Name

Community

Несамый необходимый штепсель, ночастенько выручает. Меняет текст наназвание слоя. Использую всвязке соштатным Rename Selection.

Выделяем текстовые блоки, применяем Rename Selection, оставляем старое название, добавляем кнему дефис иномер. Запускаем команду Change Text toLayer Name.

Математично!

Data Roulette

Community

Когда работаешь над продуктом, утебя есть постоянно используемые наборы данных. Эти наборы накапливаются икочуют изпродукта впродукт. Data Roulette позволяет хранить ихвGoogle-таблицах ирандомно заполнять ими макеты. Итекстами, ифотками.

Делаем Google-таблицу. Линк нанеё добавляем вData Roulette. Называем целевые слои всоответствии сназваниями колонок таблицы поставив вначало решётку. Можно сделать это вмастер-компоненте. Помере необходимости добавляем вGoogle-таблицу новые колонки.

Рулетка рулит!

Content Reel

Community

Для того, кому лень возиться сGoogle-таблицами, Microsoft наплагинил Content Reel. Только нужно залогиниться. Тогда можно будет создавать свои наборы данных.

Тут всё просто. Выделяем целевые слои, выбираем свой или чужой набор данных, вставляем.

Copypasta

Community

Очень часто нужно что-то добавить навсе экраны сценария. Водно итоже место. ИCopypasta прекрасно решает эту задачку.

Выделяем нужную деталь, запускаем Copypasta, жмём Save selection, выбираем целевые экраны, Duplicate Layers.

Шикарно.

Safely Delete Components

Community

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

Safely Delete Components удаляет неиспользуемые мастер-компоненты. Ноэтот плагин нужно использовать состорожностью. Нестоит запускать его вбиблиотеках.

После переноса компонентов всценарную библиотеку, выделяем компоненты локальные изапускаем Safe Delete.

SBOL-Typograph

Community

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

Божечки-Ёжечки!

Хорошо, ногдеже тот самый плагин?

Конечно, это неполный список годноты. Янеставил абсолютно все плагины имог что-то упустить. Ксожалению, механизмы каталогизации исортировки вFigma Community оставляют желать лучшего. Посути ихинет вовсе. Поисковая строка даранжирование поколичеству установок. Апопулярность далеко невсегда результат качества. Поэтому ищите, ставьте, испытывайте иделитесь вашими рабочими лошадками вкомментариях.

Всем удачи напродуктовом фронте!

P.S.: Видео-версию статьи можете посмотреть намоём YouTube-канале.

P.P.S.: Ясерьёзно про комментарии.

Подробнее..

Как подобрать дизайнера для проекта?

05.06.2021 22:17:23 | Автор: admin

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

Я постаралась систематизировать в этой статье свой опыт подбора множества дизайнеров на проекты самой различной сложности: от пакета баннеров, лэндинга до интерфейса No-code платформы без ТЗ. Материал будет полезен тем, кто впервые подбирает дизайнера на проект или получает дизайн не того качества, которого хотелось бы.

Поймите - кто конкретно вам нужен

Соревнование / Irina SalvartСоревнование / Irina Salvart

Дизайнеры, как и программисты все - разные. Как 1С-программист не напишет вам CMS на Python, так и дизайнер лэндингов не сделает прототип CRM, которым будет удобно пользоваться.

Запомним следующее:

  • UX дизайнер/UX инженер/UX аналитик: занимаются разбором требований, пишут ТЗ, создают CJM и, иногда, прототипы.

  • UI дизайнер: создает дизайн на базе прототипа, ТЗ, CJM и прочей информации. Часто заказчики поручают разработку сложных продуктов с нуля UI дизайнеру, и это самые большие грабли, которые только можно представить.

  • У дизайнера часто есть специализация. Мой коллега, который делает только интернет-магазины, прокачал себя в этом настолько, что конверсия его работ высока в любой тематике, за которую он бы ни взялся - практически без предварительного анализа. Однако, графический дизайн простой презентации способен отнять у него массу времени и энергозатрат.

В итоге, если вам нужен простой лэндинг - для него, как правило, хватит и UI дизайнера с ТЗ в зубах. Если нужен прототип CRM, упаковка стартапа - ищем жесткого аналитика, UX инженера с опытом разработки подобных решений.

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

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

Внимательно изучите портфолио дизайнера

Наброски / Irina SalvartНаброски / Irina Salvart

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

Насколько работы похожи? Не раз видела портфолио дизайнеров, где 3-5-7 магазинов подряд являются просто перекрашенными версиями друг друга. С таким дизайнером новую кашу не сваришь.

Есть ли работы, схожие по направленности с вашей? Планируете делать магазин? Не поручайте его дизайнеру, у которого 10 баннеров, 3 лэндинга и 1 сайт услуг. И не верьте тому, кто скажет разберусь. Интернет-магазины подразумевают множество специфики, которую нужно сначала понять, прожить, выстрадать, а потом уже применять.

Какие используются шрифты? Часто начинающие дизайнеры грешат тем, что выбирают или неудачные, нечитаемые шрифты, от которых режет глаз, или просто вставляют стандарт в виде Arial по всем макетам. И то, и другое - повод отказать.

Есть ли работы, созданные для крупных компаний? Если у дизайнера есть опыт работы, скажем, с одним из крупных банков России - значит, как минимум, он стрессоустойчив. Если эти работы не выглядят как кусок понятно чего - значит, еще и хитёр, раз смог сделать что-то хорошее в условиях постоянно меняющихся требований и множества стейкхолдеров. В общем, несколько крупных компаний, доверившихся этому дизайнеру, вряд ли сильно ошиблись все сразу.

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

Как с адаптивностью работ? Удивительно, но в 2к21 некоторые дизайнеры делают мобильные версии по остаточному принципу, хотя мобайл - это отдельная и сложная разработка. Посмотрите, делает ли дизайнер мобильные версии своих сайтов, насколько они удобны в использовании, не грешат ли они слишком малым/слишком большим количеством информации на экран. Если мобильных версий нет вовсе - право слово, не стоит.

Что с цветами? Если в проекте больше 3-4 цветов, или цвета спорят за внимание - в 99% случаев перед вами колхоз. Также следует обратить внимание на то, выделены ли CTA элементы (например, кнопки Купить, Заказать, Перезвоните мне) отдельным, контрастным цветом? Если да - это хороший знак. Планируя проект на большую аудиторию - помните, что далеко не все люди умеют правильно различать цвета, и дизайнер с соответствующим опытом будет очень полезен в таком деле.

Я отбираю дизайнеров по большему количеству критериев, однако выше описан тот шортлист, с которого полезно начать. Если у вас появится желание разобраться в дизайне подробнее - рекомендую начать с изучения базовых правил UI/UX дизайна и далее, в их свете, рассматривать конечные работы дизайнеров.

Попросите верстальщика посмотреть один из проектов дизайнера

Ты и твой парень-геймер / Irina SalvartТы и твой парень-геймер / Irina Salvart

Допустим, дизайнер прошел проверку по качеству работ и сейчас вы хотите понять - насколько быстро ваш верстальщик справится с выполнением проекта. Просто запросите у дизайнера доступ к Figma/PSD любого предыдущего большого проекта. Если откажется - это уже тревожный звоночек.

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

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

Не стесняйтесь спрашивать рекомендации

Всегда за / Irina SalvartВсегда за / Irina Salvart

Конечно, если вам нужны баннеры или обложка для социальной сети - в рекомендациях нет никакого смысла. Однако, если вы планируете разработку сайта или корпоративного решения за сотни тысяч рублей - нет ничего зазорного в том, чтобы спросить рекомендации, телефоны, мессенджеры предыдущих заказчиков. У опытных специалистов, которые работают за адекватный бюджет (1500-4000р/час) и достаточно давно, с этим обычно не бывает проблем.

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

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

P.S. Если на той площадке, где вы нашли дизайнера, у него нет отзывов, спросите где они есть. Если их нет вообще нигде - лучше откажитесь. И помните что отзывы - не равно живые рекомендации.

Попросите показать готовые сайты

История успеха / Irina SalvartИстория успеха / Irina Salvart

Да, UI дизайнер рисует картинки. Да, он не отвечает на 100% за конечный результат в виде сайта. Однако, спустя много лет работы дизайнером, просьба показать готовые сайты мне не кажется такой уж неадекватной.

Дело в том, что если дизайнер качественно сдал макеты, подготовил ТЗ для верстальщика и базово протестировал результат на своих устройствах - сайт не будет выглядеть как то, что стыдно показать. Наличие хороших сайтов по ссылкам часто значит, что дизайнеру важно то, что он делает, и он старается помочь заказчику, а не просто поменять деньги на время.

Предложите заключить договор или безопасную сделку

Когда заказчик предлагает мне заключить договор, единственное что я спрашиваю - оформим на самозанятого, ИП или ООО? Предполагаю, ООО есть далеко не у всех, а вот оформить самозанятого можно за 5 минут. Наличие статуса или ИП у дизайнера свидетельствует о том, что он серьезно относится к своей работе. Также, у ответственного дизайнера всегда есть рамочный бриф, договор, который он готов прислать по запросу.

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

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

Оцените личное общение

Дизайнер из 90-х / Irina SalvartДизайнер из 90-х / Irina Salvart
  • Насколько внимателен дизайнер? Например, вы задаете 2 вопроса в одном сообщении. Если вам отвечают на один, причем такое повторяется не раз - думайте сразу о том количестве правок, которое будете писать, и сколько раз будете просить их исправить.

  • Не боится ли он задавать вопросы, не строит ли из себя мачо, говоря что все понятно? Чем сложнее проект - тем больше вопросов заказчику я задаю, чтобы убедиться, правильно ли я его поняла. Неправильно понятое ТЗ = реальный шанс на то, что половину проекта придется переделывать.

  • Предложил ли дизайнер заполнить бриф, передать ему подробности проекта? Если брифа нет, как и других документов - один тревожный звоночек у вас есть.

  • Насколько грамотно и адекватно он общается? Если со второго сообщения в вас тыкают и говорят на сленге - стоит задуматься.

Работайте по адекватным ценам и соблюдайте договоренности

С моей точки зрения, в ценовой категории 200-1000 рублей в час непрофессиональных и откровенно странных дизайнеров гораздо больше, чем в категории 1500-2500 рублей в час. В первом случае, вы можете делать проект за 100.000 рублей 3 месяца, а во втором за те же деньги проект будет готов через 2 недели. В общем, попытка сэкономить часто будет стоить долгих поисков, большей вероятности возможных проблем, обильного списка правок и увеличения сроков.

И наконец, очевидное: соблюдайте договоренности и не заваливайте дизайнера десятками далеких от дизайна вопросов. Лучше изучить его анкету, работы, обсудить с понравившимся кандидатом 30 минут по Zoom и принять решение: работать или нет.Для найма на постоянную основу, критерии, конечно, другие, однако никто не мешает попробовать сделать с дизайнером 1-3 проекта, а затем, рассмотреть в качестве постоянного сотрудника.

Буду рада услышать о вашем опыте подбора дизайнера на проект!

Подробнее..

Представляем бета-версию Jetpack Compose

04.03.2021 14:05:18 | Автор: admin

Совсем недавно, 24 февраля, мы анонсировали запуск бета-версииJetpack Compose. Этот новый набор инструментов для разработки пользовательского интерфейса позволит легко и быстро создавать оригинальные приложения для всех платформ Android. Jetpack Compose предоставляет современные и декларативные API для языка Kotlin для создания привлекательных и быстрых приложений с меньшим объемом кода. Набор совместим с существующими приложениями для Android и библиотеками Jetpack. Кроме того, его можно использовать вместе с Android Views.

Бета-версия Compose это уже готовый API со всеми основными функциями, необходимыми для комфортной работы. Версия стабильная, поэтому мы не будем изменять или удалять API. Финальная версия 1.0 станет доступна уже в этом году. Сейчас самое время начать знакомство с Compose и запланировать применение новых инструментов в следующих проектах и компонентах.

Возможности бета-версии

При создании Compose нашей команде помогали и другие разработчики, которые оставляли свои отзывы. С момента открытия исходного кода в 2019 году мы выпустили 30 публичных версий продукта, получили более 700 внешних отчетов об ошибках и больше 200 внешних дополнений. Нам нравится наблюдать за результатами вашей работы с Сompose, и мы внимательно изучили все отзывы и предложения, чтобы усовершенствовать API и расставить приоритеты при разработке. Мы значительно доработали альфа-версию продукта, а также добавили и улучшили функционал. Вот некоторые из них:

  • Поддержка сопрограмм (новое)

  • Поддержка специальных возможностей для TalkBack. Другие технологии появятся в финальной версии (новое)

  • Новый API для простого использованияанимации(новое)

  • Совместимостьс Views

  • Компоненты Material UIс примерами кода

  • Ленивые списки аналог RecyclerView

  • РазметкаConstraint Layoutна основе DSL

  • Модификаторы

  • Тестирование

  • Темы и графика для простого добавления тёмных и светлых тем

  • Ввод и жесты

  • Редактируемый и обычный текст

  • Управление окнами

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

Бета-версия Compose поддерживается в последней версииAndroid Studio Arctic Fox Canary, в которой тоже многоновых инструментов:

  • Live Literals: обновление литералов в реальном времени при предварительном просмотре, на устройстве и в эмуляторе (новое)

  • Предварительный просмотр анимации(новое)

  • Поддержка Compose в инструменте Layout Inspector(новое)

  • Интерактивный предварительный просмотр: воспроизведение сборки, выполненной с помощью Compose, в изолированной среде и взаимодействие с ней (новое)

  • Предварительный просмотр разметки: разметка сборки, выполненной с помощью Compose, прямо на устройстве даже при отсутствии полного приложения (новое)

Live Literals в Android EmulatorLive Literals в Android EmulatorLayout Inspector для Jetpack ComposeLayout Inspector для Jetpack Compose

Работа с уже созданным приложением

Jetpack Compose безупречно работает с Android Views, и вам не придется менять старые привычки. Интерфейсы из Compose можно встроить в Android Views, и наоборот. Вдокументации по совместимостирассказано обо всех возможностях использования этих наборов инструментов.

Compose работает не только с Views, но и с самымираспространенными библиотеками. Вам не придется переписывать приложение. Вот что мы интегрировали:

  • Navigation

  • ViewModel

  • LiveData/RX/Flow

  • Paging

  • Hilt

БиблиотекиMDC-Android Compose Theme AdapterиAccompanistработают с темамиMaterialиAppCompatXML. Вам не придется повторять определения тем. Accompanist также предлагает оболочки распространеннымбиблиотекам для загрузки изображений.

Легкость работы в Compose

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

Jetpack Compose полностью написан на языке Kotlin и использует все егопреимущества, предлагая мощные, простые и интуитивные API. Например,сопрограммыпозволяют создавать простые асинхронные API для описания жестов, анимации и прокрутки. Это облегчает написание кода, сочетающего асинхронные события, например жесты, передающие анимацию, с отменой и очисткой, обеспечиваемой структурированным параллелизмом.

Знакомство с Compose

Чтобы вы ближе познакомились с Jetpack Compose, мы обновили своймини-курсс видео, практическими работами и полезными документами. Сегодня мы также обновилируководство по документации, добавили несколько видеороликов и представили новуюпрактическую работу по созданию анимации. Все это поможет лучше работать с Compose. Помимо статей обархитектуре,специальных возможностяхитестировании, теперь вы можете изучить информацию обанимации,спискахидекларативности.

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

Заключение

В бета-версии Jetpack Compose все нужные API и функции готовы к выходу версии 1.0. Самое время знакомиться с набором инструментов и думать о том, где реализовать его возможности. Мы рады вашимотзывамоб использовании Compose. Своими впечатлениями можно также поделиться с другими разработчиками на канале #compose вKotlin Slack.


Выражаем благодарность за помощь в подготовке статьи коллегам: Анна-Кьяра Беллини(менеджер по продуктам),Ник Бутчер(подразделение по работе с разработчиками) и Звиад Кардава (подразделение по работе с разработчиками)

Подробнее..

Реализация двойной панели инструментов в QT

06.03.2021 18:05:18 | Автор: admin

Ссылка на исходный код

Задача и требования

  • Создание двойного "тулбара" (панели инструментов), положение разделителя которого меняло бы соотношение полей сплиттераа.
    Иными словами разделитель должен перетягиваться мышкой.

  • Положение разделителя тулбаров так же должно зависеть от положения сплиттера.

  • При наведении курсора на разделитель тулбаров должен меняться тип курсора на горизонтальный QT::SizeHorCursor

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

  • В целом данный тулбар должен соответствовать типичному представлению пользователя о данном типе тулбаров, это сделает интерфейс простым и понятным

  • Приложение должно давать возможность пользователю менять размер окна

Реализация

Первый шаг

Создаем Qt Widgets application. Основы cpp и h файлов, с которыми мы работаем, сгенерируются без нашего участия.

Object Inspector

Выполнение задачи необходимо начать с построения структуры интерфейса.

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

Параметры виджетов

У виджетов MainWindow, centralwidget и у обоих тулбаров задаем параметр Mouse tracking, как true. Это позволит перехватывать события нажатия мыши, отпускания мыши и передвижения мыши над тулбарами.

У обоих списков, что лежат в сплиттере, задаем параметр минимальной ширины. В моем случае этот параметр равен 200 пикселям. У сплиттера задаем параметр childrenCollapsible, как false. Эти действия необходимы для того, чтобы не было возможности "схлопнуть" одну из секций сплиттера или увести тулбар за пределы экрана. Ниже покажу: как это связано с реализацией cpp файла.

У тулбаров нужно установить параметр movable, как false, чтобы убрать дефолтное задание размеров тулбаров пользователем.

MainWindow.h

private:    Ui::MainWindow *ui;    int timerId = 0;    bool toolbar_dragged = false;

Под Ui::MainWindow *ui создаем две переменные: timerId и toolbar_dragged.
timerId необходима для хранения времени таймера, которое мы будем использовать.
toolbar_dragged определяет: тянет ли пользователь за разделитель

public:    MainWindow(QWidget *parent = nullptr);    ~MainWindow();private slots:    void on_splitter_splitterMoved(int pos, int index, bool windowResized  = true);    void mouseMoveEvent(QMouseEvent *event);    void mouseReleaseEvent(QMouseEvent *e);    void mousePressEvent(QMouseEvent *event);    void timerEvent(QTimerEvent *event);    void resizeEvent(QResizeEvent* event);

Задаем слоты и указываем входной параметр слота on_splitter_splitterMoved windowResized, как параметр, по умолчанию заданный, как true. В resizeEvent, событии изменения размера окна, будет вызываться on_splitter_splitterMoved при помощи emit. Позиция сплиттера при изменении окна меняется, потому и должна меняться позиция разделителя тулбаров. Но в случае изменения размера окна перетягивания разделителя не происходит, toolbar_dragged == false. Потому для случая, когда окно меняет размер, необходимо данное входное условие. Подробнее об этом в cpp файле.

MainWindow.cpp

В файл необходимо включить две библиотеки:

QMouseEvent нужна для обработки сообщений мыши
QWidget необходима для работы с виджетами приложения

#include <QMouseEvent>#include <QWidget>

Рассмотрим конструктор главного окна:

centralWidget()->layout()->setContentsMargins убирает отступы от границ окна, созданные добавлением layout-а, как центрального виджета.
setSpacing(2) нужен потому, что иконки на этом тулбаре не передают событие передвижения мыши в MainWindow, Отступ в 2 пикселя вполне достаточен, чтобы избавиться от этой проблемы.

MainWindow::MainWindow(QWidget *parent)    : QMainWindow(parent)    , ui(new Ui::MainWindow){    ui->setupUi(this);    centralWidget()->layout()->setContentsMargins(0, 0, 0, 0);    //this is nessesary    this->ui->toolBar_2->layout()->setSpacing(2);}

Функция on_splitter_splitterMoved задает фиксированный размер левого тулбара. Второй тулбар автоматически подстраивается под размер первого. Данный размер можно задавать многократно, что мы и делаем. Обычными resize и move методами это сделать нельзя.

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

void MainWindow::on_splitter_splitterMoved(int pos, int /*index*/, bool windowResized){    if (((pos>this->ui->listWidget->minimumSize().width()) &&        (pos<(this->width() - this->ui->listWidget_2->minimumSize().width()))) || windowResized)    {        this->ui->toolBar->setMaximumSize(pos, ui->toolBar->rect().height());        this->ui->toolBar->setMinimumSize(pos, ui->toolBar->rect().height());    }}

Функция mouseReleaseEvent вызывается, когда пользователь отпускает мышь. После этого нужно приветси курсор к типу Qt::ArrowCursor и задать соответствующую переменную toolbar_dragged, как false.

void MainWindow::mouseReleaseEvent(QMouseEvent* /*e*/){    if (toolbar_dragged)    {        toolbar_dragged = false;        this->setCursor(Qt::ArrowCursor);    }}

mousePressEvent перехватывает событие нажатия мыши пользователем.

При срабатывании данной функции, если мышь находится на промежутке +-20 пикселей от разделителя сплиттера, на высоте тулбаров, toolbar_dragged становится true, а тип курсора меняется на соответствующий перетягиванию.

void MainWindow::mousePressEvent(QMouseEvent *event){    QList<int> currentSizes = this->ui->splitter->sizes();    if ((this->ui->toolBar_2->underMouse()) &&        (event->buttons() == Qt::LeftButton) &&        (event->pos().x() < (currentSizes[0]+20)))    {        this->ui->toolBar_2->movableChanged(true);        toolbar_dragged = true;        this->setCursor(Qt::SizeHorCursor);    }    else if ((this->ui->toolBar->underMouse()) &&             (event->buttons() == Qt::LeftButton) &&             (event->pos().x() > (currentSizes[0]-20)))    {        this->ui->toolBar->movableChanged(true);        toolbar_dragged = true;        this->setCursor(Qt::SizeHorCursor);    }}

mouseMoveEvent вызывается при перемещении мыши. Если происходит перетягивание разделителя (toolbar_dragged), то вызывает функцию on_splitter_splitterMoved для изменения размера тулбара, предварительно поменяв размеры сплиттера.

В противном случае, если перетягивания не происходит, но мышь находится на промежутке -2 +5 пикселей от разделителя сплиттера, на высоте тулбаров, с отступами от горизонтальных границ в два пикселя, то тип курсора меняется на SizeHorCursor. Если эти отступы не сделать, то мышь не будет менять тип с SizeHorCursor на ArrowCursor, даже если задать Mouse tracking параметр, как true, всем другим виджетам.

void MainWindow::mouseMoveEvent(QMouseEvent* event){    QList<int> currentSizes = this->ui->splitter->sizes();    if (toolbar_dragged)    {        QList<int> currentSizes = this->ui->splitter->sizes();        currentSizes[0] = event->pos().x();        currentSizes[1] = this->width() - event->pos().x();        this->ui->splitter->setSizes(currentSizes);        emit on_splitter_splitterMoved(event->pos().x(), 1, false);    }    else if ((event->pos().y() > (2+this->ui->toolBar->y())) &&             (event->pos().y() < (this->ui->toolBar->height()-2+this->ui->toolBar->y())) &&             (event->pos().x() < (currentSizes[0]+5)) &&             (event->pos().x() > (currentSizes[0]-10)))    {        this->setCursor(Qt::SizeHorCursor);    }    else    {        this->setCursor(Qt::ArrowCursor);    }}

resizeEvent - событие изменения размера окна. В этом событии нельзя вызвать on_splitter_splitterMoved, потому необходимо сделать таймер, который будет "выходить за пределы" resizeEvent, работать вне ее.

void MainWindow::resizeEvent(QResizeEvent* event){        if (timerId)        {            killTimer(timerId);            timerId = 0;        }        // delay beetween ends of resize and your action        timerId = startTimer(1);    QMainWindow::resizeEvent(event);}

timerEvent меняет размеры тулбара через on_splitter_splitterMoved. При изменении размера окна, положение разделителя сплиттера определяется автоматически

void MainWindow::timerEvent(QTimerEvent *event){    QList<int> currentSizes = this->ui->splitter->sizes();    this->ui->toolBar_2->adjustSize();    emit on_splitter_splitterMoved(currentSizes[0], 1,true);    killTimer(event->timerId());    timerId = 0;}
Подробнее..

Перевод Запускаем модель машинного обучения на iPhone

18.04.2021 18:19:42 | Автор: admin

Чего уж только на Хабре не было, и DOOM на осциллографе, тесте на беременности и калькуляторе запускали, даже сервер Minecraftна зеркалке Canon 200D поднимали. Сегодня же, специально к старту нового потока курса по Machine Learning и углубленного Machine Learning и Deep Learning, попробуем описать кратчайший путь от обучения модели машинного обучения на Python до доказательства концепции iOS-приложения, которое можно развернуть на iPhone. Цель статьи дать базовый скаффолдинг, оставляя место для дальнейшей настройки, подходящей для конкретного случая использования.


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

Шаг 1. Настройка среды

Во-первых, давайте создадим виртуальную среду Python под названием .core_ml_demo, а затем установим необходимые библиотеки: pandas, scikit-learn и coremltools. Чтобы создать виртуальную среду, выполните в терминале эти команды:

python3 -m venv ~/.core_ml_demosource  ~/.core_ml_demo/bin/activatepython3 -m pip install \pandas==1.1.1 \scikit-learn==0.19.2 \coremltools==4.0

Далее установим Xcode. XCode это инструментарий разработки для продуктов Apple. Обратите внимание, что Xcode довольно большой (больше 10 ГБ). Я бы порекомендовал выпить чашку кофе или запустить установку на ночь.

Примечание: в этом туториале используется Xcode 12.3 (12C33) на MacOS Catalina 10.15.5.

Шаг 2. Обучение модели

Мы будем использовать набор данных Boston Housing Price от scikit-learn для обучения модели линейной регрессии и прогнозирования цен на жильё на основе свойств и социально-экономических атрибутов.

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

import pandas as pdfrom sklearn.linear_model import LinearRegressionfrom sklearn.datasets import load_boston# Load databoston = load_boston()boston_df = pd.DataFrame(boston["data"])boston_df.columns = boston["feature_names"]boston_df["PRICE"]= boston["target"]y = boston_df["PRICE"]X = boston_df.loc[:,["RM", "AGE", "PTRATIO"]]# Train a modellm = LinearRegression()lm.fit(X, y)

Шаг 3. Преобразование модели в Core ML

Apple предоставляет два способа разработки моделей для iOS. Первый, Create ML, позволяет создавать модели полностью в рамках экосистемы Apple. Второй, Core ML, позволяет интегрировать модели от третьих лиц в платформу Apple, преобразовав их в формат Core ML. Поскольку мы заинтересованы в запуске обученной модели на iOS, воспользуемся вторым способом.

Перед импортом в Xcode мы преобразуем нашу модель sklearn в формат Core ML (.mlmodel) с помощью пакета python coremltool; coremltools позволяет назначать метаданные объекту модели, такие как информация об авторстве, описание функций модели и результатов.

# Convert sklearn model to CoreMLimport coremltools as cmlmodel = cml.converters.sklearn. \convert(lm,        ["RM", "AGE", "PTRATIO"],        "PRICE")# Assign model metadatamodel.author = "Medium Author"model.license = "MIT"model.short_description = \"Predicts house price in Boston"# Assign feature descriptionsmodel.input_description["RM"] = \"Number of bedrooms"model.input_description["AGE"] = \"Proportion of units built pre 1940"model.input_description["PTRATIO"] = \"Pupil-teacher ratio by town"# Assign the output descriptionmodel.output_description["PRICE"] = \"Median Value in 1k (USD)"# Save modelmodel.save('bhousing.mlmodel')

Шаг 4. Создание нового проекта в Xcode

С Python мы закончили. Теперь можно завершить прототип приложения при помощи только Xcode и Swift. Это можно сделать так:

  1. Откройте Xcode и создайте новый проект.

  2. Выберите iOS как тип мультиплатформы.

  3. Выберите тип приложения App.

Создание нового проекта Xcode для iOSСоздание нового проекта Xcode для iOS

4. Дайте проекту название и выберите интерфейс SwiftUI.

Конфигурация проектаКонфигурация проекта

Теперь просто перетащите созданный на третьем шаге файл модели .ml в каталог Xcode. Xcode автоматически сгенерирует класс Swift для вашей модели, как показано в редакторе ниже. Если вы посмотрите на класс, то заметите, что он содержит детали, которые мы ввели при сохранении нашей модели на Python с помощью coremltools, такие как описания объектов и целевых полей. Это удобно при управлении моделью.

Импорт файла .coreml в проект XcodeИмпорт файла .coreml в проект Xcode

Шаг 5. Создание UI модели

Далее создадим базовый пользовательский интерфейс, изменив файл contentView.swift. Приведённый ниже код на Swift отображает пользовательский интерфейс, который позволяет пользователям настраивать атрибуты дома, а затем прогнозировать его цену. Есть несколько элементов, которые мы можем здесь рассмотреть.

NavigationView содержит необходимый пользовательский интерфейс. Он включает:

  • Степпер (строки 1930) для каждой из наших трёх функций, который позволяет пользователям изменять значения функций. Степперы это в основном виджеты, которые изменяют @State атрибутных переменных нашего дома (строки 68).

  • Кнопку на панели навигации (строки 3140) для вызова нашей модели из функции predictPrice (строка 46). На экране появится предупреждающее сообщение с прогнозируемой ценой.

За пределами NavigationView у нас есть функция predictPrice (строки 4662). Она создаёт экземпляр класса Swift Core ML model и генерирует прогноз в соответствии с хранящимися в состояниях объектов значениями.

import SwiftUIimport CoreMLimport Foundationstruct ContentView: View {  @State private var rm = 6.5  @State private var age = 84.0  @State private var ptratio = 16.5      @State private var alertTitle = ""  @State private var alertMessage = ""  @State private var showingAlert = false      var body: some View {      NavigationView {        VStack {        Text("House Attributes")            .font(.title)        Stepper(value: $rm, in: 1...10,                step: 0.5) {            Text("Rooms: \(rm)")          }          Stepper(value: $age, in: 1...100,              step: 0.5) {          Text("Age: \(age)")          }          Stepper(value: $ptratio, in: 12...22,              step: 0.5) {          Text("Pupil-teacher ratio: \(ptratio)")          }          .navigationBarTitle("Price Predictor")          .navigationBarItems(trailing:              Button(action: predictPrice) {                  Text("Predict Price")              }          )          .alert(isPresented: $showingAlert) {              Alert(title: Text(alertTitle),                    message: Text(alertMessage),              dismissButton: .default(Text("OK")))          }        }      }  }            func predictPrice() {    let model = bhousing()    do { let p = try      model.prediction(          RM: Double(rm),          AGE: Double(age),          PTRATIO: Double(ptratio))        alertMessage = "$\(String((p.PRICE * 1000)))"      alertTitle = "The predicted price is:"  } catch {    alertTitle = "Error"    alertMessage = "Please retry."  }    showingAlert = true}}struct ContentView_Previews: PreviewProvider {    static var previews: some View {        ContentView()    }}

И, наконец, самое интересное: мы можем создать и запустить симуляцию приложения в Xcode, чтобы увидеть нашу модель в действии. В приведённом ниже примере я создал симуляцию с помощью iPhone 12.

Симуляция модели работает на iOS.Симуляция модели работает на iOS.

Заключение

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

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

Если вы кодите на Python и столкнулись в работе с задачами машинного обучения обратите внимание на наш курс Machine Learning, на котором вы сможете систематизировать и углубить полученные самостоятельно знания, пообщаться с профессионалами и применить модели Machine Learning на практике. Даже можно будет ворваться на хакатоны Kaggle.

Если же есть намерение сменить сферу деятельности и погрузиться в ML более плотно то можно посмотреть на продвинутый курс Machine Learning и Deep Learning, на котором вы освоите основные алгоритмы машинного обучения, обучите рекомендательную систему и создадите несколько нейронных сетей.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Дайджест интересных материалов для мобильного разработчика 391 (19 25 апреля)

25.04.2021 18:04:25 | Автор: admin
В новой подборке машинное обучение на iPhone и прямые intent, навигация без магии и уменьшение размера приложения, извилистые дороги операционных систем, продуктовые фреймворки, простой дизайн, мобильное здоровье в прошлом году и многое другое!



Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

Как мы подружили Flutter с CallKit Call Directory
Как запустить модель машинного обучения на iPhone
Как мы создали фреймворк для построения графиков в iOS-приложении
Как мы уменьшили размер приложения Pinterest для iOS на 30%
Swift Memory Layout
Презентация Apple: новый iPad Pro
Презентация Apple: Apple TV 4K
Презентация Apple: метки AirTag
Создание Издателей для HealthKit
Разматывающиеся переходы в Swift 5
MVVM + Координаторы туториал по iOS-архитектуре
Юнит-тесты в iOS
Геттеры и сеттеры в Swift
Самые полезные шорткаты Xcode
MJMaterialSwitch: материальный переключатель для iOS
SwiftShield: обфускация Swift-кода

Android

Давайте будем прямыми в своих intent
Применение SQLiteOpenHelper и Database Inspector в Android-разработке
Фоновая работа в Android: обзор возможностей WorkManager
Пишем под Android с Elmslie
Навигация в многомодульном приложении на Jetpack без магии и DI
Android 12 сможет автоматически переводить приложения на другие языки
Пример приложения для VoIP звонков для Android
CI/CD для Android с использованием Bitbucket Pipelines и Gradle Play Publisher
Модульная Навигация с помощью Jetpack Compose
Приложение Pi Practice в Compose
Анимация с помощью Jetpack Compose
Ускоряем компиляцию Android на устройстве Apple M1
NavigationUI
RecyclerView с волшебными касаниями
RoundedProgressBar: красивый индикатор для Android
Login Template: вход в приложение на Jetpack Compose
Dads: лучшие папины шутки для Android

Разработка

Создаём 2,5D-игру жанра Dungeon Crawling в Unity
Извилистые дороги корейских ОС, или Как Tizen OS и webOS к успеху шли
Создаем свой шахматный движок: алгоритм игры компьютера
Миграция мобильного приложения на Dart 2.12 (Flutter 2)
Библиотека Oboe для высокопроизводительного аудио в играх и приложениях
Игровые механики на уроке геометрии или векторы на Unity 3D
Разделяй и властвуй Использование FSM в Unity
Лучшие клавиатуры для программирования 2021
Nhost делает открытую альтернативу Firebase
Podlodka #212: профессия: Solution Architect
Резюме, которое приведет вас в FAANG
5 главных продуктовых фреймворков Waze
Взламывая код мобильной продуктивности
ВКонтакте проводит седьмой сезон VK Cup
5 вещей, которые я узнал после решения более 500 задач на Leetcode
UserZoom получил $100 млн. на тестирование пользовательского опыта
Что на самом деле означает простой дизайн? Правильный подход к созданию UI-kit для iOS
6 приемов в дизайне, которые помогут вам установить отношения с вашими пользователями
Visual Studio 2022
Как писать самодокументирующийся код
3 структуры данных для прохождения кодинг-собеседования
Веб-приложения это не будущее
Ваш UI неряшливый? 7 распространенных ошибок, которых следует избегать
1 год работы с Flutter: извлеченные уроки
Дизайн для дислексии
Все циклы плохо пахнут
Дайте своему клиенту приятные впечатления от заказа McDelivery Simplified
Test-Driven Development во Flutter
Руководство CTO по современному технологическому стеку
22 лучшие практики, которые помогут вывести ваши навыки проектирования API на новый уровень
Ваш язык программирования не имеет значения
20 вещей, которые я хотел бы знать до того, как начал работать менеджером по продукту
Как Duolingo проводит масштабные эксперименты
Создание поиска для вашего продукта
Скелетоны в Flutter
Чистая архитектура для чайников
3 различных типа роадмепов, которые необходимо освоить каждому PM

Аналитика, маркетинг и монетизация

Epic Games Store увеличивает количество приложений
EA готовит мобильный Battlefield
Мобильное здоровье 2021: отчет Sensor Tower
LOVEMOBILE #11: SLON Media
Zoom запустил фонд для инвестиций в приложения на своей платформе
Microsoft делает новый магазин приложений для Windows 10
Правила AppTrackingTransparency начнут работу с 26 апреля
Я локализовал свою игру на 11 языков: что это дало
N26: страховой банк
Доходы приложений для свиданий в январе поставили рекорд
40 тыс MAU в приложении с бюджетом в 150 тыс
7 метрик, которые помогут вам принимать более разумные решения на этапе Product-Market Fit

AI, Устройства, IoT

Samsung превратит устаревшие смартфоны пользователей в устройства для управления умным домом
Молодые изобретатели смогут получить 3 млн рублейв конкурсе James Dyson Award
Я запрограммировал простой AI для NFS Most Wanted
Математика, необходимая для успешного прохождения собеседований по машинному обучению

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

Дайджест интересных материалов для мобильного разработчика 392 (26 апреля 2 мая)

04.05.2021 08:16:25 | Автор: admin
Уходим на длинные выходные с новой подборкой интересных статей и новостей. В ней библиотеки и борьба с Apple, карточки ВКонтакте и качество кода, Flutter и Kotlin, умение заканчивать проекты, понимать путь клиента и многое другое!



Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

Переход вашего приложения на модули пакетов Swift
Мечтал стать сценаристом, а стал программистом
В App Store появились поисковые подсказки
10 SwiftUI-библиотек 2021
Начато производство Apple M2
Apple выпускает обновление iOS 14.5
Немецкие компании подают антимонопольный иск против Apple, касающийся iOS 14.5
ФАС оштрафовала Apple на $12 млн по иску Лаборатории Касперского
Начинаем работу с Combine (практические примеры фреймворка Combine в UIKit и SwiftUI)
Руководство по многопоточности Grand Central Dispatch
Градиент в Swift за 4 строчки кода
SwiftUI в продакшене
Что такое Замыкания и как они работают в Swift? (Пошаговое руководство)
Кастомные переходы View Controller-ов в Swift
Введение в работу с Codable в Swift
Swift: как написать полный логер
Скелетный проект для масштабируемой разработки под iOS
SwiftUI: как создать ячейку со свайпом
Кастомный Top Tab
Простая валидация форм с RxSwift
Начинаем работу с Firebase Realtime DB на WatchOS
GLWalkthrough: онбординг с подсветкой

Android

Как реализован экран с карточками заявок в друзья и рекомендациями в приложении ВКонтакте
Как мы интегрировали Huawei Mobile Services в два этапа
Google Play усиливает борьбу с мислидами
Улучшаем работу Layout Preview в Android Studio
Как повысить качество кода
Android Studio начала поддерживать M1
Опубликовано расписание I/O 2021
Google Play будет собирать данные об использовании приложений для ускорения запуска
Курс Kotlin для начинающих
Кастомная форма с помощью Jetpack Compose
Уроки, извлеченные при переносе моего приложения на Jetpack Compose
Автомиграция Room
Тест на Android Studio бенчмарк компьютеров
Не запускайте бенчмарки в отлаживаемом Android-приложении
Один AlertDialog, чтобы править ими всеми
Jetpack Compose: простой способ сделать RecyclerView
Динамическая доставка с помощью Jetpack Compose
Эволюция Quality Assurance для приложений в Azimo
Красивый сплеш скрин для Android с анимацией
Пример WebRTC в Kotlin
Как обрабатывать изменения конфигурации в Android
Android RecyclerView Swipe Gestures: жесты в RecyclerView
Android ScreenshotDetection: определение скриншотов
Linkester: тестирование глубоких ссылок в приложении

Разработка

Еще пять инструментов против читеров на мобильном проекте с DAU 1 млн пользователей
Моя история реализации офлайн приложения Хабра
Этический антидизайн: как разработать продукт, не вызывающий привыкания
История разработки SDK для приема платежей в мобильном приложении на Flutter
Немного о графиках, сплайнах и генерации ландшафта
Тестирование push-уведомлений в мобильных приложениях
Решение задач позиционирования при просмотре карты во Flutter
Нестабильные(Flaky) тесты одна из основных проблем автоматизированного тестирования
Углубленный анализ тестирования виджетов во Flutter. Часть I: testWidgets() и TestVariant
Начинающему QA: полезные функции снифферов на примере Charles Proxy
Podlodka #213: инвестиции в стартапы
Microsoft Build 2021 пройдет с 25 по 27 мая
Как заканчивать игры (и другие проекты)
Книга Создание мобильных приложений в масштабе: 39 инженерных задач
Дизайн приложений: примеры для вдохновения #41
Хороший дизайн это наука, а не искусство
Кейс: как Surf и Росбанк сделали первое в России банковское приложение на Flutter
Как нобелевский лауреат помог нам с дизайном приложения для I Love Supersport
5 лучших инструментов для создания приложений без кода
Как великие продуктовые менеджеры принимают правильные решения: подход Привычный
Как безопасно выкладывать в open source внутреннее ПО: лучшие практики
5 преимуществ парного программирования и как делать это удаленно с помощью VS Code
Как ежегодно экономить 135,000 евро с Google Analytics 4 + BigQuery
Локальные уведомления во Flutter
10 забавных расширений VS Code, которые помогут программировать
Лучшие практики для дизайна модальных окон
Хотите стать лучшим UX-дизайнером в 2021? Делайте для эмоций
Лучшие практики: дизайн автозаполнения
Почему хорошие инженеры не начинают собственный бизнес
Интервью у senior-инженера в Facebook: единственная статья, которую вам нужно прочитать
Метрики качества кода

Аналитика, маркетинг и монетизация

Понимаем путь клиента по приложению с помощью событий Firebase и BigQuery
Mobvista покупает Reyun
Маркетологи в мобайле: Роман Хуртов (Parimatch Russia)
Neverland помогает с работой по саду
Oath Care: форум для мам по подписке

AI, Устройства, IoT

S в аббревиатуре IoT означает Security, или Как я лампу хакнул
Йога глазами дата-сайентиста: как мы строили computer vision в мобильном приложении
О чем спорят строители Умных Домов, Бань, Дач и Гаражей
Amazon выкладывает софт DeepRacer в open source
10 лучших проектов в области науки о данных для начинающих

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

IOS. UI. Примы. Часть 1

27.03.2021 20:12:03 | Автор: admin

Привет читателям Хабра!

Я iOS-разработчик, и так случилось, что мне приходилось много делать в ui: кастомные view, тени, layout-ы, кнопки и вот это всё. В этой и паре следующих статей хочу поделиться некоторыми приёмами, которые помогали мне добиваться весьма красивых и интересных эффектов в плане рисования компонентов ui. Надеюсь, кому-нибудь это будет полезно. Ну или просто интересно.

Небольшое введение

Не берусь говорить за всех, но, исходя из личного опыта, сложилось впечатление, что для достаточно большого количества разработчиков рисование каких-то "плашек" с нестандартными формой и поведением крайне нежелательная задача. Кто-то больше в архитектуре, кто-то больше про "сделать бизнесу хорошо" с минимальными усилиями (соответственно, просят поумерить пыл дизайнеров) и т.п. И если уж приходится делать что-то из ряда вон, то начинается google, stackoverflow, эксперименты и т.д., что занимает немало времени, и появляется ощущение, что оно того вообще не стоит. Собственно, эту небольшую серию статей я и задумал как некоторую справку, прочтение которой снимет ряд вопросов и позволит быстрее оценивать/реализовывать нетипичные ui-компоненты. На конкретных примерах постараюсь продемонстрировать, как, что и почему можно делать.

Пример 1: view с нестандартными границей и тенью

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

Теперь чуть подробнее. У CALayer есть свойство mask. В документации можно прочитать, что это тот же самый опциональный CALayer, и если он не nil, то его альфа-канал используется как маска для контента исходного layer. То есть если взять png-картинку с котом и прозрачностью и каким-то образом засунуть ее в CALayer (назовем его catLayer), то при присваивании layer.mask = catLayer контент нашего исходного layer будет в виде кота, что бы ни находилось у него внутри. Может, текстовый кот получится, если внутри layer много текста. В нашем же случае нужен layer-маска в виде произвольной фигуры. Тут может помочь CAShapeLayer - наследник CALayer, который, грубо говоря, умеет внутри себя рисовать произвольную форму посредством задания ему проперти path. При использовании shapeLayer в качестве маски, всё, что находится вне формы, описываемой shapeLayer.path, работает как фильтр с alpha = 0.

Саму форму можно задать, используя UIBezierPath: для этого у последнего есть функции
addLine(to:), move(to:), addArc(withCenter:radius:startAngle:endAngle:clockwise) и т.д.
Здесь хотелось бы отметить пару моментов. Итоговый path должен выглядеть так, будто его "нарисовали, не отрывая карандаш от бумаги": стартуем из произвольной точки на границе и постепенно добавляем линии к общему пути так, чтобы конец предыдущей линии был началом следующей линии, и так далее. В конце возвращаемся в исходную точку. Некоторых сбивает с толку функция addArc, потому что в ней есть вроде и startAngle и endAngle, и clockwise. Вот clockwise как раз и нужен для того, чтобы управлять тем, вдоль какой из частей окружности, заданной двумя углами, мы двигаемся. В нашем примере в правом верхнем углу добавляется кусок окружности от -/2 до 0 с clockwise равным именно true, иначе мы бы просто вырезали целую окружность из нашей view:


А зачем здесь вообще дополнительный слой? Почему бы не задать маску у исходного?
Проблема в том, что маска работает так, что отрезает просто всё, что ей попадётся, в том числе и тень слоя. Так что если задавать mask у слоя исходной view, то тени просто не будет видно.

Наконец, чтобы придать нужную форму тени, у CALayer есть свойство shadowPath.

Полный код примера 1
import UIKitfinal class SimpleCustomBorderAndShadowView: UIView {  private let frontLayer = CALayer()  private let inset: CGFloat = 40    // MARK: Override    override init(frame: CGRect) {    super.init(frame: frame)    setup()  }    required init?(coder: NSCoder) {    super.init(coder: coder)    setup()  }    override func layoutSubviews() {    super.layoutSubviews()    frontLayer.frame = bounds        let maskAndShadowPath = UIBezierPath()    maskAndShadowPath.move(to: CGPoint(x: 0, y: inset))    maskAndShadowPath.addLine(to: CGPoint(x: inset, y: 0))    maskAndShadowPath.addLine(to: CGPoint(x: bounds.width - inset, y: 0))    maskAndShadowPath.addArc(withCenter: CGPoint(x: bounds.width - inset, y: inset),                             radius: inset,                             startAngle: -CGFloat.pi / 2,                             endAngle: 0,                             clockwise: true)    maskAndShadowPath.addLine(to: CGPoint(x: bounds.width, y: bounds.height - inset))    maskAndShadowPath.addLine(to: CGPoint(x: bounds.width - inset, y: bounds.height))    maskAndShadowPath.addLine(to: CGPoint(x: inset, y: bounds.height))    maskAndShadowPath.addArc(withCenter: CGPoint(x: inset, y: bounds.height - inset),                             radius: inset,                             startAngle: CGFloat.pi / 2,                             endAngle: CGFloat.pi,                             clockwise: true)    maskAndShadowPath.close()        (frontLayer.mask as? CAShapeLayer)?.frame = bounds    (frontLayer.mask as? CAShapeLayer)?.path = maskAndShadowPath.cgPath    layer.shadowPath = maskAndShadowPath.cgPath   }    // MARK: Setup    private func setup() {    backgroundColor = .clear        layer.shadowColor = UIColor.black.cgColor    layer.shadowOffset = .zero    layer.shadowRadius = 20    layer.shadowOpacity = 1        frontLayer.mask = CAShapeLayer()    frontLayer.backgroundColor = UIColor.white.cgColor    layer.addSublayer(frontLayer)  }}

Пример 2: view с вырезанной кривой произвольного вида

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

Для того, чтобы вырезать что-то внутри слоя, нужно понимать, по какому правилу происходит раскрашивание форм, созданных с помощью UIBezierPath. В принципе, про это довольно внятно написано здесь. Получается, чтобы добиться эффекта как на картинке выше, нужно в итоговый path для маски добавить путь, обходящий внешнюю границу view, что делается с помощью UIBezierPath(roundedRect:cornerRadius:), и после добавить путь, отвечающей вырезу в форме кривой.

Для формы кривой используется функция addQuadCurve(to:controlPoint:). И если взять UIBezierPath, вызывать addQuadCurve, проставить ему ширину с помощью lineWidth, и добавить это в итоговый path для маски то... Ничего не выйдет. Если чуть-чуть задуматься и ещё вспомнить про это, то всё начинает казаться логичным: CoreGraphics нужно как-то сказать о границах, при переходе через которые происходит подсчёт каких-то counter-ов для дальнейшего решения о том, красить данную область или нет. Чтобы построить путь именно вокруг кривой, у CGPath есть функция copy(strokingWithWidth:lineCap:lineJoin:miterLimit:). Сам CGPath, в свою очередь, можно получить из UIBezierPath, обращаясь к свойству cgPath.

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

Полный код примера 2
import UIKitfinal class ErasedPathView: UIView {  private let frontLayer = CAShapeLayer()    // MARK: Override    override init(frame: CGRect) {    super.init(frame: frame)    setup()  }    required init?(coder: NSCoder) {    super.init(coder: coder)    setup()  }    override func layoutSubviews() {    super.layoutSubviews()        frontLayer.frame = bounds        let maskAndShadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 20)        let curvePath = UIBezierPath()    curvePath.move(to: CGPoint(x: bounds.width / 4, y: bounds.height / 4))    curvePath.addQuadCurve(to: CGPoint(x: bounds.width * 3 / 4, y: bounds.height * 3 / 4),                           controlPoint: CGPoint(x: bounds.width, y: 0))        let innerPath =  UIBezierPath(cgPath: curvePath.cgPath.copy(strokingWithWidth: 70, lineCap: .round, lineJoin: .round, miterLimit: 0))    maskAndShadowPath.append(innerPath)        (frontLayer.mask as? CAShapeLayer)?.frame = bounds    (frontLayer.mask as? CAShapeLayer)?.path = maskAndShadowPath.cgPath    layer.shadowPath = maskAndShadowPath.cgPath  }    // MARK: Setup    private func setup() {    backgroundColor = .clear    frontLayer.backgroundColor = UIColor.white.cgColor        layer.addSublayer(frontLayer)    let mask = CAShapeLayer()    mask.fillRule = .evenOdd    frontLayer.mask = mask        layer.shadowColor = UIColor.black.cgColor    layer.shadowOffset = .zero    layer.shadowRadius = 20    layer.shadowOpacity = 1  }}

Пример 3: рисование форм внутри view

Для того, чтобы просто рисовать внутри вашей view всё, что нравится, без создания дополнительных слоёв, можно опять же использовать CAShapeLayer. Нужно сделать override статического свойства layerClass у исходной view, возвращая ShapeLayer.self, и так же как и в Примере 1 задать этому слою path.

Есть один нюанс, не упомянутый ранее. При построении непрерывного пути при рисовании произвольной формы можно случайно перепрыгнуть из конца очередной линии в совершенно другое место. Типичный пример добавление нового куска окружности при непустом path. В таких случаях CoreGraphics просто напросто дорисует за вас недостающую линию, соединяющую последнюю точку пути и новую точку очередной добавляемой линии. В совокупности с fillRule у CAShapeLayer этим можно аккуратно пользоваться. Например, на третьей справа картинке (карта треф) этот подход существенно упрощает рисование: не нужно думать о том, в каких именно местах пересекаются окружности.

Пики
import UIKitfinal class SpadeCardView: UIView {    var selfLayer: CAShapeLayer { layer as! CAShapeLayer }  private let inset: CGFloat = 20    // MARK: Override    static override var layerClass: AnyClass { CAShapeLayer.self }    override init(frame: CGRect) {    super.init(frame: frame)    setup()  }    required init?(coder: NSCoder) {    super.init(coder: coder)    setup()  }    override func layoutSubviews() {    super.layoutSubviews()        let path = UIBezierPath()    let size = bounds.width - 2 * inset    let radius = size / 4    let alpha = atan(2 * radius / size)        path.move(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2))    path.addArc(withCenter: CGPoint(x: inset + radius, y: bounds.height / 2),                radius: radius, startAngle: 0,                endAngle: CGFloat.pi + 2 * alpha,                clockwise: true)    path.addLine(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2 - size / 2))    path.addArc(withCenter: CGPoint(x: bounds.width / 2 + radius, y: bounds.height / 2),                radius: radius,                startAngle: -2 * alpha,                endAngle: CGFloat.pi,                clockwise: true)    path.addQuadCurve(to: CGPoint(x: bounds.width / 2 + radius, y: bounds.height / 2 + size / 2),                      controlPoint: CGPoint(x: bounds.width / 2, y: bounds.height / 2 + size / 2))    path.addLine(to: CGPoint(x: bounds.width / 2 - radius, y: bounds.height / 2 + size / 2))    path.addQuadCurve(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2),                      controlPoint: CGPoint(x: bounds.width / 2, y: bounds.height / 2 + size / 2))        selfLayer.path = path.cgPath  }    // MARK: Setup    private func setup() {    selfLayer.fillColor = UIColor.black.cgColor    selfLayer.strokeColor = UIColor.black.cgColor    selfLayer.lineWidth = 2        layer.shadowColor = UIColor.black.cgColor    layer.shadowOffset = .zero    layer.shadowRadius = 10    layer.shadowOpacity = 1  }}
Бубны
import UIKitfinal class DiamondCardView: UIView {    var selfLayer: CAShapeLayer { layer as! CAShapeLayer }  private let inset: CGFloat = 20  private let adjustment: CGFloat = 10    // MARK: Override    static override var layerClass: AnyClass { CAShapeLayer.self }    override init(frame: CGRect) {    super.init(frame: frame)    setup()  }    required init?(coder: NSCoder) {    super.init(coder: coder)    setup()  }    override func layoutSubviews() {    super.layoutSubviews()        let path = UIBezierPath()    let size = bounds.width - 2 * inset        path.move(to: CGPoint(x: inset, y: bounds.height / 2))    path.addQuadCurve(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2 - size / 2),                      controlPoint: CGPoint(x: bounds.width / 2 - adjustment, y: bounds.height / 2 - adjustment))    path.addQuadCurve(to: CGPoint(x: bounds.width - inset, y: bounds.height / 2),                      controlPoint: CGPoint(x: bounds.width / 2 + adjustment, y: bounds.height / 2 - adjustment))    path.addQuadCurve(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2 + size / 2),                      controlPoint: CGPoint(x: bounds.width / 2 + adjustment, y: bounds.height / 2 + adjustment))    path.addQuadCurve(to: CGPoint(x: inset, y: bounds.height / 2),                      controlPoint: CGPoint(x: bounds.width / 2 - adjustment, y: bounds.height / 2 + adjustment))        selfLayer.path = path.cgPath  }    // MARK: Setup    private func setup() {    selfLayer.fillColor = UIColor.red.cgColor    selfLayer.strokeColor = UIColor.red.cgColor    selfLayer.lineWidth = 2        layer.shadowColor = UIColor.black.cgColor    layer.shadowOffset = .zero    layer.shadowRadius = 20    layer.shadowOpacity = 1  }}
Трефы
import UIKitfinal class ClubCardView: UIView {    var selfLayer: CAShapeLayer { layer as! CAShapeLayer }  private let inset: CGFloat = 20  private let adjustment: CGFloat = 10    // MARK: Override    static override var layerClass: AnyClass { CAShapeLayer.self }    override init(frame: CGRect) {    super.init(frame: frame)    setup()  }    required init?(coder: NSCoder) {    super.init(coder: coder)    setup()  }    override func layoutSubviews() {    super.layoutSubviews()        let path = UIBezierPath()    let size = bounds.width - 2 * inset    let radius = size / 4        path.move(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2))    path.addArc(withCenter: CGPoint(x: bounds.width / 2 - radius, y: bounds.height / 2 + adjustment),                radius: radius,                startAngle: 0,                endAngle: 2 * CGFloat.pi,                clockwise: true)    path.addArc(withCenter: CGPoint(x: bounds.width / 2, y: bounds.height / 2 - radius),                radius: radius,                startAngle: CGFloat.pi / 2,                endAngle: 5 * CGFloat.pi / 2,                clockwise: true)    path.addArc(withCenter: CGPoint(x: bounds.width / 2 + radius, y: bounds.height / 2 + adjustment),                radius: radius,                startAngle: CGFloat.pi,                endAngle: 3 * CGFloat.pi,                clockwise: true)    path.addQuadCurve(to: CGPoint(x: bounds.width / 2 + radius, y: bounds.height / 2 + size / 2),                      controlPoint: CGPoint(x: bounds.width / 2, y: bounds.height / 2 + size / 2))    path.addLine(to: CGPoint(x: bounds.width / 2 - radius, y: bounds.height / 2 + size / 2))    path.addQuadCurve(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2),                      controlPoint: CGPoint(x: bounds.width / 2, y: bounds.height / 2 + size / 2))        selfLayer.path = path.cgPath  }    // MARK: Setup    private func setup() {    selfLayer.fillColor = UIColor.black.cgColor    selfLayer.strokeColor = UIColor.black.cgColor    selfLayer.fillRule = .nonZero    selfLayer.lineWidth = 2        layer.shadowColor = UIColor.black.cgColor    layer.shadowOffset = .zero    layer.shadowRadius = 20    layer.shadowOpacity = 1  }}
Черви
import UIKitfinal class HeartCardView: UIView {    var selfLayer: CAShapeLayer { layer as! CAShapeLayer }  private let inset: CGFloat = 20    // MARK: Override    static override var layerClass: AnyClass { CAShapeLayer.self }    override init(frame: CGRect) {    super.init(frame: frame)    setup()  }    required init?(coder: NSCoder) {    super.init(coder: coder)    setup()  }    override func layoutSubviews() {    super.layoutSubviews()        let path = UIBezierPath()    let size = bounds.width - 2 * inset    let radius = size / 4    let alpha = atan(4 * radius / (3 * size))        path.move(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2 + size / 2))    path.addArc(withCenter: CGPoint(x: inset + radius, y: bounds.height / 2 - radius),                radius: radius,                startAngle: CGFloat.pi - 2 * alpha,                endAngle: 0,                clockwise: true)    path.addArc(withCenter: CGPoint(x: bounds.width / 2 + radius, y: bounds.height / 2 - radius),                radius: radius,                startAngle: -CGFloat.pi,                endAngle: 2 * alpha,                clockwise: true)    path.addLine(to: CGPoint(x: bounds.width / 2, y: bounds.height / 2 + size / 2))        selfLayer.path = path.cgPath  }    // MARK: Setup    private func setup() {    selfLayer.fillColor = UIColor.red.cgColor    selfLayer.strokeColor = UIColor.red.cgColor        layer.shadowColor = UIColor.black.cgColor    layer.shadowOffset = .zero    layer.shadowRadius = 20    layer.shadowOpacity = 1  }}

Заключение

Ниже, так сказать, things to remember:

  • +1 CALayer, mask, CAShapeLayer, shadowPath для кастомной границы и тени

  • copy(strokingWithWidth:lineCap:lineJoin:miterLimit:) для объемной обводки path

  • CAShapeLayer, path + fillRule даёт интересные возможности

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

Подробнее..

SVGator.com на практике

20.05.2021 18:13:57 | Автор: admin

Привет, мы дизайнеры экосистемы Своё для фермеров и банковских сервисов Россельхозбанка. Рассказываем, зачем нам понадобился SVGator.

Как мы пришли к SVGator.com

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

Наш путь начался с lottie, как с более популярного сервиса. Ключевой недостаток: встроенная api. Нам хотелось найти более прагматичное решение без интеграции, поэтому выбор пал на SVGator. В наши цели попадало закрытие микроанимаций элементов интерфейса, оформительские детали и т.п.

Что за зверь и какие задачи решает

SVGator.com это web-платформа для создания svg-анимаций, то есть svg-файлов со встроенными анимациями, которые без каких-либо проблем интегрируются в html. Можно задать последовательную обработку таких анимаций. Возможен экспорт как js, так и чистым CSS. Возможности js немного шире, но разница не существенная.

В рамках продуктов группы Своё мы создавали различные анимации для лоадеров, микроэлементов (таких как стрелка дропдауна), логотипов и элементов оформления. Мы ещё находимся на стадии экспериментов с SVGator, но уже чётко понимаем, в каких моментах он имеет преимущества и что можно создать с его помощью:

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

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

Элементы дизайна. Например, иллюстрации

Интерфейс и функционал

На данный момент SVGator располагает исключительно веб-платформой, но весь необходимый функционал присутствует. Интерфейс напоминает классическое решение канувшего в Лету Flash и взявшего бразды правления After Effects. Слева слои файла. Сверху панель инструментов. Снизу таймлайн, а по центру сама рабочая область. Если есть навыки работы с АЕ, совершенно точно SVGator покажется очень простым. Но в этом и его преимущество порог входа значительно ниже.

Инструмент включает в себя следующие функции: работа с размером, положением, поворотом, наклоном, цветом, прозрачностью, а также богатый функционал для работы с обводкой. Помимо этого, поддерживается работа с масками и векторами движения. Этого достаточно для работы с классическими решениями анимации. Ещё один важный момент работа с easing, которые также встраиваются в svg-файл. Это упрощает работу на этапе front-end, и дизайнеру не приходится тревожить разработчика скоростью анимации.

Механика работы несколько отличается от привычных решений After Effects, вы выбираете необходимый элемент в меню слева и добавляете на него нужный эффект. После этого слой появляется в области таймлайна и можно менять значения, добиваясь нужной анимации. Сами значения находятся на панели справа. Easing могут быть привязаны как ко всему таймлайну, так и к конкретному отрезку между keyframes.

Чем svg отличается от sequence, gif, video? А также основные конкуренты:
Lottie и Keyshape

Ключевые преимущества svg вес, отсутствие api и возможность давать плавную анимацию. Разберём каждую из альтернатив.

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

Gif. Решение потеряло свою актуальность также из-за веса. Однако есть и другой минус отсутствие плавности анимации. Часто в погоне за оптимизацией размера дизайнерам приходится поскупиться количеством кадров в секунду, а также сама постобработка оставляет желать лучшего.

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

Lottie. Это один из ключевых конкурентов SVGator. Компания раньше вышла на рынок и имеет значительно больше почитателей. Плюс возможности анимации значительно больше, поскольку базовая анимация собирается в Аfter Еffects, а lottie является своего рода оболочкой для такой анимации. Но для работы с данным решением необходимо предустановить api на платформу, что не всегда хорошо для продакшена больших нагруженных систем.

Keyshape. Очень близкая по возможностям платформа, различия лишь в деталях. Текущий функционал Keyshape несколько выше и позволяет делать экспорт чистым svg + css. К недостаткам можно отнести её стационарность и возможность работать исключительно на macOS.

Вот поэтому и стоит обратить внимание на SVGator. Плавные анимации, реализованные кодом, кроссплатформенные, без каких-либо api и с нормальным весом.

SVGator в экосистеме Своё

На наших продуктах Своё Фермерство, Своё Жильё, Своё Родное, Монеты, Подбор персонала, Своё Село и т.д. уже присутствуют наработки в SVGator: лоадер, интерактивные состояния компонентов, логотипы, а также иллюстрации для оформления разделов. Мы планируем увеличивать их долю и продолжать экспериментировать.

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

Перспективы

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

Объёмные решения склонны к излишней нагрузке системы и могут стать камнем преткновения. Поэтому в наших планах использовать этот инструмент поступательно, небольшими шагами, анимируя поначалу небольшие элементы.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru