Исходник игры для Android написан на языке C# на платформе Xamarin.Android. Исходник представляет игру Мозаика, где картинки можно передвигать и таким образом составлять красивые узоры. Xamarin надстройка для среды .NET позволяющая создавать Android приложения на языке C# с использованием всех возможностей популярного языка. Xamarin.Android обеспечивает получить полный доступ к нативному (родному) Android SDK без каких либо ограничений. Программирование приложений на языке C# с помощью Xamarin интуитивно понятно и позволяет быстро перестраиваться в программировании самых различных десктоповых и веб приложений для Windows, Lunix, Android, iOS.
Компоновка элементов интерфейса игры Мозаика базируется на макете RelativeLayout, удобном контейнере для размещения элементов и групп элементов. Использование RelativeLayout предоставляет возможность создавать позиции элементов в точных единицах. Контейнер хоть и носит название относительный, но позволяет использовать абсолютные координаты для позиционирования элементов. В данном исходнике это и было использовано. Макет RelativeLayout создаётся в XML дизайнере, а дочерние элементы, картинки упакованные в ImageView, создаются и добавляются на поле макета программным способом. Перед показом игрового поля кратковременно демонстрируется экранная заставка.
Для управления частичками Мозаики, файлы картинок упакованы в контейнер Android.ImageView. Такая оболочка для изображений имеет много полезных свойств. При помощи ImageView можно позиционировать изображение на макете по пиксельным координатам, масштабировать по горизонтали и вертикали, добавлять цветовую маску для вложенного изображения и др. Методы унаследованные от родительского класса ViewSetY(…), SetX(…) размещают контейнеры картинок по осям X и Y в любом месте родительского макета RelativeLayout в пиксельных единицах. Используя пространственные координаты, можно эффективно управлять положением картинок на экране смартфона или планшета. Представления ImageView создаются и добавляются в родительский контейнер программным способом. Картинки для наполнения ImageView загружаются из ресурсов Drawable и далее программно каждая в свое представление.
Получить программно ширину и высоту контейнера RelativeLayout во время создания или восстановления невозможно. В течение работы OnCreate(...), OnStart(..), OnResume(...) создаются только объекты визуальных классов, при этом все методы и свойства измерения на выходе выдают нулевые значения. Фактически они еще не «знают» как расположит их на экране родительский контейнер. Надежное получение ширины и высоты макета для размещения элементов ImageView гарантированно после полного создания дерева представлений. К сожалению, подобно программированию в Windows, в Android нет событий OnShow(), где можно перед показом элемента (окна) узнать его размеры. Но выход всегда есть: можно запросить размеры интересующего нас объекта RelativeLayout в отложенной задаче, которая получает доступ к интересуемым размерам после завершения подготовки пользовательского интерфейса.
// Добавляем в поток интерфейса асинхронную задачу прорисовки
// картинок только после получения действительных
// размеров главного макета-контейнера.
// Задача исполнится после готовности
// пользовательского интерфейса.
layoutMain.Post(() =>
{
ComputePos(layoutMain);
ShufflePositions();
InitImages(layoutMain);
}
);
// -- Аналог кода на Java --
view.Post(new Runnable() {
@Override
public void run() {
view.getHeight();
}
});
// --
// -- Аналог кода на Kotlin --
view.Post(Runnable { view.getHeight() })
// --
Размеры представлений ImageView, а значит и картинок в них, высчитываются автоматически при визуализации MainActivity (Activity это единица экрана с пользовательским интерфейсом, Activity в приложении может быть несколько). Изображения загруженные в ImageView квадратные, а физические размеры высчитываются в пикселях. За основу расчетов, в данном исходнике игры, принята ширина главного макета в вертикальном положении. По вертикали картинки располагаются на экране до максимального заполнения. При загрузке на смартфоне, планшете или другом Android устройстве с различными размерами дисплеев визуально получается гармоничное заполнение разноцветными квадратиками.
// Вычисление позиций и размеров квадратиков
void ComputePos(RelativeLayout layoutMain)
{
int widthLayout = layoutMain.Width;
int heightLayout = layoutMain.Height;
// Размер картинок высчитывается точно для горизонтали,
// чтобы гармонично смотрелось по ширине.
int widthRect = widthLayout / NumberRectHorizontal;
// Картинка квадратная.
int heightRect = widthRect;
// Количество строк до заполнения контейнера по высоте.
int numberRectVertical = heightLayout / heightRect;
// Все позиции в пространстве.
RectPositionImages = new RectF[NumberRectHorizontal * numberRectVertical];
// Расчет позиций в пространстве.
int countPosY = 0;
int countPosX = 0;
for (int i = 0; i < RectPositionImages.Length; i++)
{
var rect = new RectF
{
Left = widthRect * countPosX,
Top = widthRect * countPosY,
};
rect.Right = rect.Left + widthRect;
rect.Bottom = rect.Top + heightRect;
RectPositionImages[i] = rect;
if (countPosX > 0 && countPosX % (NumberRectHorizontal - 1) == 0)
{
countPosX = -1;
countPosY++;
}
countPosX++;
}
}
Координаты позиций и размерность элементов ImageView вычисляются после создания дерева представлений MainActivity и хранятся в массиве прямоугольников RectF[] RectPositionImages. Хранение координат отдельно от контейнеров картинок позволяет контролировать их положения на экране дисплея и корректировать в случае необходимости. Пространственными координаты названы, потому что они не привязаны к элементам макета и только виртуально делят пространство главного контейнера на ячейки. Пространственные координаты вычисляются на основе количества ячеек по ширине экрана. Элементы ImageView размещаются на позициях в контейнере RelativeLayout исчисляемых в пикселях. При создании приложения создается игровое поле с размерами ячеек высчитанных соответственно размеру экрана данного Android устройства.
// Количество позиций в пространстве
RectPositionImages = new RectF[NumberRectHorizontal * numberRectVertical];
// Расчет позиций в пространстве.
int countPosY = 0;
int countPosX = 0;
for (int i = 0; i < RectPositionImages.Length; i++)
{
var rect = new RectF
{
Left = widthRect * countPosX,
Top = widthRect * countPosY,
};
rect.Right = rect.Left + widthRect;
rect.Bottom = rect.Top + heightRect;
RectPositionImages[i] = rect;
if (countPosX > 0 && countPosX % (NumberRectHorizontal - 1) == 0)
{
countPosX = -1;
countPosY++;
}
countPosX++;
}
Для взаимодействия пользователя с игрой Мозаика применяется событие Touch(...). Это событие представляет собой реагирование на прикосновение пальцем или стилусом к сенсорному экрану. Прикосновение к объекту ImageView вызывает видимое изменение размеров и прозрачности картинки. Такая полезная обратная связь даёт информацию игроку Мозаики о том, что он действительно выбрал необходимый квадратик и может его перемещать в желаемое место для создания задуманного узора. В процессе перемещения также есть обратный сигнал о том, что квадратик находится над нужным местом и его можно отпустить. При этом цвет перемещаемой картинки изменяется если она находится на допустимом расстоянии от целевого места размещения. После отпускания картинки (подсобытие MotionEventActions.Up) активная картинка обменивается координатами позиции с нижележащей картинкой.
private void ImageView_Touch(object sender, View.TouchEventArgs e)
{
// Координаты курсора
MotionEvent motionEvent = e.Event;
// Принимаем абсолютные координаты курсора.
float cursorX = motionEvent.RawX;
float cursorY = motionEvent.RawY;
// Главный макет для доступа ко всем картинкам.
RelativeLayout layoutMain =
this.FindViewById(Resource.Id.LayoutMain);
// Активная картинка
ImageView ivSender = (ImageView)sender;
// Позиции активной картинки в пространстве экрана
Positions posSender = (Positions)ivSender.Tag;
// Центр картинки сделаем посередине, чтобы её
// было видно из-под пальца.
ivSender.PivotX = ivSender.Width / 2;
ivSender.PivotY = ivSender.Height / 2;
// Прикасаемся, т.е. нажимаем.
if (motionEvent.Action == MotionEventActions.Down)
{
// Увеличиваем активную картинку для удобного
// вождения пальцем.
ivSender.ScaleX = 2.0f;
ivSender.ScaleY = 2.0f;
// Делаем картинку полупрозрачной, чтобы было видно
// картинки под ней.
ivSender.Alpha = 0.5f;
// Поднимаем над всеми.
ivSender.BringToFront();
// Запоминаем данную позицию активной картинки.
posSender.Position = new RectF
{
Left = ivSender.GetX(),
Top = ivSender.GetY(),
Right = ivSender.GetX() + ivSender.Width,
Bottom = ivSender.GetY() + ivSender.Height
};
// Постоянная дельта на время вождения картинки.
// Дельта это разница между координатным положением
// картинки по данной оси и местом соприкосновения пальца.
// Измеряется в абсолютных единицах.
// Расстояние от места прикосновения до начала координат активной картинки.
DeltaX = cursorX - ivSender.GetX();
DeltaY = cursorY - ivSender.GetY();
}
// Перемещение активной картинки в поисках места для создания узора.
if (motionEvent.Action == MotionEventActions.Move)
{
// Из координат курсора, во время перемещения, вычитаем расстояние от места прикосновения
// до начала координат выбранной картинки.
// Благодаря этому картинка относительно пальца будет неподвижна,
// и будет двигаться точно под пальцем.
ivSender.SetX(cursorX - DeltaX);
ivSender.SetY(cursorY - DeltaY);
// Отлавливаем место возможного отпускания картинки.
for (int i = 0; i < RectPositionImages.Length; i++)
{
if (ivSender.GetX() < (RectPositionImages[i].Left + 20) &&
ivSender.GetX() > (RectPositionImages[i].Left - 20) &&
ivSender.GetY() < (RectPositionImages[i].Top + 20) &&
ivSender.GetY() > (RectPositionImages[i].Top - 20))
{
// Если место найдено просигналим изменением цвета
// передвигаемой картинки.
ivSender.SetColorFilter(Color.DarkViolet);
// Устанавливаем флаг место найдено.
posSender.isFoundPos = true;
// Запоминаем новую позицию для активной картинки.
posSender.newPos = RectPositionImages[i];
break;
}
// Если отдалились от места возможного приземления
// снимаем цветовую сигнализацию перемещаемой картикни.
ivSender.ClearColorFilter();
// Снимаем флаг обнаружения места приземления.
posSender.isFoundPos = false;
}
}
// Поднимаем палец. Отпускаем картинку.
if (motionEvent.Action == MotionEventActions.Up)
{
// Возвращаем картинке нормальный масштаб.
ivSender.ScaleX = 1;
ivSender.ScaleY = 1;
// Восстанавливаем непрозрачность.
ivSender.Alpha = 1.0f;
// Отпуская курсор принимаем новые координаты для данной картинки.
// А картинка которая была уже на этой позиции отправляется на прежнее
// место активной картинки.
if (posSender.isFoundPos == true)
{
// Извлекаем информацию о нижележащей картинке под перемещаемой.
for (int img = 0; img < layoutMain.ChildCount; img++)
{
ImageView imageView = (ImageView)layoutMain.GetChildAt(img);
if (imageView.GetX() == posSender.newPos.Left && imageView.GetY() == posSender.newPos.Top)
{
// --- Если картинку нашли ---
// Поднимаем её над всеми картинками.
imageView.BringToFront();
// Перемещаем с анимацией картинку на старое место передвигаемой картинки.
imageView.Animate().TranslationX(posSender.Position.Left);
imageView.Animate().TranslationY(posSender.Position.Top);
break;
}
}
// Активная картинка устанавливается на новое место.
ivSender.Animate().TranslationX(posSender.newPos.Left);
ivSender.Animate().TranslationY(posSender.newPos.Top);
// Сбрасываем флаг найденного места.
posSender.isFoundPos = false;
// Восстанавливаем настоящий цвет.
ivSender.ClearColorFilter();
}
else
{
// Если нижележащая картинка не найдена,
// возвращаем активную картинку на прежнее место.
ivSender.Animate().TranslationX(posSender.Position.Left);
ivSender.Animate().TranslationY(posSender.Position.Top);
// Восстанавливаем настоящий цвет.
ivSender.ClearColorFilter();
}
}
}
Свойство Tag класса Android.View предназначено для хранения пользовательской информации непосредственно в объекте класса. ImageView является наследником класса View и соответственно тоже имеет данное свойство. Свойство удобно тем, что в нем можно хранить любой объект. В исходнике игры Мозаика ImageView.Tag используется для хранения информации о текущей и новой позиции владельца свойства при исполнении события Touch(...). Для этого создан класс Positions в котором храниться координатная информация для необходимых перемещений цветных квадратиков.
// Собственный класс обязательно наследуем от Java.Lang.Object
// иначе свойству элементов Tag не сможем
// присвоить объект класса.
class Positions : Java.Lang.Object
{
// Текущая позиция
public RectF Position;
// Новая позиция
public RectF newPos;
// Флаг обнаружения места приземления.
public bool isFoundPos;
}
К статье прикреплен архив исходника игры Мозаика для скачивания. Исходник написан на языке C# на платформе Xamarin.Android. Инструмент программирования MS Visual Studio 2019. Целевая платформа API 26 (Android 8.0).
Скачать исходник
Тема: «Исходник игры Мозаика для Android»
AndroidMosaic.zip
Размер:996 КбайтЗагрузки:642