Что такое canvas на телефоне андроид
Перейти к содержимому

Что такое canvas на телефоне андроид

  • автор:

Класс 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()

 public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 

В API 21 появилась перегруженная версия метода, в котором можно указать координаты двух точек вместо RectF.

Метод drawArc() позволяет рисовать дуги и сектора. Ниже приводится код для трёх вариантов: сектор с заливкой (похож на PacMan), сектор без заливки (контур) и часть дуги:

 float width = 400f; float height = 240f; float radius = 100f; Path path = new Path(); path.addCircle(width, height, radius, Path.Direction.CW); Paint paint = new Paint(); paint.setColor(Color.WHITE); // установим белый цвет paint.setStrokeWidth(5); paint.setStyle(Paint.Style.FILL); // заливаем paint.setAntiAlias(true); float center_x, center_y; center_x = 240; center_y = 220; final RectF oval = new RectF(); oval.set(center_x - radius, center_y - radius, center_x + radius, center_y + radius); canvas.drawArc(oval, 45, 270, true, paint); // рисуем пакмана // рисуем большого пакмана без заливки paint.setStyle(Paint.Style.STROKE); oval.set(center_x - 200f, center_y - 200f, center_x + 200f, center_y + 200f); canvas.drawArc(oval, 45, 270, true, paint); paint.setStyle(Paint.Style.STROKE); // рисуем разорванное кольцо center_y = 540; oval.set(center_x - radius, center_y - radius, center_x + radius, center_y + radius); canvas.drawArc(oval, 135, 270, false, paint); 

drawArc

Метод drawBitmap()

Вывести готовое изображение просто.

 canvas.drawBitmap(mBitmap, 450, 530, mPaint); 

Метод drawCircle()

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

Нарисуем зелёный круг.

 float w, h, cx, cy, radius; w = getWidth(); h = getHeight(); cx = w / 2; cy = h / 2; if (w > h) < radius = h / 4; >else < radius = w / 4; >Paint paint = new Paint(); paint.setColor(Color.GREEN); // установим зелёный цвет paint.setStyle(Paint.Style.FILL); canvas.drawCircle(cx, cy, radius, paint); 

No EmbossMaskFilter

drawLine(s)()

Простой метод — указываем начальные и конечные координаты отрезка.

 package ru.alexanderklimov.simplepaint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; public class Draw2D extends View < private Paint mPaint; public Draw2D(Context context) < super(context); init(); >private void init() < mPaint = new Paint(); mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(10); >@Override protected void onDraw(Canvas canvas) < canvas.drawLine(0, 0, 480, 650, mPaint); canvas.drawLine(480, 0, 0, 650, mPaint); >> 

drawLine

Метод drawOval()

Метод drawOval() рисует овалы. Естественно, если вы зададите одинаковые размеры ширины и высоты, то получите круг/окружность.

 @Override protected void onDraw(Canvas canvas) < mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.GRAY); mPaint.setAntiAlias(true); // овал по всему экрану RectF oval1 = new RectF(0, 0, getWidth(), getHeight()); canvas.drawOval(oval1, mPaint); // овал в виде круга (одинаковые размеры высоты и ширины) mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(Color.RED); RectF oval2 = new RectF(50, 50, 150, 150); canvas.drawOval(oval2, mPaint); // маленький синий овал mPaint.setColor(Color.BLUE); RectF oval3 = new RectF(250, 50, 350, 300); canvas.drawOval(oval3, mPaint); >

drawOval

Если вам нужно наклонить овал в ту или иную сторону, то поверните холст на требуемый угол с помощью метода rotate(). Не забудьте потом повернуть холст обратно, что следующие фигуры выводились нормально.

Повернём синий овал из предыдущего примера:

 float rotate_center_x = 200; float rotate_center_y = 200; float rotate_angle = 45; // поворачиваем холст canvas.rotate(-rotate_angle, rotate_center_x, rotate_center_y); // маленький синий овал paint.setColor(Color.BLUE); RectF oval3 = new RectF(250, 50, 350, 300); canvas.drawOval(oval3, paint); // возвращаем холст на прежний угол canvas.rotate(rotate_angle, rotate_center_x, rotate_center_y); 

drawOval

В API 21 появилась перегруженная версия метода, в котором можно указать координаты двух точек вместо RectF:

 void drawOval (float left, float top, float right, float bottom, Paint paint) 

Метод drawPaint()

Метод позволяет закрасить весь холст одним цветом.

 @Override protected void onDraw(Canvas canvas) < // закрашиваем холст жёлтым цветом mPaint.setColor(Color.YELLOW); // стиль Заливка mPaint.setStyle(Paint.Style.FILL); canvas.drawPaint(mPaint); >

Метод drawRect()

