Рисование в android studio

Здравствуйте, читатели Хабра!

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

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

Теория

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

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

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

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

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

В первом случае изображение будет обновляться только по нашему требованию, что подходит для простых игр типа шахмат, которые не требует большого 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);
>

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

Каждый раз, когда нам необходимо обновить содержимое 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 и возможно создания вашей первой игры.

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.

Цель нашего урока — понять основы графики. Мы напишем простую рисовалку — хотя это слишком громко сказано. Пока мы сами рисовать ничего не будем — за нас это будет делать глупая машина, т.е. Android. Но тем не менее некоторые полезные вещи мы узнаем, а значит повысим свой профессиональный уровень. Продолжить своё обучение можно в разделе Котошоп.

Создадим новый проект SimplePaint. Далее в нём создадим новый класс Draw2D, который будет наследоваться от View. Именно в этом классе мы и будем проводить графические опыты. Щёлкаем правой кнопкой мыши на имени пакета и выбираем в меню New | Java Class. В открывшемся диалоговом окне устанавливаем имя для класса Draw2D.

В данном коде мы наследуемся от класса android.view.View и переопределяем метод класса onDraw(Canvas canvas).

Далее необходимо загрузить созданный класс при старте программы. Открываем основной файл активности MainActivity и заменяем строчку после super.onCreate(savedInstanceState):

В нашем случае мы говорим системе, что не нужно загружать разметку в экран активности. Вместо неё мы загрузим свой класс, у которого есть свой холст для рисования.

Подготовительные работы закончены. Перейдём к графике. Весь дальнейший код мы будем писать в классе Draw2D. Совсем коротко о теории рисования. Для графики используется холст Canvas — некая графическая поверхность для рисования. Прежде чем что-то рисовать, нужно определить некоторые параметры — цвет, толщина, фигура. Представьте себе, что вы рисуете на бумаге и в вашем распоряжении есть цветные карандаши, фломастеры, кисть, циркуль, ластик и т.п. Например, вы берёте толстый красный фломастер и рисуете жирную линию, затем берёте циркуль с жёлтым карандашом и рисуете окружность. Улавливаете аналогию? Теория закончена.

Вся работа с графикой происходит в методе onDraw() класса Draw2D. Создадим виртуальную кисть в классе. В методе укажем, что будем закрашивать всю поверхность белым цветом:

Итак, холст готов. Далее начинается собственно рисование. Следуя описанному выше принципу мы задаём перед каждым рисованием свои настройки и вызываем нужный метод. Например, для того, чтобы нарисовать жёлтый, круг мы включаем режим сглаживания, устанавливаем жёлтый цвет и вызываем метод drawCircle() с нужными координатами и заливаем окружность выбранным цветом. Получилось симпатичное солнышко.

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

Для рисования зеленого прямоугольника мы также задаём координаты и цвет. У нас получится красивая лужайка.

Далее выведем текст поверх лужайки, чтобы все видели, что она предназначена только для котов. Устанавливаем синий цвет, стиль заливки, режим сглаживания и размер прямоугольника, в который будет вписан наш текст.

Читайте также  Почему сбивается настройка каналов в телевизоре lg

При желании можно вывести текст под углом. Пусть это будет лучик солнца.

И завершим нашу композицию выводом рисунка из ресурсов.

В данном примере я вручную подбирал размеры и координаты фигур для своего экрана. В реальных приложениях необходимо сначала вычислить размеры экрана у пользователя, а потом уже выводить фигуры в соответствии с полученными результатами. Иначе получится так, что некоторые элементы композиции просто не попадут на экран при вращении устройства. Допустим, в альбомном режиме вы установите у точки X значение 800, но в портретном режиме ширина экрана будет, скажем, 480, и точка окажется вне поле зрения. Поэтому следует позаботиться о вычислениях размеров экрана и плясать от этой печки. Ниже представлен немного переделанный вариант для общего понимания.

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

— получаем доступ к 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.

Читайте также  Руфус ошибка извлечения iso образа

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

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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Ссылка на основную публикацию
Adblock
detector