Как создать программу с интерфейсом

от admin

Проектирование графического интерфейса пользователя

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

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

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

Общие принципы

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

Какие ЭИ создать?

    Разработка интерфейса обычно начинается с определения задачи или набора задач, для которых продукт предназначен

Какой должен быть дизайн ЭИ?

На самом деле, дизайн ЭИ — тема отдельной статьи. Тут нужно учитывать все: начиная от цвета, формы, пропорций, заканчивая когнитивной психологией. Однако, несколько принципов все же стоит отметить:

    Цвет. Цвета делятся на теплые(желтый, оранжевый, красный), холодные(синий, зеленый), нейтральные(серый). Обычно для ЭИ используют теплые цвета. Это как раз связано с психологией восприятия. Стоит отметить, что мнение о цвете — очень субъективно и может меняться даже от настроения пользователя.

Как правильно расположить ЭИ на экране?
    Есть утверждение, что визуальная привлекательность основана на пропорциях. Помните известное число 1.62? Это так называемый принцип Золотого сечения. Суть в том, что весь отрезок относится к большей его части так, как большая часть, относится к меньшей. Например, общая ширина сайта 900px, делим 900 на 1.62, получаем

555px, это ширина блока с контентом. Теперь от 900 отнимаем 555 и получаем 345px. Это ширина меньшей части:

Как ЭИ должны себя вести?

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

Такой переключатель напрямую отражает состояние ЭИ

В заключении

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

GUI с нуля за час. Ищем решения типовых задач при создании графического интерфейса

Lead iOS-разработчик в neoviso Игорь Шавловский в материале «Давайте напишем Windows!» предложил «зажигать пиксели один за другим». Теперь переходим к рендеру и разбираем код из репозитория на GitHub. Для её запуска используйте класс CustomApplication.

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

Так как корневой элемент интерфейс — это рабочий стол, рисовать следует именно его, при этом команды должны уходить на драйвер устройства (VideoDevice). Допустим, устройство есть, рабочий стол тоже. Но как их связать вместе? Нужен посредник (Application), который будет готовить девайс и через графический контекст передавать на него команды рендера с рабочего стола. Когда он должен это делать

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

Как работает GUI-приложение

Что ещё может происходить в UI-цикле? Если нам надо переместить окно на рабочем столе или сменить текст в лейбле, добавить дочерний элемент, можем ли мы сделать это во время отрисовки? Очевидно, эти операции могут быть небезопасными. Например, можно добавить элемент в массив childs в тот момент, когда он находится в процессе перечисления, что вызовет ошибку. И это не единственное тонкое место — изменение размеров элементов, текста, цвета и других параметров во время отрисовки могут создать проблемы в коде рендера.

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

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

За пределами приложения лежат устройства ввода-вывода, которые симулированы классами из пакета by.dev.gui.driver, а «виртуальной машиной» является простой JFrame, который получает от «видеоустройства» картинку (Image) и посылает события мыши в соответствующее «устройство» ввода.

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

Для управления буфером пикселей создан класс Texture. Ещё вы могли заметить, что GraphicsContext потерял свойство offset и теперь всегда рисует в текстуру, начиная с верхнего левого угла (это изменение будет объяснено ниже при рассмотрении алгоритмов полупрозрачности), у него появился метод fill () для монохромной заливки прямоугольной области, а также наследник SafeGraphicsContext. Дело в том, что дочерние элементы зачастую могут выходить за границы своих родителей, следовательно, их пиксели могут отрисоваться в несуществующие области текстуры. Чтобы не производить проверку попадания в границы родителя каждый раз, проще всего делать её на стороне графического контекста.

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

Пишем собственные элементы интерфейса и рисуем их

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

Окно приложения (Window) — тоже залитый прямоугольник с границей и заголовком, на котором лежит кнопка закрытия окна. Кнопка (Button) — это прямоугольная область, которая меняет цвет при нажатии на неё и посылает событие (callback) по окончанию нажатия.

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