У метода существует три перегруженные версии для рисования прямоугольника. Рассмотрим один из них:

 @Override protected void onDraw(Canvas canvas) < super.onDraw(canvas); // создаём пустой прямоугольник и задаём координаты верхней левой и нижней правой точек Rect myRect = new Rect(); myRect.set(0, canvas.getHeight()/2, canvas.getWidth(), canvas.getHeight()); // кисть Paint greenPaint = new Paint(); // цвет кисти - зелёный greenPaint.setColor(Color.GREEN); // тип - заливка greenPaint.setStyle(Paint.Style.FILL); // закрашиваем зелёным прямоугольником вторую половину экрана canvas.drawRect(myRect, greenPaint); >

Метод drawRoundRect()

Для рисования прямоугольников с закруглёнными углами используется метод drawRoundRect (RectF rect, float rx, float ry, Paint paint).

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

Реализуем три разных способа:

 float width = (float) getWidth(); float height = (float) getHeight(); float radius; if (width > height) < radius = height / 4; >else < radius = width / 4; >Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.WHITE); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.FILL); float center_x, center_y; center_x = width / 4; center_y = height / 4; final RectF rect = new RectF(); rect.set(center_x - radius, center_y - radius, center_x + radius, center_y + radius); canvas.drawRoundRect(rect, 50, 50, paint); paint.setStyle(Paint.Style.STROKE); center_x = width / 2; center_y = height / 2; rect.set(center_x - radius, center_y - radius, center_x + radius, center_y + radius); canvas.drawRoundRect(rect, 100, 50, paint); paint.setStyle(Paint.Style.STROKE); center_x = width * 3 / 4; center_y = height * 3 / 4; rect.set(center_x - radius, center_y - radius, center_x + radius, center_y + radius); canvas.drawRoundRect(rect, 100, 100, paint); 

drawRoundRect

В API 21 появилась перегруженная версия метода, в котором можно указать координаты двух точек вместо RectF.

Метод drawPath()

Для рисования соединённых отрезков можно использовать метод drawPath(), указав в параметрах настройки для рисования и массив координат точек. Для удобства добавим в класс Draw2D новый класс Pt, который позволит быстро создать массив точек с заданными координатами. Далее настраиваем объекты для рисования и формируем путь через созданный массив. В результате получим кошкин дом.

 // Класс для создания точки class Pt < float x, y; Pt(float _x, float _y) < x = _x; y = _y; >> // создаём массив точек Pt[] myPath = < new Pt(300, 50), new Pt(200, 200), new Pt(200, 400), new Pt(400, 400), new Pt(400, 200), new Pt(300, 50), >; @Override protected void onDraw(Canvas canvas) < super.onDraw(canvas); Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setStrokeWidth(3); paint.setStyle(Paint.Style.STROKE); Path path = new Path(); // переходим в первую точку рисования path.moveTo(myPath[0].x, myPath[0].y); // рисуем отрезки по заданным точкам for (int i = 1; i < myPath.length; i++)< path.lineTo(myPath[i].x, myPath[i].y); >// выводим результат canvas.drawPath(path, paint); > 

drawPath

Путь можно составлять не только из точек, но и из фигур, например, дуг. Сначала формируем дугу, добавляем её в путь при помощи метода Path.addArc(), повторяем операцию снова несколько раз, а в конце выводим окончательный вариант:

 float radius = 150; Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.WHITE); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.STROKE); Path path = new Path(); float center_x, center_y; center_x = 0; center_y = 0; final RectF oval = new RectF(); oval.set(center_x - radius, center_y - radius, center_x + radius, center_y + radius); path.addArc(oval, 10, 70); // первая дуга oval.set(center_x - radius - 10, center_y - radius - 10, center_x + radius + 20, center_y + radius + 20); path.addArc(oval, 5, 80); // вторая дуга oval.set(center_x - radius- 20, center_y - radius -20, center_x + radius + 40, center_y + radius + 40); path.addArc(oval, 0, 90); // третья дуга canvas.drawPath(path, paint); // выводим всё вместе 

drawPath

Можно нарисовать символ парашюта:

 Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.WHITE); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.STROKE); Path path = new Path(); path.moveTo(160.0f, 240.0f); path.lineTo(140.0f, 200.0f); path.addArc(new RectF(140, 180, 180, 220), -180, 180); path.lineTo(160.0f, 240.0f); path.close(); canvas.drawPath(path, paint); 

addArc

Метод drawPoint()

Простой метод для рисования точки в нужно месте указанной кистью. Для координат используются значения типа float.

 canvas.drawPoint(1F, 0F, mPaint); 

Метод drawText()

