Класс Canvas
Класс android.graphics.Canvas (Холст) предоставляет методы для рисования, которые отображают графические примитивы на исходном растровом изображении. При этом надо сначала подготовить кисть (класс Paint), который позволяет указывать, как именно графические примитивы должны отображаться на растровом изображении (цвет, обводка, стиль, сглаживание шрифта и т.д.).
Android поддерживает полупрозрачность, градиентные заливки, округлённые прямоугольники и сглаживание. Из-за ограниченных ресурсов векторная графика пока что не поддерживается, вместо этого используется традиционная растровая перерисовка.
Canvas работает с пикселями, поэтому следует заботиться о конвертации единиц dp в px и наоборот при необходимости. Начало координат находится в левом верхнем углу.
Получить доступ к холсту можно через объект Bitmap или компонент View. Очень часто разработчики создают свой собственный компонент, наследуясь от View, и рисуют на его холсте для реализации своих замыслов.
Методы
Ниже представлены некоторые методы класса Canvas, которые что-то рисуют.
- drawARGB()/drawRGB()/drawColor(). Заполняет холст сплошным цветом.
- drawArc(). Рисует дугу между двумя углами внутри заданной прямоугольной области.
- drawBitmap(). Рисует растровое изображение на холсте. Вы можете изменять внешний вид целевой картинки, указывая итоговый размер или используя матрицу для преобразования.
- drawBitmapMesh(). Рисует изображение с использованием сетки, с помощью которой можно управлять отображением итоговой картинки, перемещая точки внутри неё.
- drawCircle(). Рисует круг/окружность с определённым радиусом вокруг заданной точки.
- drawLine(s)(). Рисует линию (или последовательность линий) между двумя точками.
- drawOval(). Рисует овал на основе прямоугольной области.
- drawPaint(). Закрашивает весь холст с помощью заданного объекта Paint.
- drawPath(). Рисует указанный контур, используется для хранения набора графических примитивов в виде единого объекта.
- drawPicture(). Рисует объект Picture внутри заданного прямоугольника.
- drawPoint(). Рисует точку в заданном месте.
- drawPosText(). Рисует текстовую строку, учитывая смещение для каждого символа.
- drawRect(). Рисует прямоугольник.
- drawRoundRect(). Рисует прямоугольник с закруглёнными углами.
- drawText(). Рисует текстовую строку на холсте. Шрифт, размер, цвет и свойства отображения текста задаются в соответствующем объекте Paint.
- drawTextOnPath(). Рисует текст, который отображается вокруг определённого контура.
- drawVertices(). Рисует набор треугольников в виде совокупности вершинных (вертексных) точек.
- rotate() и restore(). Вращение холста
- Методы scale() и translate(). Изменение и перемещение координатной системы
Мы уже изучали основы рисования в первом месяце обучения (Работаем с графикой. Основы). Можно вернуться к этому проекту, закомментировать код вывода графики и продолжить изучение рисования при помощи методов класса Canvas.
Метод drawArc()
В API 21 появилась перегруженная версия метода, в котором можно указать координаты двух точек вместо RectF.
Метод drawArc() позволяет рисовать дуги и сектора. Ниже приводится код для трёх вариантов: сектор с заливкой (похож на PacMan), сектор без заливки (контур) и часть дуги:
Метод drawBitmap()
Вывести готовое изображение просто.
Метод drawCircle()
Первые два аргумента определяют координаты центра окружности/круга, следующий аргумент — её радиус в пикселах, последний — объект Paint. В зависимости от выбранного стиля кисти можно нарисовать закрашенный круг или только окружность.
Нарисуем зелёный круг.
drawLine(s)()
Простой метод — указываем начальные и конечные координаты отрезка.
Метод drawOval()
Метод drawOval() рисует овалы. Естественно, если вы зададите одинаковые размеры ширины и высоты, то получите круг/окружность.
Если вам нужно наклонить овал в ту или иную сторону, то поверните холст на требуемый угол с помощью метода rotate(). Не забудьте потом повернуть холст обратно, что следующие фигуры выводились нормально.
Повернём синий овал из предыдущего примера:
В API 21 появилась перегруженная версия метода, в котором можно указать координаты двух точек вместо RectF:
Метод drawPaint()
Метод позволяет закрасить весь холст одним цветом.
Метод drawRect()
У метода существует три перегруженные версии для рисования прямоугольника. Рассмотрим один из них:
Метод drawRoundRect()
Для рисования прямоугольников с закруглёнными углами используется метод drawRoundRect (RectF rect, float rx, float ry, Paint paint).
В параметрах указываются ограничивающий прямоугольник, радиусы овалов для скругления углов и кисть.
Реализуем три разных способа:
В API 21 появилась перегруженная версия метода, в котором можно указать координаты двух точек вместо RectF.
Метод drawPath()
Для рисования соединённых отрезков можно использовать метод drawPath(), указав в параметрах настройки для рисования и массив координат точек. Для удобства добавим в класс Draw2D новый класс Pt, который позволит быстро создать массив точек с заданными координатами. Далее настраиваем объекты для рисования и формируем путь через созданный массив. В результате получим кошкин дом.
Путь можно составлять не только из точек, но и из фигур, например, дуг. Сначала формируем дугу, добавляем её в путь при помощи метода Path.addArc(), повторяем операцию снова несколько раз, а в конце выводим окончательный вариант:
Можно нарисовать символ парашюта:
Метод drawPoint()
Простой метод для рисования точки в нужно месте указанной кистью. Для координат используются значения типа float.
Метод drawText()
С помощью метода drawText() можно выводить текст в заданной позиции. Добавим сначала несколько эффектов, чтобы казалось, что текст парит над поверхностью:
Центрируем текст
Есть небольшая тонкость, если вам захочется вывести текст в центре холста. Проблем с вычислением центра холста и размером текста нет. Центр можно найти, разделив пополам значения ширины и высоты холста. А ширину и высоту текста можно узнать через метод кисти getTextBounds(), который возвращает ограничивающий прямоугольник.
Но вычисление ширины текста через textBounds.width(); приводит к небольшому смещению. Лучше воспользоваться методом кисти measureText(). Тогда текст отцентрируется точнее.
Пример на Kotlin с дополнительной информацией.
Методы rotate() и restore()
Холст во время рисования можно вращать. Во многих ситуациях такой приём менее затратный по ресурсам, чем рисование самого объекта под углом. Суть в следующем: вы поворачиваете холст на нужный градус, рисуете фигуру, а затем возвращаете холст на место при помощи метода restore(), чтобы следующие фигуры рисовались в ожидаемых местах. Иначе остальные фигуры будут рисоваться уже относительно поворота.
В примере с овалом уже использовался данный метод. В примере Работаем с графикой. Основы мы также поворачивали холст, чтобы вывести текст под углом.
Вращение происходит вокруг начальной точки холста (0, 0). Но можно также использовать перегруженную версию метода rotate(float degrees, float px, float py), в которой можно указать координаты точки поворота.
Методы scale() и translate()
Стандартная система координат начинает свой отсчёт с верхнего левого угла. Иногда, для рисования сложных фигур удобнее назначить свою систему координат. Например, для рисования циферблата часов удобнее рисовать относительно центра экрана в диапазоне от -1 до 1.
Чтобы установить свою систему координат, нужно произвести трансформацию. В следующем примере мы установим координаты в диапазоне от 0 до 10 и нарисуем график в стандартном виде из точки 0,0 в левом нижнем углу в точку 10,10 в верхнем правом углу.
Для наглядности я добавил на оси несколько точек. Следует обратить внимание, что мы задали диапазон от 0 до 10 и все размеры должны масштабироваться в новых величинах, в том числе и ширина обводки в методе setStrokeWidth(). Поэтому значения должны быть достаточно маленькими, иначе толщина обводки может просто оказаться больше самой фигуры. Кстати, в некоторых случаях с текстом и другими методами рисование масштабирование может сыграть злую шутку и дробные значения не позволят увидеть текст и некоторые линии. В этих случаях приходиться создавать цепочку преобразований, когда временно масштаб увеличивается до нормальных размеров, рисуется текст с подходящим размером шрифта, затем опять всё уменьшается и т.д. Это долгая история.
Источник
Полный список
— читаем Bitmap
— выводим его на канву
— получаем информацию о нем
Начинаем тему Bitmap. Без нее в рисовании никуда, т.к. Bitmap – это объект, который хранит в себе изображение. Та же канва, с которой мы обычно работаем, это обертка, которая принимает команды от нас и рисует их на Bitmap, который мы видим в результате.
Мы рассмотрим все основные операции с Bitmap и обязательно разберем интересные материалы с официального сайта по этой теме.
В этом уроке начнем с основ. Посмотрим какие методы есть для создания Bitmap из файла, как вывести его на канву и какую инфу о себе может рассказать Bitmap.
Для получения изображения из файла используется фабрика BitmapFactory. У нее есть несколько decode* методов, которые принимают на вход массив байтов, путь к файлу, поток, файловый дескриптор или идентификатор ресурса. И на выходе мы получаем Bitmap.
Можно заметить, что все эти методы имеют также версии с использованием объекта BitmapFactory.Options. Это очень полезная штука, о ней мы отдельно еще поговорим в одном из следующих уроков.
Самые используемые из методов чтения это, конечно, следующие:
decodeFile(String pathName) – получить Bitmap из файла, указав его путь. Т.е. этим методом можем считать картинку с SD-карты. (getExternalStorageDirectory)
decodeResource(Resources res, int id) – получить Bitmap из drawable-ресурса, указав его ID. Этот метод вернет нам картинку из папок res/drawable нашего приложения.
Project name: P1571_BitmapRead
Build Target: Android 4.4
Application name: BitmapRead
Package name: ru.startandroid.develop.p1571bitmapread
Create Activity: MainActivity
В конструкторе DrawView мы получаем Bitmap из drawable-ресурса ic_launcher. На вход методу decodeResource мы передали объект ресурсов и ID требуемого ресурса.
Далее в переменную info сформируем строку с инфой о картинке:
getWidth – ширина картинки в px
getHeight – высота картинки в px
getByteCount – число байт, которое занимает картинка (доступен только с API Level 12)
getRowBytes – число байт в одной строке картинки
getConfig – инфа о способе хранения данных о пикселах
И выводим эту инфу в лог.
Настраиваем матрицу, которая повернет картинку на 45 градусов, растянет картинку в два раза в ширину и в три раза в высоту, и переместит ее на 200 вправо и 50 вниз.
Создаем два Rect объекта. rectSrc со сторонами равными половине сторон картинки. Т.е. этот прямоугольник охватывает левую верхнюю четверть картинки. Эту часть мы будем брать для вывода на экран далее в примере. А выводить мы ее будем в прямоугольник rectDst, это просто произвольная область на экране.
В методе onDraw рисуем картинку на канве тремя разными версиями метода drawBitmap. В первом случае просто выводим картинку как есть в точке (50,50). Во втором применяем матрицу, в которой мы уже настроили поворот, трансформацию и перемещение. И третий вариант возьмет от картинки часть, входящую в область rectSrc (мы там задали левую верхнюю четверть) и нарисует ее на канве в области rectDst, применив необходимые трансформации и перемещения.
Слева-направо видим все три варианта вывода. В первом случае без изменений и в указанной точке. Во втором случае преобразования были описаны в матрице. В третьем случае мы отсекли от картинки часть и нарисовали ее в указанной области, канва сама при этом растянула изображение под размеры области.
Info: size = 48 x 48, bytes = 9216 (192), config = ARGB_8888
Размер картинки = 48 (ширина) на 48 (высота). У вас тут могут быть другие цифры, т.к. метод decodeResource учитывает density устройства и вытаскивает картинку из необходимой папки. В моем случае он взял ее из drawable-mdpi.
Далее мы вывели вес картинки в байтах — 9216, и кол-во байтов в одной строке — 192. Тут понятно, что вес картинки = кол-во байтов в строке * высоту = 192 * 48 = 9126.
А если мы разделим кол-во байтов строки на ширину, то получим сколько байтов занимает один пиксел: 192 / 48 = 4 байта.
Это же подтверждает и config = ARGB_8888. Это означает, что на каждый из 4-х ARGB-компонентов пиксела (альфа, красный, зеленый, голубой) выделяется по 8 бит (= 1 байт). Следовательно, пиксел будет весить 4 байта.
Кроме ARGB_8888 есть еще несколько конфигураций:
ALPHA_8 – пиксел содержит в себе инфу только о прозрачности, о цвете здесь инфы нет. Каждый пиксел требует 8 бит (1 байт).
ARGB_4444 — аналог ARGB_8888, только каждому ARGB-компоненту отведено не по 8, а по 4 бита. Соответственно пиксел весит 16 бит (2 байта). С API Level 13 эта конфигурация объявлена устаревшей.
RGB_565 – здесь нет инфы о прозрачности, а трем RGB-компонентам выделено, соответственно по 5,6 и 5 бит. Каждый пиксел будет весить 16 бит или 2 байта.
Все вышенаписанное — это достаточно важные вещи, которые надо понимать и учитывать в разработке. Если, например, ваше приложение работает с картинками, и вы точно знаете, что они будут без прозрачности, то лучше использовать RGB_565. Все ваши картинки в памяти займут в два раза меньше места, чем при использовании дефолтового ARGB_8888. При большом кол-ве картинок это существенная оптимизация!
На размер (а следовательно и вес) изображения также следует обращать внимание. Следите, чтобы ваши картинки в памяти не были размера больше, чем вам нужно. Приведу пример из практики. Как-то пришлось оптимизировать приложение, в котором был экран со списком юзеров с аватарками. Аватарки эти изначально грузились с сайта и кешировались на SD. Для списка использовался memory-кэш, но он переполнялся мгновенно и постоянно подчитывал инфу с SD.
Вскрытие показало, что с сайта картинки грузились в разрешении 200 х 200 и прямо так и сохранялись на SD. В memory-кэш они помещались в том же разрешении, занимая, соответственно, по 200 * 200 * 4 = 160 000 байт каждая! Т.е. 6 картинок в кэше и уже метр памяти занят. А список там на сотни позиций. Конечно, никакого кэша не хватит при скроллинге.
Глянули на layout строки списка. Каждый ImageView, который отображал аватарку, был размером всего 32х32 dp. Т.е. в случае mdpi нам требовалась картинка 32х32 пиксела. Т.е. 32 * 32 * 4 = 4096 байт. Получается, что вместо одной аватарки 200х200 в кэше свободно могли бы разместиться почти 40 аватарок 32х32.
В итоге, при чтении картинок с сайта и сохранении их на SD поставили сразу изменение размера до необходимого, и стало значительно лучше. Еще как вариант, у сайта сразу просить требуемый размер картинки.
В общем, старайтесь использовать минимально-требуемый вам формат и размер. А о том как выбрать формат, поменять размер и использовать кэши мы обязательно поговорим в следующих уроках.
На следующем уроке:
— создаем и меняем Bitmap
— разбираемся с density и mutable
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник