- Класс Canvas
- Методы
- Метод drawArc()
- Метод drawBitmap()
- Метод drawCircle()
- drawLine(s)()
- Метод drawOval()
- Метод drawPaint()
- Метод drawRect()
- Метод drawRoundRect()
- Метод drawPath()
- Метод drawPoint()
- Метод drawText()
- Центрируем текст
- Методы rotate() и restore()
- Методы scale() и translate()
- Android Developers Guide To Custom Canvas Drawing
- What is the Android Canvas?
- What backs the Android Canvas?
- Where to find the Canvas?
- What alternatives are there to drawing on the android canvas?
- Bitmap Drawable
- Nine patch
- How do I create a nine patch?
- Layer List
- State List
- Level List
- Transition
- Inset
- Clip Drawable
- Scale Drawable
- Shape Drawable
- Vector Drawables
- Where will you interact with an android canvas as a developer?
- When does it make sense to use an android canvas?
- Where can I go to see the list of possible android canvas API methods?
- Getting Started with the android canvas
- What is the difference between “px”, “dip”, “dp” and “sp”? AKA units of measure?
- What is a px?
- What is a dp?
- What about sp?
- Converting Between Units of Measure
- Why does the Android Canvas API use both ints and floats?
- Drawing Order
- Modifying the Canvas
- What Canvas State Properties Can be Saved?
- What is a matrix?
- Creating a Canvas
- Colors
- Int Colors
- Long Colors
- Color instances
- What is alpha?
- Color Management
- Drawing on the Canvas
- Clipping the Canvas
- Filling the canvas with a color
- Drawing Shapes
- Text Drawing
- What is the difference between getTextBounds() and measureText()?
- What do all of the different FontMetrics mean?
- Porter Duff Modes
- Blend Modes
- Color Filters
- What if I can’t find what I’m looking for in the Canvas documentation?
- Performance
- Avoid Overdraw
- Avoid Repetitive Calculations
- How to use the Picture
- Debugging Tips
- Why is my draw call not showing up on the canvas? Common reasons:
- How to center my text on a canvas?
- What’s next?
Класс 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(). Поэтому значения должны быть достаточно маленькими, иначе толщина обводки может просто оказаться больше самой фигуры. Кстати, в некоторых случаях с текстом и другими методами рисование масштабирование может сыграть злую шутку и дробные значения не позволят увидеть текст и некоторые линии. В этих случаях приходиться создавать цепочку преобразований, когда временно масштаб увеличивается до нормальных размеров, рисуется текст с подходящим размером шрифта, затем опять всё уменьшается и т.д. Это долгая история.
Источник
Android Developers Guide To Custom Canvas Drawing
The android canvas is at the heart of almost all views within the android platform.
As an android developer a designer may give you a gorgeous looking screen to create.
Depending on the project they may push the bounds in terms of what you need to create.
At some point the standard Android widgets may not be enough for you to work with. You’ll need to make your own.
A big portion of creating your own widget will be drawing on the android canvas.
For example, let’s say you are working on a financial app. A designer might hand you an image of a screen that has this graph in it:
Since none of the built-in views will help you with this you’ll either need to find a library or you’ll need to create your own.
Sometimes even 3rd party libraries don’t cover what we need.
Part of the process of creating a custom view is drawing on the screen. This guide will focus on 2d screen drawing.
As you can tell this guide is long.
You may want to take this guide with you:
What is the Android Canvas?
The Android Canvas is a class in Android that is used for two-dimensional drawing. If you were to take a look at the source code for views such as the TextView you’d see that they ultimately perform actions on an instance of a Canvas object.
It’s good to imagine the canvas as a real-life canvas. It’s good for you to think about an artist’s canvas. It’s essentially blank when you first start and you subsequently add layers of paint or drawings to it. Gradually you build up a complete image.
All canvases actually contain an underlying bitmap and the operations you perform on the cameras get translated into pixels on the bitmap.
What backs the Android Canvas?
The Android canvas is actually backed by a very powerful library called Skia. Skia is available for anyone to use under the Free BSD Software License. Skia is what serves as the graphics engine for Android as well as other things such as chrome, chrome os, firefox, and firefox os.
Where to find the Canvas?
There are two common places to find the canvas one is when you’re creating a drawable in the other is when you are creating a Custom View. In both of these cases you aren’t creating the canvas itself but rather the operating system is passing the canvas into you and all you have to do is perform actions either with or on the canvas itself.
What alternatives are there to drawing on the android canvas?
There are a lot of alternatives to the canvas itself, for instance, there’s a lot of predefined Drawables. These Drawables allow you to perform a common set of operations so you don’t have to do custom canvas programming all the time.
They allow you to more easily create graphical objects that get displayed on the screen. It’s good to think about what your use cases in trying to determine which drawable—if any—is appropriate for what you need to accomplish.
Bitmap Drawable
The bitmap drawable is simply a bitmap that wraps a bitmap. A bitmap image is simply just a collection of colored pixels in a specific order. The Bitmap Drawable has some nice features for tiling and stretching images.
Nine patch
The patch 9 drawable is intended to create a drawable that will scale well across different dimensions it’s very useful for buttons in particular. However, it is still ultimately a bitmap underlying the implementation it just has some additional data that indicates which portions of the image are repeatable allowing it to stretch.
The SVG drawable may negate the need for a nine-patch and possibly be smaller in file size depending on your use case.
How do I create a nine patch?
In the event you need to create a nine patch Android Studio has a tool to help you. Simple right click on the png/jpg image and select the create nine patch file option.
Then double click on the resulting nine patch file to edit it. The editor will show the raw image on the left and stretched versions on the right.
From there click on the margins to define what parts of the patch nine are stretchable.
Notice how the stretched samples no longer have fuzzy corners? That’s because it now knows which segments of the image are not stretchable.
Layer List
The layer list drawable is essentially just an array of Drawables that are drawn in a specific order on top of the other.
State List
The stateless drawable is essentially just that. It’s a Drawable that contains different state Drawables. The current state reflects what will be shown to the user.
Imagine you have a checkbox. You wanted the checkbox to display differently for all the different states. You want one state to be the unchecked state. You want one active state when it is being pressed by a finger. You want one when it is checked. You want to display all of these states differently to the user so they have an accurate understanding of the current state of the widget.
Level List
The level is durable is kind of like a combination of the layer list drawable and the State list drawable. There are a number of layers in the level list but at the same time depending on the state, in this case, the level that determines which layer should be shown.
Transition
The transition drawable can be thought of as more of an animation. But for sake of completeness, I’m including it here and all it does is Crossfade between two different drawable resources.
Inset
The inset drawable is essentially a cropped version of a drawable. Imagine that you have this really big image and you only need a very tiny portion of it for your background. The inset drawable will be useful in this case to capture that really tiny section that you need to use.
Clip Drawable
The clip drawable clips another drawable based on the clips drawable level.
If you’ve ever wondered how to display an ImageView progressively down from top to bottom this is one way of doing it.
It’s good for showing progress. For example we could show battery status this way. Clipping the image to show less and less:
Scale Drawable
The scale drawable is used to change the size of another drawable’s size based on the level value.
Shape Drawable
The shape drawable is used to draw simple shapes. There are four defined shapes a rectangle, an oval, a line, and a ring.
Gradients, The Shape drawable also supports gradients and isn’t limited to just solid colors. Additionally, the radius of a corner can be specified for a rectangle.
Padding can also be specified. Note the padding applies to the view’s contents and not the shape itself.
Size can also be specified of the shape.
Solid colors instead of gradients can be used.
Stroke is used to add a different color to the edge of the shape. You can control the width, color, dash and space between dashes with the stroke.
Vector Drawables
Vector drawable is different from a bitmap drawable in that it doesn’t contain pixel data. Instead, it contains a series of instructions about shapes, placements, and colors. Since data isn’t being stored for every single pixel vector Drawables are smaller, more performant, and can scale.
You can also create animations using multiple xml files instead of multiple image files.
Where will you interact with an android canvas as a developer?
There two common places where you will find a canvas as an android developer. The first is in a custom view the second is in a custom drawable. In both cases, you do not need to create the canvas yourself.
Instead, the onDraw method that both the View and the Drawable classes implement will be called with a canvas argument. You will then use the canvas to perform drawing actions gradually building up the desired image.
When does it make sense to use an android canvas?
This question gets a little more tricky but it really comes down to the amount of customization your use case requires.
If you need a static image, for example, it doesn’t make much sense to go through all the work of creating a custom drawable just to display the image.
However, let’s say you are working on a stock trading app. The design calls for creating a really interactive graph. Let’s go on to say the designer mocked up that you could scroll through the timeline of the graph, and that touching certain points would reveal the stock price at that point.
It becomes pretty obvious that you are going to need something custom. A static image isn’t going to fulfill the requirements.
Where can I go to see the list of possible android canvas API methods?
The android developer documentation has a list of all of the methods the canvas api has. However, it’s worth noting that sometimes other class take the canvas as an argument and perform their own drawing operations. So if you can’t find an operation you are looking for it could be a result of it being located elsewhere (usually under the android.graphics package namespace).
For example, let’s say you were trying to draw a nine-patch on the canvas. The Canvas class does not have a drawPatchNine method. Instead, the nine-patch object has a draw method that accepts a canvas object.
Getting Started with the android canvas
The first gotcha is that the coordinate system is different than you’re probably used to. Your first inclination might be to think of the cartesian coordinate system. But the Android canvas doesn’t use the cartesian coordinate system.
For reference the cartesian coordinate system looks like this:
However, on Android the coordinate system looks like this:
That means for every drawing action you have to be careful with the y-axis value. In fact, any time that you are drawing you must ensure that your actions take place inside of the bounds of the canvas.
It’s not disastrous if you draw outside of the bounds. It’s not like your app will crash. However, it’s wasteful since you are performing useless actions that simply aren’t going to show up.
What is the difference between “px”, “dip”, “dp” and “sp”? AKA units of measure?
What is a px?
Px stands for pixel. From the android developer documentation:
Measuring everything in pixels becomes problematic because two phones that are physically the same size might have a different number of pixels on the screen. One phone might have a higher density (more pixels) than another.
Below are two grids that are the same total size, but one has smaller subsections. This is how phones displays are. Some displays have more pixels in the same size space.
If we don’t account for this difference in density then the drawing will not be consistent between devices. This will lead to a bad user experience.
What is a dp?
Dp or density-independent pixel was introduced to solve this pixel problem.
In order to provide consistency between devices, a new unit of measurement was introduced called dp, or dip. Which refers to density-independent pixel.
From the google documentation:
This means that if we use dp instead of px our drawing operations will appear consistent across different densities.
Here’s the difference between 50px and 50 dp:
What about sp?
Scale independent pixel is like dip but it factors in the user’s font size preferences. This is useful for accessibility.
From the google documentation:
If the user’s font settings are set at the medium setting then there is no apparent difference.
If the user changes their font size then 50 sp can be different between two otherwise identical devices as shown below:
Converting Between Units of Measure
The issue arises that all of the canvas APIs accept either integers or float pixel values. This means that the responsibility falls to you as a developer to first come up with these values in dp and then convert from dp or sp.
If you are working in a custom view it’s easy to do the conversion with a context. You simply need to get ahold of the display metrics. Then you can use the TypedValue.applyDimension to convert between types.
In a custom view this can be done like so:
However, if you are in a custom drawable then you don’t have access to resources. Fortunately, display metrics are still accessible to you.
Making the code become:
Why does the Android Canvas API use both ints and floats?
When you start using the canvas APIs you might notice that there are often methods that appear to be duplicate with on minor change and that being that instead of ints one takes floats. In particular, these int and float arguments are for positions.
So why does the canvas have both?
Ints are for representing specific whole pixels:
Floats, on the other hand, can represent fractions of a pixel.
But that brings up the question if a pixel is the smallest physical portion of an image what good is a fraction of one? And how is that even useful?
It’s useful because we can approximate a subpixel by doing some math and calculating what a color the pixel should be based on how much it belongs to it’s closest neighbors.
This might not appear like much of a difference up close, but it can make a huge difference in the overall appearance when not zoomed in so closely.
Here’s what two circles and fonts look side by side with and without antialiasing:
This is, in fact, a form of anti-aliasing. The goal of Anti-aliasing is to get rid of jagged edges on your screen because of squares not being able to perfectly simulate non-rectangular shape.
Drawing Order
When working with the canvas it’s important to think about the order in which you need to draw. If you’ve ever worked with drawing programs like photoshop or worked on webpages the same concept of layers or z indexes applies.
Except, the canvas API doesn’t have layers. Instead, you must simulate layers by drawing in a very particular order.
If you aren’t careful then you will draw over an existing portion of your image.
It’s the same if you were painting on a real canvas. Imagine you were painting a canvas. Like, Bob Ross does.
First, you start out with the background objects, and you gradually work your way forward.
When a designer hands you a comp of a custom widget the same process applies.
Work from the background to the foreground.
Modifying the Canvas
Sometimes you want to perform alterations to the canvas itself (as opposed to drawing the canvas).
For example, let’s say that you are drawing a clock widget and you want to add tick marks around the edge of the clock. It becomes very tedious to do the math for all of the necessary paths.
Instead, it becomes easier if you simply draw vertical lines and rotate the canvas.
These actions are very useful. However, it would be tedious to remember all of the actions you performed on the canvas.
Saving and Restoring Canvas State
Fortunately, the Android APIs have two helper methods for keeping track. One is for adding a history item and the other is for reverting to a previous history state.
The first method for recording state is called save. The other for restoring state is called restore.
What Canvas State Properties Can be Saved?
The save method stores the current canvas info such as position, rotation, skew and clip, and the restore method undoes the current state restoring it to the previous state. It’s important to note that this doesn’t save drawing calls, but merely where the canvas is as opposed to what the canvas has on it.
These two functions free you from having to manually track all those canvas modifications.
What is a matrix?
The docs say that the canvas has a transformation matrix.
This matrix tracks things like scale, skew, and translation (aka position). By updating the matrix you can update these values to change the canvas appearance.
Beyond these three items clipping is also stored.
You can apply modifications to the Canvas directly using either the named methods:
Or via the Canvas.concat(matrix: Matrix) method.
Creating a Canvas
Besides having a canvas created for you via a CustomView or a Drawable. You can also create one yourself. This can come in handy if you need to generate a bitmap or pdf file.
Creating a PDF from a canvas
In order to create a Canvas you need a bitmap. Once you have your canvas you can perform your drawing operations onto the canvas.
From there you can use the PDF APIs to generate a pdf from the canvas like so:
Colors
Colors are represented in three different ways.
Int Colors
The first is as a 32 bit integer (8 bits for alpha, red, green, and blue each). The four components of a color int are encoded as follows:
Long Colors
Longs are similar to int colors except they are supposed to contain a color space and more precision. To be more specific some of the color spaces call for each color to be stored in 16bits instead of 8. This is why the color is said to be more precise. Additionally, the alpha channel is stored in 10 bits in some of the color spaces instead of just 8. We’ll go into what alpha means in this context in a bit.
Color instances
Color instances are used to store colors in a different color space, with even more precision than both ints, and longs.
Currently, the Canvas and Paint APIs only support the int, and long API colors. But support for wider color space (more precision) is supposed to be coming in the future.
What is alpha?
Alpha has to do with opacity. Fully opaque means the color is completely painted, fully transparent means nothing is painted.
Let’s say you have this image with this background.
Let’s start with the lowest alpha value (0) meaning it’s completely invisible. As we gradually decrease the opacity you can see the image shows more and more until it’s fully visible.
Let’s start with the lowest alpha value (0) meaning it’s completely invisible. As we gradually decrease the opacity you can see the image shows more and more until it’s fully visible.
Color Management
Color management is a complicated topic. Device displays are not all created equal due to the technology backing the display. Even the two identical devices may have variations in the color. Some displays and even cameras are able to capture a wider range of colors.
The Android team has made steps to try and make colors more consistent across devices by supporting color profiles. The idea is that future devices would all have a color profile. The idea of having a color profile is that it would then be possible to translate between profiles allowing more consistent colors for all your users.
Currently, the Canvas and Paint APIs only support the int API colors. But support for wider color space (more precision) is supposed to be coming in the future.
Drawing on the Canvas
There are many different tools you as an android developer have to use for drawing on the canvas. Let’s go over them in alphabetical order.
Clipping the Canvas
There are a number of different canvas clipping options. These options all fundamentally do the same thing and that’s to prevent drawing actions from taking place outside of the clipped region.
Here’s an example of a Path drawn on a view:
Now let’s use the clipRect(float, float, float, float) method. First, let’s start with a blank canvas. Then call the clip rect, followed by drawing the same path.
Note the parts outside of the clip area are simply discarded.
Filling the canvas with a color
There are a few ways to fill a canvas with color. The first is drawARGB() which takes alpha, red, green, blue. Another being one of the drawColor() options. Some of these options take either a blend mode or a porter duff mode. See the blend modes section and the porter duff mode section for more details.
Drawing Shapes
There are a number of built-in functions for drawing shapes:
drawArc, drawCircle, drawDoubleRoundRect, drawOval, drawRect, drawRoundRect, drawVertices
There are also a number of ways for drawing lines, points:
drawLine, drawLines, drawPoint, drawPoints
There are also a number of ways draw bitmaps:
Text Drawing
Beyond the text drawing methods on the Canvas there are also text measuring methods. These methods are needed so that you can perform calculations to determine things such as centering text.
The Canvas API text drawing method has one downside. It’s for drawing a single line of text only. It doesn’t handle multi-line text, nor does it handle newlines.
Fortunately, there are classes that implement the android.text.Layout interface that solves this problem. With the StaticLayout, you can set the width and let the StaticLayout figure out how to keep the text within the width you specified.
What is the difference between getTextBounds() and measureText()?
There are a few common ways of measuring text. They might appear to be roughly the same but depending on the font and string contents can lead to different values.
There are two common ways of getting the width of text:
GetTextBounds:
Some have stated that these first two will lead to roughly the same value with the difference being attributed to rounding.
This might appear to be the case for some fonts. The below images show a line indicating the width via the two measurement options:
measureTextBounds has the width set at: 127 pixels
getTextBounds has the width set at: 126 pixels
However, it is actually not true for all fonts. Some fonts are intended to have characters that overlap each other.
In this case measureTextBounds has the width set at: 225 pixels
getTextBounds has the width set at: 321 pixels
The measure text looks too small until you factor in placement of other characters. For example adding a character before the g:
This is because the measure text bounds includes what is called an advance. This modifies the amount of space before and after the character.
Again measuring gives two different values:
Measure Text Bounds is: 341px
Get Text Bounds is: 336px
There’s a third less arguably common method as well: getTextWidths(string, start, length, array) This method puts the individual advancedWidths in the array. Note that the sum of all the array items yields the same width value.
What do all of the different FontMetrics mean?
It’s easier to show rather than explaining:
Porter Duff Modes
In 1984 Thomas Porter & Tom Duff wrote a paper called Compositing Digital Images.
The idea is to take two different images with alpha channels and combine them on a pixel by pixel basis. The opacity of each pixel and the porter duff mode determines what the resulting pixel will look like. This process is also referred to as alpha compositing.
Each mode represents a different calculation that will cause a different outcome. The calculations are based solely on three things:
- Alpha of the source image (the one you are drawing)
- Alpha of the destination image (the image already on the canvas)
- Porter Duff mode
The source and destination have to do with which image is on top and which is on the bottom. Note the calculation doesn’t care what’s in the color of either image.
Here are all of the available porter duff modes:
Note, that Porter & Duff’s work focused exclusively on alpha the alpha channel. In the Android implementation, 5 additional non-alpha channel-specific modes were added:
Blend Modes
Blend modes or mixing modes are a superset of the original Porter Duff modes. The difference is that there are 11 additional non-alpha related modes as opposed to the only 5 available in the porter duff enum.
Note the PorterDuff.Mode.ADD is called BlendMode.PLUS
The additional modes that aren’t available in porter duff but are in Blend are as follows:
Unfortunately, blend modes are a more recent addition to Android. They are being added in API 29.
Color Filters
This one is probably pretty obvious but it’s a filter used to modify each pixel drawn. It’s set on a Paint instance.
There are currently 4 different modes.
- BlendModeColorFilter (added in API 29)
- ColorMatrixColorFilter
- LightingColorFilter
- PorterDuffColorFilter
What if I can’t find what I’m looking for in the Canvas documentation?
Keep in mind that not all of the draw calls are on the Canvas itself. So you may need to be looking in one of the other classes under the android.graphics package.
Performance
We all want our apps to be fast. When the onDraw method is called it’s important to get through it as quickly as possible.
The UI runs on a mainThread and you don’t want to bog it down. Here are some tips to help make your canvas operations more performant:
Avoid Overdraw
A frame is a complete image sent to the screen to be displayed. Overdraw is when you draw multiple times to a portion of the frame that already has content. This is wasteful and should be avoided when possible.
Fortunately, there is an android developer setting called Debug GPU Overdraw. When this setting is enabled it will change each pixel on the screen to 5 colors indicating how bad the overdraw is:
- No change — this is perfect no overdraws
- Blue — 1 overdraw operation was performed)
- Green — 2 overdraw operations took place on that pixel
- Pink — 3 overdraw operations took place on that pixel
- Red — 4 or more! overdraw operations took place on that pixel
Avoid Repetitive Calculations
While drawing on the canvas it’s not uncommon to perform calculations for deciding where to place things and how large to draw things. Many times these calculations can be cached preventing the need to recalculate every single time onDraw is called. Instead, add logic to detect if the size has changed and if not skip the unnecessary calculations.
Use Picture Whenever Possible
The picture class records drawing operations. These operations can then be played back with a single draw call. This draw call is usually more performant than calling each of those calls individually.
How to use the Picture
First you need to create a picture object. Then call begin recording which will return a canvas object. Then draw on the canvas. When you are done drawing call endRecording. When you want to replay the drawing operations onto a canvas simply call draw on the picture object passing in the canvas you wish the contents to be displayed on.
Here’s what the code would look like:
Note that the picture has a requiresHardwareAcceleration(): Boolean method. This method will let you know if the canvas has to be hardware accelerated. Note this needs to be checked after endRecording has been fixed. If it says that it requires a hardware accelerated canvas you can’t draw to a non-hardware accelerated canvas.
Debugging Tips
Bellow is a collection of common problems you may run into and some helpful suggestions for how to solve them.
Why is my draw call not showing up on the canvas?
Common reasons:
- The opacity on your paint is set to 0
- Change the opacity to make it show up
- The color is the same as the background color
- Change the paint color to a high contrast color to make it visible
- The drawing operations occur outside the bounds of the screen
- Call Canvas.translate() to move the canvas to try and bring the drawing operations in bounds
- The arguments in your draw call are in the wrong order
- The containing view size is actually 0
- Get the width or height size
How to center my text on a canvas?
The formula for horizontal centering is easy. It’s Canvas width minus text width divided by 2. That will give you the x position to pass into the drawText method.
The formula for vertical centering is more complicated. The same general idea. It’s the height of the view divided by 2 minus half of the height of the text. That sounds easy but it gets complicated for two reasons:
- The y-axis of the view is expressed in positive numbers as it descends. This doesn’t match the familiar cartesian coordinate system.
- The FontMetrics class has multiple values so it’s easy to use the wrong values when calculating the height of the text.
- You might be tempted to use top and bottom but those have some additional space. Using those will usually cause the text to not appear truly centered.
- The ascent value is a negative number because it’s expressed in relation to the baseline. Since it’s above the baseline it’s negative. We need to add this negative number to the descent. The recommended distance below the baseline for single-spaced text. Adding the absolute value of ascent and descent will yield the total suggested height of the text.
In code that would be the following:
What’s next?
You’ve read a lot about drawing on the Canvas. You know what alternatives you have, how to draw lines, shapes, text, how to clip, how to debug, and have some knowledge of performance. You now have a really good basis for getting started with drawing.
Before you go you should sign up to get access to my video explaining how I went from a blank canvas to drawing this compass:
Источник