Как написать интерфейс для приложения

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

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

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

Введение

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

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

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

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

  • Интерфейс должен быть интуитивно понятным. Таким, чтобы пользователю не требовалось объяснять как им пользоваться.
  • Для упрощения процесса изучения необходима справка. Буквально — графическая подсказка, объясняющая значение того или иного ЭИ. Полное руководство должно быть частью интерфейса, доступной в любой момент.

  • Возвращайте пользователя в то место, где он закончил работу в прошлый раз. Зачем нажимать все заново?
  • Чаще всего, пользователи в интерфейсе сначала ищут сущность(существительное), а затем действие(глагол) к ней. Следуйте правилу «существительное -> глагол». Например, шрифт -> изменить.

  • Чем быстрее человек увидит результат — тем лучше. Пример — «живой» поиск, когда варианты, в процессе набора поискового запроса. Основной принцип: программа должна взаимодействовать с пользователем на основе наименьшей значимой единицы ввода.
  • Используйте квазирежимы. Например, ввод заглавных букв с зажатой клавишей shift — это квазирежим. С включенным capslock — режим. Основное отличие в том, что человек может забыть в каком режиме он находится, а в квазирежиме(с зажатой доп. клавишей) это сделать гораздо сложнее.

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

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

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

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

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

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

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

  • Форма. В большинстве случаев — прямоугольник со скругленными углами. Или круг. Полностью прямоугольные ЭИ, лично мне нравятся меньше. Возможно из-за своей «остроты». Опять же, форма как и цвет достаточно субъективна.
  • Основные ЭИ(часто используемые) должны быть выделены. Например размером или цветом.
  • Иконки в программе должны быть очевидными. Если нет — подписывайте. Ведь, по сути дела, вместо того чтобы объяснять, пиктограммы зачастую сами требуют для себя объяснений.
  • Старайтесь не делать слишком маленькие элементы — по ним очень трудно попасть.

Как правильно расположить ЭИ на экране?

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

  • Перед расположением, ЭИ следует упорядочить(сгруппировать) по значимости. Т.е. определить, какие наиболее важны, а какие — менее.
  • Обычно(но не обязательно), элементы размещаются в следующей градации: слева направо, сверху вниз. Слева в верху самые значимые элементы, справа внизу — менее. Это связано с порядком чтения текста. В случае с сенсорными экранами, самые важные элементы, располагаются в области действия больших пальцев рук.
  • Необходимо учитывать привычки пользователя. Например, если в Windows кнопка закрыть находится в правом верхнем углу, то программе аналогичную кнопку необходимо расположить там же. Т.е. интерфейс должен иметь как можно больше аналогий, с известными пользователю вещами.
  • Размещайте ЭИ поближе там, где большую часть времени находится курсор пользователя. Что бы ему не пришлось перемещать курсор, например, от одного конца экрана к другому.
  • Соблюдайте пропорции
  • Элемент интерфейса можно считать видимым, если он либо в данный момент доступен для органов восприятия человека, либо он был настолько недавно воспринят, что еще не успел выйти из кратковременной памяти. Для нормальной работы интерфейса, должны быть видимы только необходимые вещи — те, что идентифицируют части работающих систем, и те, что отображают способ, которым пользователь может взаимодействовать с устройством.
  • Делайте отступы между ЭИ равными или кратными друг-другу.

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

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

  • Если вы даете пользователю информацию, которую он должен куда-то ввести или как-то обработать, то информация должна оставаться на экране до того момента, пока человек ее не обработает. Иначе он может просто забыть.
  • Избегайте двусмысленности. Например, на фонарике есть одна кнопка. По нажатию фонарик включается, нажали еще раз — выключился. Если в фонарике перегорела лампочка, то при нажатии на кнопку не понятно, включаем мы его или нет. Поэтому, вместо одной кнопки выключателя, лучше использовать переключатель(например, checkbox с двумя позициями: «вкл.» и «выкл.»). За исключением случаев, когда состояние задачи, очевидно.

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

  • Делайте монотонные интерфейсы. Монотонный интерфейс — это интерфейс, в котором какое-то действие, можно сделать только одним способом. Такой подход обеспечит быструю привыкаемость к программе и автоматизацию действий.
  • Не стоит делать адаптивные интерфейсы, которые изменяются со временем. Так как для выполнения какой-то задачи, лучше изучать только один интерфейс, а не несколько. Пример — стартовая страница браузера Chrome.
  • Если задержки в процессе выполнения программы неизбежны или действие производимое пользователем очень значимо, важно, чтобы в интерфейсе была предусмотрена сообщающая о них обратная связь. Например, можно использовать индикатор хода выполнения задачи (status bar).
  • ЭИ должны отвечать. Если пользователь произвел клик, то ЭИ должен как-то отозваться, чтобы человек понял, что клик произошел.