С помощью метода drawText() можно выводить текст в заданной позиции. Добавим сначала несколько эффектов, чтобы казалось, что текст парит над поверхностью:

 @Override protected void onDraw(Canvas canvas) < super.onDraw(canvas); canvas.drawColor(Color.GRAY); Paint shadowPaint = new Paint(); shadowPaint.setAntiAlias(true); shadowPaint.setColor(Color.WHITE); shadowPaint.setTextSize(35.0f); shadowPaint.setStrokeWidth(2.0f); shadowPaint.setStyle(Paint.Style.STROKE); shadowPaint.setShadowLayer(5.0f, 10.0f, 10.0f, Color.BLACK); canvas.drawText("http://developer.alexanderklimov.ru/android", 20, 200, shadowPaint); >

drawText()

Центрируем текст

Есть небольшая тонкость, если вам захочется вывести текст в центре холста. Проблем с вычислением центра холста и размером текста нет. Центр можно найти, разделив пополам значения ширины и высоты холста. А ширину и высоту текста можно узнать через метод кисти getTextBounds(), который возвращает ограничивающий прямоугольник.

Но вычисление ширины текста через textBounds.width(); приводит к небольшому смещению. Лучше воспользоваться методом кисти measureText(). Тогда текст отцентрируется точнее.

 package ru.alexanderklimov.canvas; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.view.View; public class Draw2D extends View < private Paint mPaint = new Paint(); private Rect mTextBoundRect = new Rect(); public Draw2D(Context context) < super(context); >@Override protected void onDraw(Canvas canvas) < super.onDraw(canvas); float textWidth, textHeight; String text = "МЯЧ"; float width, height, centerX, centerY, radius; width = getWidth(); height = getHeight(); centerX = width / 2; centerY = height / 2; if (width >height) < radius = height / 4; >else < radius = width / 4; >// стиль Заливка mPaint.setStyle(Paint.Style.FILL); // закрашиваем холст белым цветом mPaint.setColor(Color.WHITE); canvas.drawPaint(mPaint); // Рисуем желтый круг mPaint.setAntiAlias(true); mPaint.setColor(Color.YELLOW); canvas.drawCircle(centerX, centerY, radius, mPaint); // Рисуем текст mPaint.setColor(Color.BLUE); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); mPaint.setTextSize(100); // Подсчитаем размер текста mPaint.getTextBounds(text, 0, text.length(), mTextBoundRect); //textWidth = textBounds.width(); // Используем measureText для измерения ширины textWidth = mPaint.measureText(text); textHeight = mTextBoundRect.height(); canvas.drawText(text, centerX - (textWidth / 2f), centerY + (textHeight /2f), mPaint ); > > 

drawText()

Пример на Kotlin с дополнительной информацией.

Методы rotate() и restore()

Холст во время рисования можно вращать. Во многих ситуациях такой приём менее затратный по ресурсам, чем рисование самого объекта под углом. Суть в следующем: вы поворачиваете холст на нужный градус, рисуете фигуру, а затем возвращаете холст на место при помощи метода restore(), чтобы следующие фигуры рисовались в ожидаемых местах. Иначе остальные фигуры будут рисоваться уже относительно поворота.

В примере с овалом уже использовался данный метод. В примере Работаем с графикой. Основы мы также поворачивали холст, чтобы вывести текст под углом.

Вращение происходит вокруг начальной точки холста (0, 0). Но можно также использовать перегруженную версию метода rotate(float degrees, float px, float py), в которой можно указать координаты точки поворота.

Методы scale() и translate()

Стандартная система координат начинает свой отсчёт с верхнего левого угла. Иногда, для рисования сложных фигур удобнее назначить свою систему координат. Например, для рисования циферблата часов удобнее рисовать относительно центра экрана в диапазоне от -1 до 1.

Чтобы установить свою систему координат, нужно произвести трансформацию. В следующем примере мы установим координаты в диапазоне от 0 до 10 и нарисуем график в стандартном виде из точки 0,0 в левом нижнем углу в точку 10,10 в верхнем правом углу.

 @Override protected void onDraw(Canvas canvas) < mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.GRAY); mPaint.setAntiAlias(true); canvas.save(); float xMax = 10F; float xMin = 0F; float yMax = 10F; float yMin = 0F; float width = canvas.getWidth(); float height = canvas.getHeight(); canvas.scale(width / (xMax - xMin), -height / (yMax - yMin)); canvas.translate(-xMin + 0.2F, -yMax + 0.2F); mPaint.setStrokeWidth(.1f); // Ось X canvas.drawLine(0F, 0F, 10F, 0F, mPaint); // Ось Y canvas.drawLine(0F, 0F, 0F, 10F, mPaint); mPaint.setColor(Color.RED); canvas.drawLine(0F, 0F, 10F, 10F, mPaint); // Точки вдоль оси X canvas.drawPoint(1F, 0F, mPaint); canvas.drawPoint(2F, 0F, mPaint); canvas.drawPoint(3F, 0F, mPaint); // Точки вдоль оси Y canvas.drawPoint(0F, 1F, mPaint); canvas.drawPoint(0F, 2F, mPaint); canvas.drawPoint(0F, 3F, mPaint); canvas.restore(); >