Осталось доставить входящие в приложение события мыши (MouseEvent) до их конечных получателей — элементов интерфейса. Первым делом надо решить, кто будет управлять этой доставкой — это должен быть класс, который имеет доступ ко всей иерархии элементов, а в нашем случае это как раз рабочий стол, приложение будет сообщать ему о новом событии мыши, вызывая метод onMouseEvent, остальное будет на его совести. Что же именно он должен делать?

Сначала надо придумать алгоритм поиска элемента, который находится в точке, в которой произошло событие мыши — nodeForMouseEvent (). Для этого надо посмотреть на рабочий стол, навести мышь на какую-нибудь кнопку и подумать, как найти именно её, зная только список элементов, лежащих на рабочем столе. И также держать в голове, что в случае, если поверх вашей кнопки лежит окно, оно перехватит событие, и кнопка его не получит. Значит событие получает тот элемент, который лежит выше всех в этой точке. Учитывая порядок отрисовки дочерних элементов в методе draw (), можно понять, что чем выше индекс элемента в массиве childs, тем позже он рисуется. В том числе если положить кнопку А на кнопку В, то кнопка А является получателем события мыши только в том случае, если нажатие не попадает в область кнопки В и попадает в область кнопки А. Так как нарисованный последним элемент лежит выше всех, искать получателя события надо рекурсивно, начиная с родительского элемента в порядке убывания индекса элемента.

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

Как можно реализовать drag-n-drop для окон?

Речь идёт о механизме перетаскивания окна зажатием мыши на его заголовке. Допустим, событие нажатия мыши происходит над заголовком конкретного окна. Окно запоминает, что его в данный момент «тянут» и ждёт событий перемещения мыши и отжатия кнопки. Однако эти события дойдут до окна только в том случае, если пользователь перемещает мышь исключительно в пределах элемента, отвечающего на это событие, то есть заголовка окна. Если же мышь выйдет за его пределы, то nodeForMouseEvent уже не найдёт его как получатель события, и перемещение окна прекратится. Более того, если окно не получит события отпускания кнопки мыши, оно так и останется навсегда в режиме перетаскивания.

Ограниченные, зависимые, тривиальные — с проблемами.

Как научиться программировать с нуля и найти первую работу. Большой FAQ от Reddit

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

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

Можно придумать несколько таких механизмов, но для реализации нашей задачи (событие кнопки при отпускании мыши и перетаскивании окон) можно обойтись малой кровью — подходом с запоминанием активного элемента. Это элемент, который в данный момент эксклюзивно отвечает на события мыши. Пока он активен, рабочий стол будет направлять события исключительно ему, независимо от того, находится ли мышь над элементом или элемент в границах своего родителя. Пока активного элемента нет, рабочий стол будет предлагать событие элементу под курсором, вызывая метод doMouseEvent (). Метод должен возвращать boolean, который говорит, готов ли элемент стать активным. Как только один из элементов вернёт true, он станет активным и будет перехватывать все события мыши. Продолжаться это будет до тер пор, пока doMouseEvent () не вернёт false, что означает — элемент более не активен, и все другие опять могут получать события мыши. Этого простого и немного костыльного подхода хватит, чтобы удовлетворить наши потребности.

Создайте приложение сами!

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

Усложняем задачу и добавляем полупрозрачность

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

Всегда помните — начинаем с малого, с частного, вычленяем простые операции, задаём нужные вопросы. И главный вопрос в каждой задаче — а в чём же она состоит, что такое полупрозрачность? И опять всё сводится к одному пикселю: оригинальный пиксель, лежащий «снизу», переписывается новым пикселем, но при этом не полностью, а в какой-то степени, называемой alpha. Alpha обычно измеряется в тех же единицах, что и каналы цвета, то есть в нашем случае — числом от 0 до 255.

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

Но нам нужен универсальный и простой способ смешивания. Проще всего понять его в мысленном эксперименте. Представляем себе чёрный квадрат и кладём поверх белый, только полностью прозрачный. Чёрный квадрат останется чёрным, потому что белый не сможет на него повлиять. Теперь представляем, что alpha белого квадрата максимальна, то есть теперь он полностью перепишет чёрный цвет своим. А сейчас мысленно погоняйте в голове прозрачность от минимуму к максимуму и наоборот. Промежуточные цвета будут серыми, то есть все каналы равномерно будут перетекать от 0 к 255, выдавая в результате градиент серого, и чем больше прозрачность белого, тем более тёмный серый мы получаем. Теперь надо увидеть правило, согласно которому всё это работает. Фактически мы имеем простое параметрическое уравнение:

color = source + (dest — source) * (alpha / alphaMax);

То есть для примера с белым и чёрным цветами по каждому каналу уравнение будет выглядеть так:

red = green = blue = 0 + (255 — 0) * (alpha / 255);

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

0 + (255 — 0) * (0 / 255) = 0 (при полной прозрачности чёрный остаётся нулём);
0 + (255 — 0) * (255 / 255) = 255 (при нулевой прозрачности цвет становится белым белый).

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

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

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

Это отлично видно, если мы представим, что альфа окна равна 50%, а кнопка не прозрачна вообще. В этом случае теоретически все пиксели окна, покрытые кнопкой, в результате должны быть лишь смесью половины рабочего стола и половины кнопки, но в момент рисования кнопки эти пиксели уже «загрязнены» окном. Математически это выглядит так:

C = Cdt*(1 — Aw) + Cw * Aw;

Cw = Cw * (1 — Ab) + Cb * Ab;

C = Cdt*(1 — Aw) + (Cw * (1 — Ab) + Cb * Ab)* Aw;

Если Ab = 1 и Aw = 0.5, то

C = 0.5 * Cdt + 0.5 * Cb;

C — цвет результата, Cdt, Cw, Cb, Aw, Ab — цвета и альфа элементов (dt — desktop, w — window, b — button).

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

А что если мы полностью где-то отрисуем элемент до того, как врисовывать его в родителя? Но для этого надо иметь отрисованные дочерние элементы, а они, в свою очередь, должны знать своих детей и т. д. В этом случае сначала отрисуются элементы, лежащие в вершинах дерева иерархии, потом — их родители, а в самом конце — на рабочем столе появятся готовые отрендеренные окна. Куда же надо их рендерить? А что, если каждому элементу иметь свою текстуру такого же размера, как и он сам? Тогда задачей рисования элемента будет только рендер самого себя и отрисовка дочерних элементов. Именно поэтому из графического контекста пропало свойство отступа — оно не требуется, все элементы теперь рисуют исключительно в свою текстуру в своей же системе координат. Давайте взглянем на код!

Осталось запустить приложение!

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

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

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

Этапы разработки пользовательского интерфейса: как сделать так, чтобы UI не лишил вас прибыли

В ноябре 2018 года студия «Лайв Тайпинг» рассказывала читателям vc.ru, из чего складывается стоимость мобильного приложения. Эта статья посвящена одному из слагаемых: пользовательскому интерфейсу.

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

Пользовательский интерфейс, или UI (User Interface) — это внешний вид продукта, способ общения между пользователем и программой. А ещё интерфейс влияет на то, будет ли продукт приносить деньги и пользоваться уважением и любовью аудитории.

Доказывать важность дизайна как магнита для пользователей удобно на примере соцсетей с миллионами пользователей. Резонансным случаем в рунете стал редизайн «Кинопоиска». 96% негативных отзывов на него говорят сами за себя: владельцы сайта, компания «Яндекс», сделала это без оглядки на мнение пользователей.

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

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

Редизайн «Живого журнала» в 2014 году тоже не впечатлял. Его хорошенько почистили от лишних элементов, но в целом он не вызвал восторгов: типографика, модульная сетка, адаптив — всё выглядело сырым и неудобным.

Читать:
Hacktool win32 autokms что это

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

Итак, разберём особенности разработки пользовательского интерфейса пошагово.

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

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

Ответить на большую часть этих вопросов поможет составление портрета целевой аудитории (ЦА) — тех самых людей, для которых делается продукт.