В заключении

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

Литература

Джеф Раскин, «Интерфейс: новые направления в проектировании компьютерных систем»
Алан Купер, «Об интерфейсе. Основы проектирования взаимодействия»
Алан Купер, «Психбольница в руках пациентов»

#статьи

  • 25 июл 2022

  • 0

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

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

Антон Яценко

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

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

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

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

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

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

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

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

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

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

Скриншот: Tkinter

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

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

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

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

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

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

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

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

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

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

import tkinter as tk

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


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

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

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

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

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

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


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

from tkinter import *
from tkinter import messagebox

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

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

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

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

window.mainloop()

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

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

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

window.geometry('400x300')

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

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

Участвовать

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 уже не найдёт его как получатель события, и перемещение окна прекратится. Более того, если окно не получит события отпускания кнопки мыши, оно так и останется навсегда в режиме перетаскивания.

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

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

Можно придумать несколько таких механизмов, но для реализации нашей задачи (событие кнопки при отпускании мыши и перетаскивании окон) можно обойтись малой кровью — подходом с запоминанием активного элемента. Это элемент, который в данный момент эксклюзивно отвечает на события мыши. Пока он активен, рабочий стол будет направлять события исключительно ему, независимо от того, находится ли мышь над элементом или элемент в границах своего родителя. Пока активного элемента нет, рабочий стол будет предлагать событие элементу под курсором, вызывая метод 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 при каждом изменении свойств, влияющих на внешний вид элемента. Более того, это должно происходить не только с самим элементом, но и со всеми его родителями, так как их текстуры зависят от внешнего вида всех дочерних элементов.

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

Фриланс-биржа для новичков: от регистрации до первого заказа.

Interfaces are a collection of constants, methods(abstract, static, and default), and nested types. All the methods of the interface need to be defined in the class. The interface is like a Class. The interface keyword is used to declare an interface.

public interface AdapterCallBackListener {

   void onRowClick(String searchText);

}

public interface OnFragmentInteractionListener {

   void onFragmentInteraction();

}

So basically in android, there are two types of interfaces we can create and we use frequently

  • Creating Java Interface
  • Creating Kotlin Interface

So in this article, we are going to create both Java and Kotlin Interface in Android studio.

Creating Java Interface in Android Studio 

Like a class, a Interface can have methods and variables, but the methods declared in an interface are by default abstract (only method signature, no body).  

  • Interfaces specify what a class must do and not how. It is the blueprint of the class.
  • An Interface is about capabilities like a Player may be an interface and any class implementing Player must be able to (or must implement) move(). So it specifies a set of methods that the class has to implement.
  • If a class implements an interface and does not provide method bodies for all functions specified in the interface, then the class must be declared abstract.
  • A Java library example is Comparator Interface. If a class implements this interface, then it can be used to sort a collection.

Syntax:

interface <interface_name> {

   // declare constant fields

   // declare methods that abstract  

   // by default.

}

To declare an interface, use the interface keyword. It is used to provide total abstraction. That means all the methods in an interface are declared with an empty body and are public and all fields are public, static, and final by default. A class that implements an interface must implement all the methods declared in the interface. To implement interface use implements keyword.

Step by Step Implementation

Step 1: Go to Android Studio and open the project in Android mode as shown in the below image.

Step 2: Now go to the app > java > your package name > right-click > New > Java Class as shown in the below image.

Step 3: After completing step 2 a pop-up screen will arise like below. Here enter your interface name and choose the Interface and click the Enter button.

After completing the above steps successfully you can find your Java interface here. Go to the app > java > your package name > GeeksforGeeks.java. And you can write your own Java code here.

Creating Kotlin Interface in Android Studio 

