Android java draw graphics

Урок 141. Рисование. Доступ к 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

package ru.startandroid.develop.p1411canvasview; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.os.Bundle; import android.view.View; public class MainActivity extends Activity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(new DrawView(this)); >class DrawView extends View < public DrawView(Context context) < super(context); >@Override protected void onDraw(Canvas canvas) < canvas.drawColor(Color.GREEN); >> >

В onCreate мы в метод setContentView передаем не layout-файл, как обычно, а свой view-компонент DrawView. Он будет занимать все содержимое Activity.

Класс DrawView является наследником View и переопределяет его метод onDraw. А этот метод дает нам доступ к объекту Canvas. Пока что не будем рисовать ничего особенного, а просто закрасим все зеленым цветом с помощью метода drawColor.

Собственно, все. Готово первое приложение, которое что-то рисует на экране.

Все сохраняем, запускаем и видим результат.

Экран зеленый, как мы и просили.

Метод onDraw был вызван системой, когда возникла необходимость прорисовать View-компонент на экране. Это также произойдет, например, если выключить-включить экран. Попробуйте поставить в onDraw лог и посмотреть результат.

Если вам надо, чтобы на канве была какая-то анимация, необходимо самим постоянно вызывать перерисовку экрана, когда ваши изменения готовы к отображению. Для этого используется метод invalidate. Вызываете его и он в свою очередь вызовет onDraw. Также есть реализации метода invalidate, которые позволяет перерисовать не весь компонент, а только его часть, указав координаты.

Читайте также:  Код пауза в html

Если нужна цикличная прорисовка, можно поместить метод invalidate прямо в onDraw и View будет постоянно перерисовываться. В некоторых уроках, думаю, будем так делать, но только для упрощения кода. А в действительности это не очень хорошая практика, т.к. это все будет идти в основном потоке. И правильнее будет реализовать такую постоянную перерисовку через SurfaceView.

Давайте посмотрим как это делается.

SurfaceView

Перепишем MainActivity.java:

package ru.startandroid.develop.p1411canvasview; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; public class MainActivity extends Activity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(new DrawView(this)); >class DrawView extends SurfaceView implements SurfaceHolder.Callback < private DrawThread drawThread; public DrawView(Context context) < super(context); getHolder().addCallback(this); >@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) < >@Override public void surfaceCreated(SurfaceHolder holder) < drawThread = new DrawThread(getHolder()); drawThread.setRunning(true); drawThread.start(); >@Override public void surfaceDestroyed(SurfaceHolder holder) < boolean retry = true; drawThread.setRunning(false); while (retry) < try < drawThread.join(); retry = false; >catch (InterruptedException e) < >> > class DrawThread extends Thread < private boolean running = false; private SurfaceHolder surfaceHolder; public DrawThread(SurfaceHolder surfaceHolder) < this.surfaceHolder = surfaceHolder; >public void setRunning(boolean running) < this.running = running; >@Override public void run() < Canvas canvas; while (running) < canvas = null; try < canvas = surfaceHolder.lockCanvas(null); if (canvas == null) continue; canvas.drawColor(Color.GREEN); >finally < if (canvas != null) < surfaceHolder.unlockCanvasAndPost(canvas); >> > > > > >

Стало чуть сложнее, правда? ) Сейчас разберемся что к чему.

Метод 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. Он нам нужен, чтобы добраться до канвы.

Читайте также:  Php get file content from post

Метод setRunning ставит метку работы, сообщающую потоку, можно ли работать.

Метод run. В нем видим цикл, который выполняется пока позволяет метка работы (running). В цикле обнуляем переменную канвы, затем от SurfaceHolder получаем канву методом lockCanvas. На всякий случай проверяем, что канва не null, и можно рисовать: снова просто закрашиваем все зеленым цветом. После того, как нарисовали, что хотели, мы возвращаем канву объекту SurfaceHolder методом unlockCanvasAndPost в секции finally (обязательной для выполнения) и SurfaceView отобразит наши художества.

Соответственно, когда в surfaceDestroyed вызывается метод setRunning(false), происходит выход из цикла в методе run и поток завершает свою работу.

Все сохраняем, запускаем и видим результат.