Для наглядности я добавил на оси несколько точек. Следует обратить внимание, что мы задали диапазон от 0 до 10 и все размеры должны масштабироваться в новых величинах, в том числе и ширина обводки в методе setStrokeWidth(). Поэтому значения должны быть достаточно маленькими, иначе толщина обводки может просто оказаться больше самой фигуры. Кстати, в некоторых случаях с текстом и другими методами рисование масштабирование может сыграть злую шутку и дробные значения не позволят увидеть текст и некоторые линии. В этих случаях приходиться создавать цепочку преобразований, когда временно масштаб увеличивается до нормальных размеров, рисуется текст с подходящим размером шрифта, затем опять всё уменьшается и т.д. Это долгая история.

Как добавить Canvas

Canvas — это 8-секундный зацикленный ролик в вертикальном формате, который занимает место обложки альбома на экране «Сейчас играет».

Canvas можно добавить в любой трек, включая предстоящие релизы.

Ваше имя должно быть указано:

  • в качестве первого из основных исполнителей трека;
  • или в качестве главного исполнителя трека и релиза.

Для администраторов и редакторов.

  1. Войдите в сервис Spotify for Artists.
  2. Откройте вкладку Музыка.
  3. Выберите трек.
  4. Нажмите ДОБАВИТЬ CANVAS (на сайте) или СОЗДАТЬ CANVAS (в приложении).

Образцы Canvas можно посмотреть на сайте canvas.spotify.com.