Interfaces are custom types provided by Kotlin that cannot be instantiated directly. Instead, these define a form of behavior that the implementing types have to follow. With the interface, you can define a set of properties and methods, that the concrete types must follow and implement. The interface definition in Kotlin begins with the interface keyword followed by the name of the interface, followed by the curly braces within which the members of the interface reside. The difference is that the members will have no definition of their own. These definitions will be provided by the conforming types.

Example:

interface Vehicle()

{

    fun start()

    fun stop()

}

Step by Step Implementation

Step 1: Go to Android Studio and open the project in Android mode as shown in the below image.

Step 2: Now go to the app > java > your package name > right-click > New > Kotlin File/Class as shown in the below image.

Step 3: After completing step 2 a pop-up screen will arise like below. Here enter your class name and choose the Interface and click the Enter button.

After completing the above steps successfully you can find your Kotlin class here. Go to the app > java > your package name > GeeksforGeeks.kt. And you can write your own Kotlin code here.

Interfaces are a collection of constants, methods(abstract, static, and default), and nested types. All the methods of the interface need to be defined in the class. The interface is like a Class. The interface keyword is used to declare an interface.

public interface AdapterCallBackListener {

   void onRowClick(String searchText);

}

public interface OnFragmentInteractionListener {

   void onFragmentInteraction();

}

So basically in android, there are two types of interfaces we can create and we use frequently

  • Creating Java Interface
  • Creating Kotlin Interface

So in this article, we are going to create both Java and Kotlin Interface in Android studio.

Creating Java Interface in Android Studio 

Like a class, a Interface can have methods and variables, but the methods declared in an interface are by default abstract (only method signature, no body).  

  • Interfaces specify what a class must do and not how. It is the blueprint of the class.
  • An Interface is about capabilities like a Player may be an interface and any class implementing Player must be able to (or must implement) move(). So it specifies a set of methods that the class has to implement.
  • If a class implements an interface and does not provide method bodies for all functions specified in the interface, then the class must be declared abstract.
  • A Java library example is Comparator Interface. If a class implements this interface, then it can be used to sort a collection.

Syntax:

interface <interface_name> {

   // declare constant fields

   // declare methods that abstract  

   // by default.

}

To declare an interface, use the interface keyword. It is used to provide total abstraction. That means all the methods in an interface are declared with an empty body and are public and all fields are public, static, and final by default. A class that implements an interface must implement all the methods declared in the interface. To implement interface use implements keyword.

Step by Step Implementation

Step 1: Go to Android Studio and open the project in Android mode as shown in the below image.

Step 2: Now go to the app > java > your package name > right-click > New > Java Class as shown in the below image.

Step 3: After completing step 2 a pop-up screen will arise like below. Here enter your interface name and choose the Interface and click the Enter button.

After completing the above steps successfully you can find your Java interface here. Go to the app > java > your package name > GeeksforGeeks.java. And you can write your own Java code here.

Creating Kotlin Interface in Android Studio 

Interfaces are custom types provided by Kotlin that cannot be instantiated directly. Instead, these define a form of behavior that the implementing types have to follow. With the interface, you can define a set of properties and methods, that the concrete types must follow and implement. The interface definition in Kotlin begins with the interface keyword followed by the name of the interface, followed by the curly braces within which the members of the interface reside. The difference is that the members will have no definition of their own. These definitions will be provided by the conforming types.

Example:

interface Vehicle()

{

    fun start()

    fun stop()

}

Step by Step Implementation

Step 1: Go to Android Studio and open the project in Android mode as shown in the below image.

Step 2: Now go to the app > java > your package name > right-click > New > Kotlin File/Class as shown in the below image.

Step 3: After completing step 2 a pop-up screen will arise like below. Here enter your class name and choose the Interface and click the Enter button.

After completing the above steps successfully you can find your Kotlin class here. Go to the app > java > your package name > GeeksforGeeks.kt. And you can write your own Kotlin code here.

Основы создания интерфейса

Введение в создание интерфейса

Последнее обновление: 14.11.2021

Графический интерфейс пользователя представляет собой иерархию объектов android.view.View и android.view.ViewGroup.
Каждый объект ViewGroup представляет контейнер, который содержит и упорядочивает дочерние объекты View. В частности, к контейнерам относят
такие элементы, как RelativeLayout, LinearLayout, GridLayout, ConstraintLayout и ряд других.

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

Иерархия визуальных компонентов

Большинство визуальных элементов, наследующихся от класса View, такие как кнопки, текстовые поля и другие, располагаются в пакете android.widget

При определении визуального у нас есть три стратегии:

  • Создать элементы управления программно в коде java

  • Объявить элементы интерфейса в XML

  • Сочетание обоих способов — базовые элементы разметки определить в XML, а остальные добавлять во время выполнения

Сначала рассмотрим первую стратегию — определение интерейса в коде Java.

Для работы с визуальными элементами создадим новый проект. В качестве шаблона проекта выберем Empty Activity:

Empty Activity в Android Studio

Пусть он будет называться ViewsApp:

Графическое приложение в Android

И после создания проекта два основных файла, которые будут нас интересовать при создании визуального интерфейса — это класс MainActivity и определение интерфейса
для этой activity в файле activity_main.xml.

MainActivity и activity_main.xml в Android Studio

Определим в классе MainActivity простейший интерфейс:

package com.example.viewapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		
        // создание TextView
        TextView textView = new TextView(this);
        // установка текста в TextView
        textView.setText("Hello Android!");
        // установка высоты текста
        textView.setTextSize(22);
        // установка визуального интерфейса для activity
        setContentView(textView);
    }
}

При создании виджетов в коде Java применяется их конструктор, в который передается контекст данного виджета, а точнее объект android.content.Context,
в качестве которого выступает текущий класс MainActivity.

TextView textView = new TextView(this);

Здесь весь интерфейс представлен элементом TextView, которое предназначено для выводa текста. С помощью методов, которые, как правило, начинаются на
set, можно установить различные свойства TextView. Например, в данном случае метод setText() устанавливает текст в поле,
а setTextSize() задает высоту шрифта.

Для установки элемента в качестве интерфейса приложения в коде Activity вызывается метод setContentView(), в который передается визуальный элемент.

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

Интерфейс в коде java в Android Studio

Подобным образом мы можем создавать более сложные интерейсы. Например, TextView, вложенный в ConstraintLayout:

package com.example.viewapp;

import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ConstraintLayout constraintLayout = new ConstraintLayout(this);
        TextView textView = new TextView(this);
        textView.setText("Hello Android!");
        textView.setTextSize(26);
		// устанавливаем параметры размеров и расположение элемента
        ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams
                (ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT);
        // выравнивание по левому краю ConstraintLayout
		layoutParams.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID;
		// выравнивание по верхней границе ConstraintLayout
        layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
		// устанавливаем параметры для textView
        textView.setLayoutParams(layoutParams);
        // добавляем TextView в ConstraintLayout
        constraintLayout.addView(textView);
		// в качестве корневого
        setContentView(constraintLayout);
    }
}

Для каждого контейнера конкретные действия по добавлению и позиционированию в нем элемента могут отличаться. В данном случае контейнеров выступает класс ConstraintLayout,
поэтому для определения позиционирования и размеров элемента необходимо создать объект ConstraintLayout.LayoutParams. (Для LinearLayout
это соответственно будет LinearLayout.LayoutParams, а для RelativeLayout — RelativeLayout.LayoutParams и т.д.). Этот объект инициализируется двумя параметрами: шириной и высотой. Для указания ширины и высоты можно использовать константу
ViewGroup.LayoutParams.WRAP_CONTENT, которая устанавливает размеры элемента, необходимые для размещения а экране его содержимого.

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

layoutParams.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID;

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

А строка кода

layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;

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

Для установки всех этих значений для конкретного элемента (TextView) в его метод setLayoutParams() передается объект ViewGroup.LayoutParams (или один из его наследников,
например, ConstraintLayout.LayoutParams).

textView.setLayoutParams(layoutParams);

Все классы контейнеров, которые наследуются от android.view.ViewGroup (RelativeLayout, LinearLayout, GridLayout, ConstraintLayout и т.д.),
имеют метод void addView(android.view.View child), который позволяет добавить в контейнер другой элемент — обычный виджет типа TextView или другой контейнер.
И в данном случае посредством данного метода TextView добавляется в ConstraintLayout:

constraintLayout.addView(textView);

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

  • Создание объекта ViewGroup.LayoutParams и установка его свойств

  • Передача объекта ViewGroup.LayoutParams в метод setLayoutParams() элемента

  • Передача элемента для добавления в метод addView() объекта контейнера

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

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