Когда мы рассматривали первый способ получения канвы (через onDraw), я упомянул, что надо самим вызывать invalidate, если нужна постоянная перерисовка. Во втором способе ничего такого делать уже не надо. У нас итак идет постоянная перерисовка в цикле.

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

— рисуем фигуры
— выводим текст

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

Источник

Основы работы с 2D графикой на Android

Совсем недавно я начал изучать программирование под android и конечно сразу поставил перед собой интересную цель — сделать небольшую игру.

Ознакомившись за пару дней с Java (отличный курс: intuit.ru/department/pl/javapl/) и почитав developer.android.com, перешел к самому главному пункту — к графике. И теперь предлагаю вам разобрать работу с ней на простом примере.

Теория

Для вывода 2D графики android предоставляет два пути:

а) Выводить графику в объекте View, который находится в вашем layout.
В этом случае вся графика/анимация управляется самим андроидом и вы, грубо говоря, только определяете какую картинку показать.
Этот способ подходит, если вы хотите вывести простую статичную графику, которая не должна динамично изменяться.

б) Рисовать графику напрямую на канве (Canvas). В этом случае вы вручную вызываете методы канвы для рисования картинок, геометрических объектов и текста.
Вы должны использовать этот способ, если графика в вашей программе должна часто обновляться/перерисовываться. Это как раз то, что нам нужно для игры.

Вариант «б» можно реализовать двумя способами:

1) В том же Activity, в котором находится наш класс View для вывода графики, мы зызываем метод invalidate(), который обновляет содержимое канвы.
2) Создаем отдельный поток, который обновляет содержимое канвы настолько быстро, насколько может (в этом случае не нужно вызывать invalidate()).

Читайте также:  Python pandas read csv column

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

Код

Сначала импортируем нужные пакеты.

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.MotionEvent;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

В нашем Activity создадим класс GraphicsView, расширяющий View и определим внутри него метод onDraw(), в котором будут находиться команды рисования.

public class Game extends Activity
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
GraphicsView myview=new GraphicsView(this); // создаем объект myview класса GraphicsView
setContentView(myview); // отображаем его в Activity
>

public class GraphicsView extends View
public GraphicsView(Context context)

@Override
protected void onDraw(Canvas canvas)
// здесь будут находиться код, рисующий нашу графику
>

Для примера будем выводить картинку, а именно, стандартную иконку приложения.

protected void onDraw(Canvas canvas)
// загружаем иконку из ресурсов в объект myBitmap
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
// рисуем myBitmap на канве в координатах 10, 10
canvas.drawBitmap(myBitmap, 10, 10, null);
>

Можете запустить программу и вы увидите следующее:

image

Каждый раз, когда нам необходимо обновить содержимое GraphicsView (например при изменении координат выводимой картинки), мы должны вызвать invalidate(), который говорит андроиду, что мы хотели бы перерисовать содержимое GraphicsView, и он вызовет наш метод onDraw(). Давайте будем вызывать его при касании экрана.

Определим у нашего класса GraphicsView метод onTouchEvent().

public boolean onTouchEvent(MotionEvent event)
if(event.getAction() == MotionEvent.ACTION_DOWN) < invalidate() >
return true;
>

Теперь, запустив программу и коснувшись экрана, мы будем вызывать invalidate(). Но, так как код внутри onDraw() у нас не меняется, то и никаких изменений на экране мы не увидим.

Давайте менять координаты картинки на те, в которых произошло прикосновение.

Дополним код командами event.getX() и event.getX(). При прикосновении к экрану они получат координаты касания. Сохраним их в переменных touchX и touchY.

public boolean onTouchEvent(MotionEvent event)
if(event.getAction() == MotionEvent.ACTION_DOWN)
touchX = event.getX();
touchY = event.getY();
invalidate();
>
return true;
>

Вернемся к onDraw. Зададим переменные типа float с начальными нулевыми значениями и затем впишем их вместо фиксированных координат рисунка.

float touchX = 0;
float touchY = 0;

@Override
protected void onDraw(Canvas canvas)
// загружаем иконку из ресурсов в объект myBitmap
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
// рисуем myBitmap на канве в координатах касания
canvas.drawBitmap(myBitmap, touchX, touchY, null);
>

Можете запустить программу и коснуться экрана — картинка переместится в точку касания.

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

Источник

Оцените статью