Что делать, если вы не видите свою заставку Canvas

  • Canvas можно добавить к той версии трека, на которую у вас есть права.
    Примечание. В Spotify может быть несколько версий одной и той же песни (например, версии для различных регионов или версии с цензурой и без).
  • Заставки Canvas видны только в мобильном приложении Spotify.
  • Смартфоны, на которых показываются заставки Canvas:
    • iPhone с iOS 12 или более поздних версий;
    • Android 6.0, 7.0, 8.0 или более поздних версий.

    Страна или регион

    Алжир, Египет, Кения, Ливия, Нигерия, Тунис, ЮАР.

    Бахрейн, Вьетнам, Гонконг, Израиль, Индонезия, Иордания, Ирак, Катар, Кувейт, Ливан, Малайзия, Объединенные Арабские Эмираты, Оман, Палестина, Саудовская Аравия, Сингапур, Таиланд, Тайвань, Филиппины, Южная Корея, Япония.

    Австрия, Албания, Андорра, Бельгия, Болгария, Босния и Герцеговина, Великобритания, Венгрия, Германия, Греция, Дания, Ирландия, Исландия, Испания, Италия, Кипр, Косово, Латвия, Литва, Лихтенштейн, Люксембург, Мальта, Монако, Нидерланды, Норвегия, Польша, Португалия, Румыния, Северная Македония, Сербия, Словакия, Словения, Турция, Финляндия, Франция, Хорватия, Черногория, Чехия, Швейцария, Швеция, Эстония.

    Гватемала, Гондурас, Доминиканская Республика, Канада, Коста-Рика, Мексика, Никарагуа, Панама, Сальвадор, США.

    Аргентина, Боливия, Бразилия, Колумбия, Парагвай, Перу, Уругвай, Чили, Эквадор.

    Австралия, Новая Зеландия.

    Похожие статьи

    • Инструкции для заставок Canvas
    • Политика в отношении контента заставки Canvas
    • Как мы подсчитываем просмотры заставок Canvas

    Была ли эта информация полезной?

    Canvas javascript не отображает красный квадрат на телефоне

    Не отображается на телефоне, а css кнопку и фон видно. На компъютере всё отлично работает.

    import Interfaces from "./interface.js"; (function() < var canvas = document.getElementById("canvas"), context = canvas.getContext("2d"); window.addEventListener("resize" ,resizeCanvas ,false); function resizeCanvas() < canvas.width = window.innerWidth; canvas.height = window.innerHeight; drawStuff(); >resizeCanvas(); function drawStuff() < context.fillStyle = "red"; context.fillRect(100 ,100 ,2000 ,2000);>)(); 
    html ,body < padding:0; margin:0; height:100%; width:100%;>canvas < top:0; left:0; bottom:0; right:0; position: fixed;>.container < width: 100%; height: 100%;>.shadow < background-color: transparent; box-shadow: inset 0 0 100px 10px gray; position: fixed;>#menu < width: 50pt; height: 50pt; border: none; right: 0; position:fixed; border-radius: 10%;>#menu:hover < background-color: red;>#menu:active < background-color: blue;>.active_menu

    Update . Проблема может быть или в (function ()<>)() (1.3Gb ОП),или в размерах экрана .Я начал писать с ноля скрипты и теперь телефон отображает холст (пока стандартных размеров) .Теперь это выглядит так .

    var canvas = document.getElementById("canvas"), context = canvas.getContext("2d"); function draw() < context.fillStyle = "red"; context.fillRect(0 ,0 ,100 ,100); >window.onload = draw; 

    Update 2 На телефоне скрипты работают лишь в случае ,если они описаны внутри html файла ,из вне они не хотят работать .

         body function myFunction()  

    Пишем игру для Android используя Canvas

    Привет Хабр!
    Сегодня я хочу рассказать о том, как написать простую логическую игру для Android OS с использованием Canvas. С этой игрой я познакомился около 5 лет назад на своем телефоне. Название забылось, а поиски на нескольких тематических форумах ни к чему не привели и я решил написать свою реализации этой игры. Разработка для меня скорее хобби, хотя иногда берусь за небольшие проекты. После некоторых раздумий я решил не использовать движок, а писать самостоятельно. Причины такого решения: желание получить опыт работы с Canvas’ом.

    Суть такова.

    Имеется игровое поле подобное шахматной доске, однако без деления клеток на черные и белые. Размер поля может быть произвольным, однако играть на поле меньше, чем 2х3 не имеет особого смысла. Мне нравится играть на поле 10х10.

    Количество игроков может произвольным, однако на мой взгляд интереснее играть вдвоем или вчетвером. Если играть втроем на прямоугольном поле, то возникает дисбаланс, поскольку один из игроков будет отдален от «пустого» угла. Если играть компанией больше, чем 4 человека, то сложно воплощать свою стратегию.

    Каждый игрок по очереди должен положить один объект (назовем его атомом) в свободную ячейку или в ячейку, где уже есть его атомы. Если в ячейке накапливается «критическая масса», равная числу соседних ячеек, то атомы из данной ячейки перемещаются в соседние ячейки, при этом находящиеся в соседних ячейках атомы «захватываются», т.е. теперь они принадлежат игроку, чьи атомы разлетелись.

    Пара картинок для пояснения сути.

    Пустое поле 4х4 с указанием критической массы для каждой ячейки.

    Игровая ситуация после третьего хода.

    Игровая ситуация после четвертого хода (первыми ходят синие). Видно, что в верхнем левом углу скопилось критическое количество атомов.

    Бах! Они разлетелись, захватив 2 синих атома в ячейке [0][1]. И в этой ячейке [0][1] теперь тоже критическое количество. Цепная реакция!

    Ситуация после разлета. Конец четвертого хода. Синие сейчас совершат пятый ход.

    Реализация. Графическая часть.

    Приступим к реализации. Создадим производный от View класс.

    public class GameView extends View < private Bitmap mBitmap; private Canvas mCanvas; private Paint paint, mBitmapPaint; private float canvasSize; private final int horizontalCountOfCells, verticalCountOfCells; public GameView(Context context, AttributeSet attrs) < super(context, attrs); //размер игрового поля horizontalCountOfCells =10; verticalCountOfCells =10; //в xml разметке позднее пропишем размер вьюхи равный 300dp canvasSize=(int)convertDpToPixel(300, context); mBitmap = Bitmap.createBitmap((int) canvasSize, (int) canvasSize, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); mBitmapPaint = new Paint(Paint.DITHER_FLAG); //определяем параметры кисти, которой будем рисовать сетку и атомы paint =new Paint(); paint.setAntiAlias(true); paint.setDither(true); paint.setColor(0xffff0505); paint.setStrokeWidth(5f); paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeCap(Paint.Cap.ROUND); //рисуем сетку for(int x=0;x< horizontalCountOfCells +1;x++) mCanvas.drawLine((float)x* canvasSize / horizontalCountOfCells, 0, (float)x* canvasSize / horizontalCountOfCells, canvasSize, paint); for(int y=0;y< verticalCountOfCells +1;y++) mCanvas.drawLine(0, (float)y* canvasSize / verticalCountOfCells, canvasSize, (float)y* canvasSize / verticalCountOfCells, paint); >@Override protected void onDraw(Canvas canvas) < canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); >//переводим dp в пиксели public float convertDpToPixel(float dp,Context context) < Resources resources = context.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); return dp * (metrics.densityDpi/160f); >> 

    Здесь я допустил немного говнокода кода плохого качества, а именно захардкордил в коде цвет линий, разделяющих ячейки игрового поля и размер вьюхи считается равным 300 dp. Этот размер можно получить из объекта attrs класса AttributeSet, однако не будем загромождать код.

    Также сразу набросаем Activity, дабы убедиться, что все рисуется красиво.

    public class GameActivity extends Activity < Override public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); >> 

    И разметка main.xml

    Теперь добавим возможность изменять масштаб игрового поля, поскольку возможны промахи мимо нужной ячейки по причине их малого размера. Для этого переопределяем нужные нам методы реализации ScaleGestureDetector.SimpleOnScaleGestureListener интерфейса OnScaleGestureListener в нашем классе GameView.

     private final ScaleGestureDetector scaleGestureDetector; private final int viewSize; private float mScaleFactor; public GameView(Context context, AttributeSet attrs) < //в xml разметке позднее пропишем размер вьюхи равный 300dp viewSize=(int)convertDpToPixel(300, context); mScaleFactor=1f;//значение зума по умолчанию canvasSize=(int)(viewSize*mScaleFactor);//определяем размер канваса … scaleGestureDetector=new ScaleGestureDetector(context, new MyScaleGestureListener()); >@Override protected void onDraw(Canvas canvas) < canvas.save(); canvas.scale(mScaleFactor, mScaleFactor);//зумируем канвас canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); canvas.restore(); >//в случае касания пальем передаем управление MyScaleGestureListener @Override public boolean onTouchEvent(MotionEvent event) < scaleGestureDetector.onTouchEvent(event); return true; >//унаследовались от ScaleGestureDetector.SimpleOnScaleGestureListener, чтобы не писать пустую реализацию ненужных //методов интерфейса OnScaleGestureListener private class MyScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener < //обрабатываем "щипок" пальцами @Override public boolean onScale(ScaleGestureDetector scaleGestureDetector) < float scaleFactor=scaleGestureDetector.getScaleFactor();//получаем значение зума относительно предыдущего состояния //получаем координаты фокальной точки - точки между пальцами float focusX=scaleGestureDetector.getFocusX(); float focusY=scaleGestureDetector.getFocusY(); //следим чтобы канвас не уменьшили меньше исходного размера и не допускаем увеличения больше чем в 2 раза if(mScaleFactor*scaleFactor>1 && mScaleFactor*scaleFactor <2)< mScaleFactor *= scaleGestureDetector.getScaleFactor(); canvasSize =viewSize*mScaleFactor;//изменяем хранимое в памяти значение размера канваса //используется при расчетах //по умолчанию после зума канвас отскролит в левый верхний угол. //Скролим канвас так, чтобы на экране оставалась //область канваса, над которой был жест зума //Для получения данной формулы достаточно школьных знаний математики (декартовы координаты). int scrollX=(int)((getScrollX()+focusX)*scaleFactor-focusX); scrollX=Math.min( Math.max(scrollX, 0), (int) canvasSize -viewSize); int scrollY=(int)((getScrollY()+focusY)*scaleFactor-focusY); scrollY=Math.min( Math.max(scrollY, 0), (int) canvasSize -viewSize); scrollTo(scrollX, scrollY); >//вызываем перерисовку принудительно invalidate(); return true; > > 

    Обратите внимание, что установлены границы значения увеличинения (увеличение будет на промежутке от 1 до 2), а также производится скролл игрового поля к точке зумирования (точка посередине между пальцами) с проверкой на показ области вне игрового поля. Скролл к точке зуммирования (фокальной точке) производится следующим образом — расчитывается координаты фокальной точки относительно начала канваса(верхний левый угол), умножаются на коэффициент зуммирования и вычитаются координаты точки относительно вьюхи. После этого берем ближайшее значение из промежутка [0, canvasSize -viewSize] для предотвращения скролла за пределы игрового поля.

    Теперь напишем обработку скролла, одиночного тапа и двойного тапа (двойной тап будет возвращать к исходному масштабу игрового поля).

     private final GestureDetector detector; public GameView(Context context, AttributeSet attrs) < . detector=new GestureDetector(context, new MyGestureListener()); >//в случае касания пальем передаем обработку Motion Event'а MyGestureListener'у и MyScaleGestureListener'у @Override public boolean onTouchEvent(MotionEvent event) < detector.onTouchEvent(event); scaleGestureDetector.onTouchEvent(event); return true; >//унаследовались от GestureDetector.SimpleOnGestureListener, чтобы не писать пустую //реализацию ненужных методов интерфейса OnGestureListener private class MyGestureListener extends GestureDetector.SimpleOnGestureListener < //обрабатываем скролл (перемещение пальца по экрану) @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) < //не даем канвасу показать края по горизонтали if(getScrollX()+distanceX< canvasSize -viewSize && getScrollX()+distanceX>0) < scrollBy((int)distanceX, 0); >//не даем канвасу показать края по вертикали if(getScrollY()+distanceY< canvasSize -viewSize && getScrollY()+distanceY>0) < scrollBy(0, (int)distanceY); >return true; > //обрабатываем одиночный тап @Override public boolean onSingleTapConfirmed(MotionEvent event) < //получаем координаты ячейки, по которой тапнули int cellX=(int)((event.getX()+getScrollX())/mScaleFactor); int cellY=(int)((event.getY()+getScrollY())/mScaleFactor); return true; >//обрабатываем двойной тап @Override public boolean onDoubleTapEvent(MotionEvent event) < //зумируем канвас к первоначальному виду mScaleFactor=1f; canvasSize =viewSize; scrollTo(0, 0);//скролим, чтобы не было видно краев канваса. invalidate();//перерисовываем канвас return true; >> 

    Получившийся код
    При одиночном тапе мы рассчитываем координаты ячейки, по которой тапнули, например верхняя левая ячейка будет иметь координаты 0,0.

    Напишем метод рисования атомов drawAtoms. параметры метода — координаты ячейки, в которой рисуем атомы, цвет и количество атомов.

     void drawAtoms(int cellX, int cellY, int color, int count) < //считаем координаты центра ячейки float x0=((1f/(2* horizontalCountOfCells))*viewSize+(1f/ horizontalCountOfCells)*cellX*viewSize); float y0=((1f/(2* verticalCountOfCells))*viewSize+(1f/ verticalCountOfCells)*cellY*viewSize); paint.setColor(color); switch (count)< //todo non-absolute values case 1: drawAtoms(cellX, cellY, color, 0);//стираем существующие атомы mCanvas.drawCircle(x0, y0, 3, paint);//рисуем один атом в центре ячейки break; case 2: drawAtoms(cellX, cellY, color, 0); //рисуем пару атомов на удалении от центра ячейки mCanvas.drawCircle(x0-7, y0, 3, paint); mCanvas.drawCircle(x0+7, y0, 3, paint); break; case 3: drawAtoms(cellX, cellY, color, 0); //рисуем три атома в вершинах правильного треугольника с центром в центре ячейки mCanvas.drawCircle(x0 - 7, y0 + 4, 3, paint); mCanvas.drawCircle(x0 + 7, y0 + 4, 3, paint); mCanvas.drawCircle(x0, y0-8, 3, paint); break; case 4: drawAtoms(cellX, cellY, color, 0); //рисуем 4 атом в вершинах квадрата с центром в центре ячейки mCanvas.drawCircle(x0-7, y0-7, 3, paint); mCanvas.drawCircle(x0-7, y0+7, 3, paint); mCanvas.drawCircle(x0+7, y0+7, 3, paint); mCanvas.drawCircle(x0+7, y0-7, 3, paint); break; case 0: //устанавливаем кисти режим стирания paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); //заполнение обойденного участка paint.setStyle(Paint.Style.FILL); //рисуем большой круг, на месте которого ничего не останется mCanvas.drawCircle(x0, y0, 17, paint); //возвращаем исходные параметры paint.setStyle(Paint.Style.STROKE); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); break; >invalidate();//перерисовываем канвас > 

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

    Осталось добавить скроллбары. Этот процесс хорошо описан здесь habrahabr.ru/post/120931.

    Прежде чем приступить к описанию игровой логики добавим глобальную переменную типа GameLogic (наш класс, описывающий логику) с именем logic и добавим в метод обработки одиночного тапа onSingleTapConfirmed
    вызов будущего метода логической обработки добавления нового атома.
    logic.addAtom(cellX, cellY);
    Также необходим сеттер для этой переменной.

    Реализация. Игровая логика.

    Создадим класс GameLogic, описывающий логику игры. Также нам понадобится внутренний класс Cell, хранящий параметры ячейки игрового поля.

     public class GameLogic < private class Cell< int player=0, countOfAtoms=0;//хозяин ячейки, количество атомов в ячейке final int maxCountOfAtoms;//емкость ячейки Cell(int maxCountOfAtoms)< this.maxCountOfAtoms=maxCountOfAtoms; >public int getCountOfAtoms() < return countOfAtoms; >public int getPlayer() < return player; >public void setPlayer(int player) < this.player = player; >public void resetCount() < this.countOfAtoms = 0; >public void addAtom() < this.countOfAtoms++; >boolean isFilled() < return this.countOfAtoms == this.maxCountOfAtoms; >> > 

    Сам класс GameLogic

     private final GameView view; private final GameActivity activity; private int moveNumber=0, currentPlayer=0; private final int COUNT_OF_PLAYERS, BOARD_WIDTH, BOARD_HEIGHT; private final Cell[][] cells; private final int[] colors=;//цвета атомов private final Handler mHandler; public GameLogic(GameView view, GameActivity activity) < this.view = view; this.activity=activity; mHandler=new Handler(); //инициализируем игровые параметры (количество игроков, размер доски) this.COUNT_OF_PLAYERS=2; this.BOARD_HEIGHT=10; this.BOARD_WIDTH=10; cells=new Cell[BOARD_WIDTH][BOARD_HEIGHT]; for(int x=0; xelse if((x==0 || x==BOARD_WIDTH-1) || (y==0 || y==BOARD_HEIGHT-1))< cells[x][y]=new Cell(3);//краевые, но не угловые - 3 >else < cells[x][y]=new Cell(4);//остальные - 4 >> > > //вызывается из вьюхи по одиночному тапу public void addAtom(final int cellX, final int cellY) < //получаем ячейку, в которую добавляем атом, если ее нет в массиве - выходим из функции. final Cell currentCell; try< currentCell=cells[cellX][cellY]; >catch (IndexOutOfBoundsException ex) < return; >//если в ячейке уже есть атомы этого игрока if(currentCell.getPlayer()==currentPlayer) < currentCell.addAtom(); view.drawAtoms(cellX, cellY, colors[currentPlayer], currentCell.getCountOfAtoms()); //если ячейка заполнена if(currentCell.isFilled())< final Listnearby=new ArrayList(4);//лист соседних ячеек selfAddCell(cellX, cellY-1, nearby); selfAddCell(cellX, cellY+1, nearby); selfAddCell(cellX-1, cellY, nearby); selfAddCell(cellX+1, cellY, nearby); for(Cell nearbyCell:nearby) < nearbyCell.setPlayer(currentPlayer);//соседним ячейкам устанавливаем нового владельца >delayedAddAtom(cellX, cellY-1); delayedAddAtom(cellX, cellY+1); delayedAddAtom(cellX-1, cellY); delayedAddAtom(cellX+1, cellY); //через секунду произойдет вызов метода run() mHandler.postDelayed(new Runnable() < @Override public void run() < //текущая ячейка становится нейтральной (ничейной) currentCell.setPlayer(-1); //и пустой currentCell.resetCount(); view.drawAtoms(cellX, cellY, 0x000000, 0); >>, 1000); return; > >else if(currentCell.getPlayer()==-1)< currentCell.addAtom(); view.drawAtoms(cellX, cellY, colors[currentPlayer], currentCell.getCountOfAtoms()); currentCell.setPlayer(currentPlayer); >else < return; >> //через секунду соседние ячйки получат по атому private void delayedAddAtom(final int cellX, final int cellY) < mHandler.postDelayed(new Runnable() < @Override public void run() < addAtom(cellX, cellY); >>, 1000); > //добавляем в лист target существующие ячейки private void selfAddCell(int cellX, int cellY, List target)< try< target.add(cells[cellX][cellY]); >catch (IndexOutOfBoundsException ignore)<> > 

    Здесь мы видим конструктор, который создает двумерный массив объектов Cell и метод addAtom, вызываемый из вьюхи по одиночному тапу и из себя при условии что ячейка наполнена атомами под завязку.
    Теперь можно добавлять атомы в ячейки, а при накоплении в ячейке критического количества атомов они разлетятся в стороны через секунду. Однако сейчас во время этой секунды можно добавлять атомы. Избавимся от этого, добавив переменную-флаг isLock и методы isLock(), lock() и unlock() в класс GameView.

    Также необходимо добавить смену игрока после хода, подсчет очков и обработку конца игры (когда все атомы принадлежат одному игроку и каждый игрок совершил как минимум 1 ход).
    Добавим в конец метода addAtom() следующий код

     int[] score=scoring(); if(moveNumber==0)< endTurn(score); >else < //проверка на конец игры. Нельзя делать в теле метода endTurn() потому что //не определим конец игры при бесконечной реакции int losersCount=0; for(int i=0; iif(losersCount+1==COUNT_OF_PLAYERS) < isEndGame=true; >if(!mHandler.hasMessages(0)) < view.unlock(); endTurn(score); >> > //обработка конца хода private void endTurn(int[] score)< if(!isEndGame)< if(currentPlayer == COUNT_OF_PLAYERS-1)< moveNumber++; currentPlayer=0; >else < currentPlayer++; >>else < activity.endGame(currentPlayer, score[currentPlayer]); >//извещаем активити, о том, что поменялся счет, текущий игрок и номер хода activity.setMoveNumber(moveNumber); activity.setPlayerName(currentPlayer); activity.setScore(score); > //подсчет очков int[] scoring() < int[] score=new int[COUNT_OF_PLAYERS]; for(int x=0; x> > return score; > 

    Весь код
    Осталось написать реализацию методов

     void setPlayerName(int playerID)<> void setScore(int[] score)<> void setMoveNumber(int moveNumber)<> void endGame(int winnerID, int score)<> 

    Это будет домашним заданием. Тут все тривиально.

    После сборки обработать напильником

    Далее требуется небольшое допиливание получившегося приложения в виде создания Activity для настройки игры (размер игрового поля, количество игроков, цвет атомов и имена игроков, однако это не тема данной статьи. Итоговый код можно посмотреть на гитхабе, получившуюся игру попробовать на Google Play

    Надеюсь, кому-нибудь статья поможет.
    Прошу большими камнями не кидаться. Осознаю низкое качество кода и обещаю стремиться избегать говнокода. Спасибо за внимание.

    P.S. Это моя первая статья на хабре.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *