Как реализовать метод View.onDraw()?

Посмотреть в Telegram: @AndroidSobes/76
Метод onDraw(canvas: Canvas) часто требуется переопределять при создании кастомной view.
Canvas и Paint – это два основных объекта, которые используются при реализации метода onDraw().

Canvas передается как параметр метода onDraw() и отвечает за то, что рисовать. Canvas определяет фигуры, которые будут нарисованы в методе onDraw(). Для этого в классе Canvas есть методы draw...(). Большинство методов draw...() принимают параметром объект Paint.

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

Метод onDraw() вызывается часто и скорость отработки этого метода влияет на то, как быстро будет отрисовываться UI пользователя. Важное правило реализации onDraw() – не создавать в этом методе новые объекты. Это значит, что все объекты Paint, которые используются в onDraw(), должны быть созданы до вызова onDraw(), например при создании view.
Для правильной отрисовки view, необходимо учитывать ее размер. Иногда требуется выполнять сложные вычисления размеров и позиций отдельных частей, основываясь на размере view.
Как было сказано в предыдущем посте, для метода onDraw() важна скорость отработки, поэтому хорошей практикой считается не производить вычислений в этом методе.

Для обработки изменения размера view переопределяется метод onSizeChanged(). Этот метод вызывается, когда у view первый раз выставляется размер, и потом каждый раз когда размер меняется, что значительно реже, чем вызовы метода onDraw(). Аргументы метода onSizeChanged() – четыре целых числа, текущие и предыдущие значения ширины и высоты. При первом вызове значения предыдущих ширины и высоты равны нулю.
Размеры, которые передаются в onSizeChanged() включают паддинги. Это значит, что для вычисления реального размера контента view нужно их вычесть. Для получения значений паддингов используются методы getPadding...().

Все вычисления размеров следует выполнять в onSizeChanged() и потом использовать полученные результаты в onDraw().
После определения размеров и позиций составных частей view, приступаем к рисованию. Для каждой view метод onDraw() реализуется по-разному, но есть общие группы операций на объектах Canvas и Paint, которые используются при рисовании:

• Текст можно нарисовать методом Canvas.drawText(), который принимает рисуемый текст, координаты, где его нарисовать, и объект Paint. С помощью Paint задаются параметры текста, например шрифт методом setTypeface() и цвет методом setColor().

Canvas имеет методы для рисования простых геометрических фигур, такие как drawLine(), drawOval(), drawRect(). С помощью объекта Paint можно указать закрашивать фигуры или нет. Для этого используется метод setStyle().

• Для рисования сложных фигур используется объект Path, который содержит методы для добавления простых составляющих фигур, такие как lineTo(), addOval(), addRect(). Созданная фигура рисуется методом drawPath().

• Для закрашивания фигур градиентом используется метод Paint.setShader(), в который передается объект LinearGradient.
При разговоре о реализации метода onDraw() речь часто заходит об оптимизациях этого метода и других вещей, связанных с UI.

Не следует выполнять вычисления и создавать объекты в методе onDraw(). Создание новых объектов может спровоцировать сборку мусора, что приводит к Stop the world паузам и лагам пользовательского UI. Создавайте объекты Paint при инициализации view и переиспользуйте их в методе onDraw(). Также избегайте создания объектов во время отработки анимаций.

Другой способ оптимизировать UI – уменьшить количество вызовов метода onDraw(). Большинство вызовов onDraw() является результатом отработки invalidate(), поэтому старайтесь избегать частого вызова этого метода.

Еще одна дорогая операция – обход view-иерархии. При отработке метода requestLayout() система проходит по всей иерархии, чтобы определить размеры каждой из view. Для некоторых лэйаутов проход выполняется несколько раз, что вызывает проблемы с производительностью.
Для создания плавного UI необходимо уменьшать вложенность вью насколько это возможно. Старайтесь создавать кастомные плоские view, вместо нескольких вложенных стандартных лэйаутов. Для не универсальных view проще вычислять размеры, основываясь на специфике их использования в приложении.