Полный список
— получаем доступ к Canvas
Наконец-то начинаем цикл уроков по графике в Android. Не прошло и двух с половиной лет с момента создания сайта.
Для начала рассмотрим обычное 2D рисование.
Для рисования используется объект Canvas. Сразу договоримся, что я буду называть его «канва». Тем более, что в русском языке даже есть такое слово, известное в узких кругах вышивающих крестиком. Можно еще, конечно, Canvas перевести как «холст» или «полотно», но как-то пафосно получается. «Канва» — проще и удобнее для меня.
Сразу скажу, что канва является лишь инструментом для рисования. А весь результат сохраняется на Bitmap. Мы не можем напрямую попросить Bitmap нарисовать на себе линию или круг, поэтому канва выступает посредником и помогает нам нарисовать то, что нужно.
В этом уроке разберем два способа получения доступа к канве.
Первый способ – через наследника View класса. Нам нужно просто переопределить его метод onDraw и он даст нам доступ к канве. Кода тут минимум и все предельно просто. Но есть недостаток – все рисование выполняется в основном потоке. Это прокатит, если у вас статичная картинка или не слишком динамичная анимация.
Второй способ – через SurfaceView. Этот способ подойдет, если планируете рисовать что-то тяжелое и динамичное. Под рисование здесь будет выделен отдельный поток. Это уже немного посложнее в реализации, чем первый способ.
Project name: P1411_CanvasView
Build Target: Android 2.3.3
Application name: CanvasView
Package name: ru.startandroid.develop.p1411canvasview
Create Activity: MainActivity
В onCreate мы в метод setContentView передаем не layout-файл, как обычно, а свой view-компонент DrawView. Он будет занимать все содержимое Activity.
Класс DrawView является наследником View и переопределяет его метод onDraw. А этот метод дает нам доступ к объекту Canvas. Пока что не будем рисовать ничего особенного, а просто закрасим все зеленым цветом с помощью метода drawColor.
Собственно, все. Готово первое приложение, которое что-то рисует на экране.
Все сохраняем, запускаем и видим результат.
Экран зеленый, как мы и просили.
Метод onDraw был вызван системой, когда возникла необходимость прорисовать View-компонент на экране. Это также произойдет, например, если выключить-включить экран. Попробуйте поставить в onDraw лог и посмотреть результат.
Если вам надо, чтобы на канве была какая-то анимация, необходимо самим постоянно вызывать перерисовку экрана, когда ваши изменения готовы к отображению. Для этого используется метод invalidate. Вызываете его и он в свою очередь вызовет onDraw. Также есть реализации метода invalidate, которые позволяет перерисовать не весь компонент, а только его часть, указав координаты.
Если нужна цикличная прорисовка, можно поместить метод invalidate прямо в onDraw и View будет постоянно перерисовываться. В некоторых уроках, думаю, будем так делать, но только для упрощения кода. А в действительности это не очень хорошая практика, т.к. это все будет идти в основном потоке. И правильнее будет реализовать такую постоянную перерисовку через SurfaceView.
Давайте посмотрим как это делается.
SurfaceView
Стало чуть сложнее, правда? ) Сейчас разберемся что к чему.
Метод onCreate, собственно, ничуть не изменился. Мы также в метод setContentView передаем наш объект DrawView.
Смотрим DrawView. Он является наследником SurfaceView и заодно реализует интерфейс обработчика SurfaceHolder.Callback. Напоминаю, что с SurfaceView мы уже работали в уроке про камеру (Урок 132). Этот компонент только отображает контент. А работа с ним ведется через обработчика SurfaceHolder.
В конструкторе DrawView мы получаем SurfaceHolder и сообщаем ему, что сами будем обрабатывать его события. Таких событий три:
surfaceChanged — был изменен формат или размер SurfaceView
surfaceCreated – SurfaceView создан и готов к отображению информации
surfaceDestroyed – вызывается перед тем, как SurfaceView будет уничтожен
В surfaceCreated мы создаем свой поток прорисовки (о нем чуть позже), передаем ему SurfaceHolder. Вызовом метода setRunning(true) ставим ему метку о том, что он может работать и стартуем его.
В surfaceDestroyed мы своему потоку сообщаем (setRunning(false)) о том, что его работа должна быть прекращена, т.к. SurfaceView сейчас будет уничтожено. Далее запускаем цикл, который ждет, пока не завершит работу наш поток прорисовки. Дождаться надо обязательно, иначе поток может попытаться нарисовать что-либо на уничтоженном SurfaceView.
DrawThread, наследник Thread, – это наш поток прорисовки. В нем и будет происходить рисование.
В конструктор передаем SurfaceHolder. Он нам нужен, чтобы добраться до канвы.
Метод setRunning ставит метку работы, сообщающую потоку, можно ли работать.
Метод run. В нем видим цикл, который выполняется пока позволяет метка работы (running). В цикле обнуляем переменную канвы, затем от SurfaceHolder получаем канву методом lockCanvas. На всякий случай проверяем, что канва не null, и можно рисовать: снова просто закрашиваем все зеленым цветом. После того, как нарисовали, что хотели, мы возвращаем канву объекту SurfaceHolder методом unlockCanvasAndPost в секции finally (обязательной для выполнения) и SurfaceView отобразит наши художества.
Соответственно, когда в surfaceDestroyed вызывается метод setRunning(false), происходит выход из цикла в методе run и поток завершает свою работу.
Все сохраняем, запускаем и видим результат.
Когда мы рассматривали первый способ получения канвы (через onDraw), я упомянул, что надо самим вызывать invalidate, если нужна постоянная перерисовка. Во втором способе ничего такого делать уже не надо. У нас итак идет постоянная перерисовка в цикле.
На этом вводный урок закончим. Мы рассмотрели два способа получения канвы. В последующих уроках я буду использовать первый способ, т.к. он проще, кода в нем значительно меньше и можно будет сосредоточиться непосредственно на работе с канвой и рисовании.
На следующем уроке:
— рисуем фигуры
— выводим текст
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Getting Started with Android Canvas Drawing 🖼
Learn the basics of drawing with the Android Canvas Class
Diving into using the Android Canvas class can unlock magical super powers you never knew you had 🤯. Imagine being able to draw anything* your heart desires just with some basic shapes, paths and bitmaps? Well, the Android Canvas gives you just that ability.
What is a Canvas?
Canvas is a class in Android that performs 2D drawing of different objects onto the screen. The saying “a blank canvas” is very similar to what a Canvas object is on Android. It is basically, an empty space to draw onto.
The Canvas class is not a new concept, this class is actually wrapping a SKCanvas under the hood. The SKCanvas comes from SKIA, which is a 2D Graphics Library that is used on many different platforms. SKIA is used on platforms such as Google Chrome, Firefox OS, Flutter, Fuschia etc. Once you understand how the Canvas works on Android, the same drawing concepts apply to many other different platforms.
Tip: Check out the SKIA source code for a deeper understanding of the Canvas implementation.
It is useful to know that SKIA is used in the underlying code for Android, so when you get stuck trying to understand how a certain API works, you can look at the source for SKIA to gain a deeper understanding.
Canvas Coordinate System
The coordinate system of the Android canvas starts in the top left corner, where [0,0] represents that point. The y axis is positive downwards, and x axis positive towards the right.
All elements drawn on a canvas are placed relative to the [0,0] point.
When working with the Canvas, you are working with px and not dp, so any methods such as translating, or resizing will be done in pixel sizes. This means you need to translate any dp values into px before calling any canvas operations. This will ensure that your drawing looks consistent across devices with different pixel densities.
Canvas draw commands will draw over previously drawn items. The last draw command will be the topmost item drawn onto your canvas. It is up to you to ensure that your items are laid out correctly (Alternatively, you might want to use some of the built-in layout mechanisms for this — such as LinearLayout ).
How do I use a Canvas?
To draw onto a canvas in Android, you will need four things:
- A bitmap or a view — to hold the pixels where the canvas will be drawn.
- Canvas — to run the drawing commands on.
- Drawing commands — to indicate to the canvas what to draw.
- Paint — to describe how to draw the commands.
Get access to a Canvas instance
In order to get access to a Canvas instance, you will need to create a class that extends from View . This will then allow you to override the onDraw method, which has a Canvas as a parameter.
You can then include this view inside your layout XML and this will then automatically invoke the onDraw method of the Custom View.
You can also get access to a Canvas object by programatically creating one in code, like this:
It’s worth noting at this point that any Canvas created programmatically without using a View , will be software rendered and not hardware rendered. This can affect the appearance of some of the drawing commands. For instance, some commands are just not supported with hardware rendering, or only supported from a certain API level. For more information about the differences between hardware rendering and software rendering, read this post.
What can I draw on a Canvas? ✏️
There are many different things you can draw onto a Canvas . One of the most common drawing operations is to draw a bitmap (image) onto the canvas. The method for doing this is just called drawBitmap and it takes in the bitmap object that is loaded up either with Android’s built-in mechanisms, or with Glide.
The second parameter here allows us to pass in the portion of the bitmap that you want to render, when passing in null the whole bitmap will be rendered. The third parameter is a RectF object which represents the scale and translation of the bitmap that you want to draw on screen.
Tip: Make sure your RectF object that you pass into the drawBitmap function is scaled with the correct aspect ratio otherwise your output may be stretched
You need to be careful with this, since it can quite easily stretch the bitmap, as the Canvas calls don’t take into account the aspect ratio of the provided image. You need to ensure the rect that is passed in is properly scaled. The fourth parameter is the paint object, we will cover the purpose of this parameter soon.
There are many other Canvas drawing methods that can give you some great looking views. We won’t be covering them all here, but here are two other examples of drawing methods on the Canvas class:
To draw a circle onto the view, give it a center point x,y , its size and a paint object:
Another one is the drawRect() method. This draws a rectangle on screen:
This is not the full list of drawing methods but just a small highlight of some of them to get you more comfortable with the concepts, feel free to browse the Canvas documentation for a comprehensive list of all of them.
Paint 🎨
In my opinion, the Paint class is possibly the most interesting graphics class and it is also my favourite, for that very reason. There is so much that a Paint object can do that can really make your drawing operations shine. ✨
The Paint class typically holds colour and style information. The Paint object is then used for drawing objects (i.e. bitmap, text, paths etc) onto a Canvas .
To create a Paint object:
This object should be created before using it in Canvas#onDraw() . It is not recommended to create it in onDraw() since you shouldn’t be doing object allocations in that method.
Tip: Use the isAntiAlias flag to ensure your drawing has smooth edges.
The isAntiAlias flag is quite an important one. If you are drawing objects to your canvas and you notice that the edges of your objects have jagged edges, it is likely that you haven’t set this flag to true. This flag indicates to the paint to smooth out the edges of the object you are drawing to the canvas.
The Paint class has more than just those three properties, there are many more things you can do with it. For instance, you can also set properties related to text rendering, such as the typeface , letterSpacing (kerning) and textSize .
Tip: Check that the Canvas/Paint APIs you are using work across different API versions. See this site for more information.
It is worth noting that the Paint#setShadowlayer() method doesn’t work consistently across API levels and drawing commands. It works when drawing text on a Canvas, but applying the shadow to other commands such as drawBitmap doesn’t yield the same results across API levels.
The reason for the inconsistency between API levels is because the Canvas APIs are bundled with the Android Platform and therefore are not updated until the OS is updated. See the list on this page for more information about which APIs work on which Android versions.
Once you’ve created the Paint object, pass that object into your Canvas#draw*() calls and the drawing will then take on the properties you’ve specified in the paint.
Next up…
In the next few articles, we will be diving into other parts of working with Canvas and drawing on Android. Be sure to subscribe to updates and follow me on Twitter for more tips.
If you prefer watching this content — be sure to check out my talk from Mobile Matters London for a summary of this content.
Источник