Главная задача дизайнеров при изучении аудитории — включить эмпатию на максимум и понять, как эта аудитория думает, дышит, видит, слышит и действует. Этому способствуют следующие методы:

  • Коридорный метод. Обратная связь поступает от родных, друзей и коллег дизайнеров. Собрать её легко, но этого недостаточно.
  • Разговор с вами. Справедливо предполагается, что вы как никто знаете, что нужно вашей аудитории.
  • Полевые исследования. В рамках метода дизайнеры идут в народ: общаются с людьми напрямую, если делают продукт для местного рынка, или читают форумы, если для зарубежного;
  • Проблемное интервью. Задавая пользователям вопросы про их жизнь и место проблемы в ней, дизайнеры узнают, как эта проблема решается сейчас и насколько полезным окажется их продукт. То, что он может оказаться бесполезным — тоже ценный результат: не придётся тратить деньги на приложение, которым никто не будет пользоваться.

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

Ключевые персоны — это характерные представители ЦА. Они могут быть разными по профессии, уровню жизни, мотивации пользоваться приложением и прочим параметрам, но опыт, ожидания и страхи каждой персоны ложатся в основу внешнего вида продукта и его функциональности. Например, типичному пользователю приложения «Киноголик» для покупки абонементов в кино 23 года, он работает в ИТ-компании и любит смотреть фильмы на английском.

Такая персона становится центром user story, или пользовательской истории. Это краткий, в несколько строк, рассказ про персону и то, как она работает с функциональностью приложения и какой цели достигает. User story строится по шаблону:

«Как <роль пользователя>, я <что-то хочу получить> <с такой-то целью>»

Поместив нашего 23-летнего фаната оригинальных версий в этот шаблон, получим:

«Как <23-летний любитель версий с оригинальной озвучкой>, я <ищу через функцию ”Поиск” фильмы с субтитрами> <чтобы сходить фильм на английском>»

Компания Intercom славится не только комплексным решением по внедрению чатов в сайты и мобильные приложения, но и изобретением подхода Jobs To Be Done. В основе подхода лежит не личное качество ключевой персоны, а обстоятельства и мотивация, которые толкают персону пользоваться продуктом. «Размышления» персоны называются Job story, а шаблон выглядит так:

«Когда <я оказался в такой ситуации>, я хочу <что-то сделать по некоторым причинам> <с такой-то целью>»

Ситуация с кинолюбом в рамках такого подхода выглядит иначе:

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

Подробнее про подход Jobs To Be Done написала в своём блоге платформа Tilda.

От User story и Job story мы переходим к User scenario. Это маршрут взаимодействия пользователя с продуктом и достижения цели.

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

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

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

Будет ли вайрфрейм грубым наброском, который вы сделали с коллегами ручкой на бумаге для принтера, или созданной в графическом редакторе организованной картой экранов — решать вам. Единственное: готовьтесь объяснить клиенту, что визуально вайрфрейм не имеет отношения к финальному продукту.

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

Детализированный прототип — следующий шаг вайрфрейма по лестнице эволюции пользовательского интерфейса. Как и вайрфрейм, это макет, но чуть более конкретный: если в вайрфрейме экран с чатом состоит из окружностей и прямоугольников, только намекающих на свои назначения, то в детализированном прототипе окружность — это ТОЧНО фото пользователя, а прямоугольник — ТОЧНО текст сообщения c прикреплёнными файлами, аудиозаписями и стикерами.

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

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

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

В качестве софта для этой задачи мы используем Overflow, чей слоган «User flows done right» даёт понять, что время за этой работой пройдёт продуктивно и с пользой. В Overflow легко импортируются экраны из Sketch или Figma, а сделать flow для 100 экранов можно за час — гораздо быстрее, чем рисовать стрелочки самому.

Нужен дополнительный уровень понимания, как продукт будет работать? С помощью таких сервисов, как Marvel, InVision, POP App и Origami Studio детализированный прототип можно превратить в интерактивный. Его польза в том, что он даёт прокликать (а в случае, если у нас мобильное приложение — прокликать прямо в телефоне) все элементы интерфейса и оценить логику работы продукта до того, как он попадёт в руки конечного пользователя.

Его создание — этап необязательный, так как с презентацией будущей работы справляются макеты и user flow. Но когда нужно показать возможности мобильного приложения и раскрыть перед клиентом предстоящий объём работ в деталях, используйте его.

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

Резюмируем: вы получаете детальный прототип, его кликабельную версию (опционально) и карту экранов. Они соответствуют выработанным и согласованным в рамках этого этапа гипотезам продукта.

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

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

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

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

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

Уважающий дисциплину дизайнер уже давно оценил прелести работы в Sketch — ведь здесь так удобно вести макеты для iOS и Android в отдельных файлах, превращать в легко читаемые символы повторяющиеся элементы интерфейса, заранее присваивать таким элементам стили и отступы от краёв экрана и делать много чего ещё. Всё это складывается в дизайн-систему.

Как и в случае прототипа, готовый дизайн согласовывается с клиентом. Когда всех всё устраивает, дизайн готовится для передачи разработчикам. Сколько времени и сил можно сэкономить, сделав это грамотно посредством Zeplin (спойлер: очень много), можно узнать из нашей статьи.

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

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

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

Кнопка с целевым действием отличается от остальных элементов на экране. Чем подчеркнуть отличие — цветом, размером или формой — решать дизайнеру на этапе дизайна.

Не менее важно выбрать подходящее слово, которым нужно подписать кнопку. Будет ли это слово глаголом, существительным, прилагательным или другой частью речи, зависит от функции кнопки. Об этом писал в своём блоге Илья Бирман, а на сайте «Бюро Горбунова» он даёт общий совет.

Желаю вам делать хорошие пользовательские интерфейсы и не делать плохие. Если у вас возникли вопросы к нашему процессу или вы хотите поделиться своим подходом к разработке интерфейса — добро пожаловать в комментарии.

Python GUI: создаём простое приложение с PyQt и Qt Designer

Обложка: Python GUI: создаём простое приложение с PyQt и Qt Designer

Эта статья предназначена для тех, кто только начинает своё знакомство с созданием приложений с графическим интерфейсом (GUI) на Python. В ней мы рассмотрим основы использования PyQt в связке с Qt Designer. Шаг за шагом мы создадим простое Python GUI приложение, которое будет отображать содержимое выбранной директории.

Что нам потребуется

Нам понадобятся PyQt и Qt Designer, ну и Python, само собой.

В этой статье используется PyQt5 с Python 3, но особых различий между PyQt и PySide или их версиями для Python 2 нет.

Windows: PyQt можно скачать здесь. В комплекте с ним идёт Qt Designer.

macOS: Вы можете установить PyQt с помощью Homebrew:

$ brew install pyqt5

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

Linux: Всё нужное, вероятно, есть в репозиториях вашего дистрибутива. Qt Designer можно установить из Центра Приложений, но PyQt придётся устанавливать через терминал. Установить всё, что нам понадобится, одной командой можно, например, так:

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

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

Если вы используете Windows, то, скорее всего, путь C:\Python36\Scripts (измените 36 на вашу версию Python) не прописан в вашем PATH . Загляните в этот тред на Stack Overflow, чтобы узнать, как решить проблему.

Дизайн

Основы

Теперь, когда у нас всё готово к работе, давайте начнём с простого дизайна.

Откройте Qt Designer, где вы увидите диалог новой формы, выберите Main Window и нажмите Create.

Создание новой формы в Qt Designer

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

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

Все элементы формы и их иерархия по умолчанию отображаются в правой части окна Qt Designer под названием Object Inspector. Вы с лёгкостью можете удалять объекты, кликая по ним правой кнопкой мыши в этом окне. Или же вы можете выбрать их в основной форме и нажать клавишу DEL на клавиатуре.

Удаление ненужных элементов с формы

В итоге мы имеем почти пустую форму. Единственный оставшийся объект — centralwidget , но он нам понадобится, поэтому с ним мы ничего не будем делать.

Теперь перетащите куда-нибудь в основную форму List Widget (не List View) и Push Button из Widget Box.

Макеты

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

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

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

Предпросмотр формы, созданной без использования макетов

Теперь в меню Qt Designer нажмите Form, затем выберите Preview и увидите что-то похожее на скриншот выше. Выглядит хорошо, не так ли? Но вот что случится, когда мы изменим размер окна:

Проблема при изменении размера окна

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

Основное окно уже поддерживает макеты, поэтому нам ничего не нужно добавлять в нашу форму. Просто кликните правой кнопкой мыши по Main Window в Object Inspector и выберите Lay outLay out vertically. Также вы можете кликнуть правой кнопкой по пустой области в форме и выбрать те же опции:

Вертикальное расположение виджетов

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

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

Горизонтальное размещение двух кнопок в вертикальном

Если у вас не получается переместить элемент в главном окне, вы можете сделать это в окне Object Inspector.

Последние штрихи

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

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

Свойства элементов можно изменить в разделе Property Editor.

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

Нажмите на кнопку, которую вы добавили в форму. Теперь в Property Editor вы должны видеть все свойства этого элемента. В данный момент нас интересуют objectName и text в разделе QAbstractButton . Вы можете сворачивать разделы в Property Editor нажатием по названию раздела.

Измените значение objectName на btnBrowse и text на Выберите папку.

Должно получиться так:

Изменение названия и текста кнопки

Именем объекта списка является listWidget , что вполне подходит в данном случае.

Сохраните дизайн как design.ui в папке проекта.

Превращаем дизайн в код

Конечно, можно использовать .ui -файлы напрямую из Python-кода, однако есть и другой путь, который может показаться легче. Можно конвертировать код .ui -файла в Python-файл, который мы потом сможем импортировать и использовать. Для этого мы используем команду pyuic5 из терминала/командной строки.

Чтобы конвертировать .ui -файл в Python-файл с названием design.py , используйте следующую команду:

Пишем код

Теперь у нас есть файл design.py с нужной частью дизайна нашего приложения и мы начинать работу над созданием его логики.

Создайте файл main.py в папке, где находится design.py .

Используем дизайн

Для Python GUI приложения понадобятся следующие модули:

Также нам нужен код дизайна, который мы создали ранее, поэтому его мы тоже импортируем:

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

В этом классе мы будем взаимодействовать с элементами интерфейса, добавлять соединения и всё остальное, что нам потребуется. Но для начала нам нужно инициализировать класс при запуске кода. С этим мы разберёмся в функции main() :

И чтобы выполнить эту функцию, мы воспользуемся привычной конструкцией:

В итоге main.py выглядит таким образом:

Если запустить этот код: $ python3 main.py , то наше приложение запустится!

Запущенная программа

Но нажатие на кнопку ничего не даёт, поэтому нам придётся с этим разобраться.

Добавляем функциональность в наше Python GUI приложение

Примечание Весь дальнейший код пишется внутри класса ExampleApp .

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

Добавьте эту строку в метод __init__ класса ExampleApp , чтобы выполнить привязку при запуске приложения. А теперь взглянем на неё поближе:

  • self.btnBrowse : здесь btnBrowse — имя объекта, который мы определили в Qt Designer. self говорит само за себя и означает принадлежность к текущему классу;
  • clicked — событие, которое мы хотим привязать. У разных элементов разные события, например, у виджетов списка есть itemSelectionChanged и т.д.;
  • connect() — метод, который привязывает событие к вызову переданной функции;
  • self.browse_folder — просто функция (метод), которую мы описали в классе ExampleApp .

Для открытия диалога выбора папки мы можем использовать встроенный метод QtWidgets.QFileDialog.getExistingDirectory :

Если пользователь выберет директорию, переменной directory присвоится абсолютный путь к выбранной директории, в противном случае она будет равна None . Чтобы не выполнять код дальше, если пользователь закроет диалог, мы используем команду if directory: .

Для отображения содержимого директории нам нужно импортировать os :

И получить список содержимого следующим образом:

Для добавления элементов в listWidget мы используем метод addItem() , а для удаления всех элементов у нас есть self.listWidget.clear() .

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

Теперь, если запустить приложение, нажать на кнопку и выбрать директорию, мы увидим:

Готовое графическое приложение (Python GUI), отображающее содержимое папки со своим исходным кодом

Так выглядит весь код нашего Python GUI приложения:

Это были основы использования Qt Designer и PyQt для разработки Python GUI приложения. Теперь вы можете спокойно изменять дизайн приложения и использовать команду pyuic5 без страха потерять написанный код.

Похожие статьи