Создаем движение в приложении
Главная » Создаем движение в приложении

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

Создаем новый проект, названия я оставлю по умолчанию, можете ввести свои, по желанию. Основой нашего приложения будет основной java класс приложения MainActivity.java. Он будет запускаться вместе со стартом приложения и также он необходим для запуска любого другого Activity или View объекта. Этот наш основной класс будет предельно простым и понятным, по сути все, что нам от него нужно, это просто его запустить. Код для MainActivity.java должен выглядеть так:

public class MainActivity extends Activity {
 
 @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(new MovementView(this));
 }
}

Теперь давайте создадим новый класс по имени MovementView.java. Здесь мы создадим область для движения нашего объекта, которая будет представлять из себя холст для рисования. Добавим этому классу такие стоки импорта:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

Этот класс будет наследовать класс SurfaceHolder.Callback, что поможет нам как можно проще создавать, изменять и разрушать объекты на рабочей области приложения. Все последующие шаги будут реализовывать методы и функции, вложенные в этот класс. Также нам нужно определить необходимые переменные, смысл и назначение которых будет видно по мере продвижения по уроку. Добавим в MovementView.java оговоренное выше:

public class MovementView extends SurfaceView implements SurfaceHolder.Callback {
 private int xPos;
 private int yPos;
 
 private int xVel;
 private int yVel;
 
 private int width;
 private int height;
 
 private int circleRadius;
 private Paint circlePaint;
 
 UpdateThread updateThread;
}

Теперь нужно создать метод который будет выполнять следующее:

     - вызывать метод super;

     - связывать наш класс с SurfaceHolder.Callback;

     - инициализировать необходимые переменные;

     - настраивать скорость движения объекта в каждом направлении;

Код метода выглядит так:

public MovementView(Context context) {
 super(context);
 getHolder().addCallback(this);
 
 circleRadius = 10;
 circlePaint = new Paint();
 circlePaint.setColor(Color.BLUE);
 
 xVel = 2;
 yVel = 2;
}

Нужно создать еще один метод, работа которого будет сводиться к двум простым вещам: перерисовывать холст для того, чтобы скрыть предыдущий объект, а также рисовать новый объект с новым набором координат:

@Override
protected void onDraw(Canvas canvas) {
 
 canvas.drawColor(Color.WHITE);
 
 canvas.drawCircle(xPos, yPos, circleRadius, circlePaint);
}

Далее нужно задать метод, определяющий физику процесса, то есть, физику движения нашего шарика по экрану. Здесь будет задаваться положение шарика и его отскакивание от границ экрана, в случае столкновения с ними.

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

public void updatePhysics() {
 xPos += xVel;
 yPos += yVel;
 if (yPos - circleRadius < 0 || yPos + circleRadius > height) {
 //В случае ударов о верх или низ холста
 if (yPos - circleRadius < 0) {
 //Удар о верхнюю грань
 yPos = circleRadius;
 }else{
 //Удар о нижнюю грань
 yPos = height - circleRadius;
 }
 //Меняем направление шарика
 yVel *= -1;
 }
 if (xPos - circleRadius < 0 || xPos + circleRadius > width) {
 //В случае столкновений с правой или левой стенками
 if (xPos - circleRadius < 0) {
 //В случае столкновений с левой стенкой
 xPos = circleRadius;
 } else {
 //В случае столкновений с правой стенкой
 xPos = width - circleRadius;
 }
 //Меняем x направление на обратное
 xVel *= -1;
 }
}

Теперь нужно создать метод, который будет вызывать рабочую область приложения в случае первого запуска программы. Запомните, что очень не желательно задавать определенные размеры экрана, ведь устройства для запуска вашего приложения абсолютно разные и всем не угодишь. Эти значения нужно задавать в относительных величинах, процентах и т.д., чтобы приложение на всех размерах экранов выглядело так, как его задумал разработчик. Этот метод будет настраивать размеры рабочего холста (захватывать рабочую область для приложения), указывать начальные координаты для шарика, он будет появляться вверху в центре экрана:

public void surfaceCreated(SurfaceHolder holder) {
 
 Rect surfaceFrame = holder.getSurfaceFrame();
 width = surfaceFrame.width();
 height = surfaceFrame.height();
 
 xPos = width / 2;
 yPos = circleRadius;
 
 updateThread = new UpdateThread(this);
 updateThread.setRunning(true);
 updateThread.start();
}

Также, при настройке наследования нашим классом SurfaceHolder.Callback, Android Studio потребовал с нас создать еще один стандартный метод. Использовать мы его не будем, но добавим, чтобы программа не ругалась и нормально работала:

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
{
 }

И напоследок, если мы сейчас запустим приложения, а потом попытаемся его закрыть, то каждый раз при закрытии пользователь будет видеть сообщение об ошибке и некорректном завершении работы. Чтобы это исправить, добавляем еще один метод, который будет нормально завершать работу приложения:

public void surfaceDestroyed(SurfaceHolder holder) {
 
 boolean retry = true;
 
 updateThread.setRunning(false);
 while (retry) {
 try {
 updateThread.join();
 retry = false;
 } catch (InterruptedException e) {
 
 }
 }
}

На данный момент полный код вашего файла MovementView.java должен выглядеть так:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
 
public class MovementView extends SurfaceView implements SurfaceHolder.Callback {
 
 private int xPos;
 private int yPos;
 
 private int xVel;
 private int yVel;
 
 private int width;
 private int height;
 
 private int circleRadius;
 private Paint circlePaint;
 
 UpdateThread updateThread;
 
 public MovementView(Context context) {
 
 super(context);
 getHolder().addCallback(this);
 
 circleRadius = 10;
 circlePaint = new Paint();
 circlePaint.setColor(Color.BLUE);
 
 xVel = 2;
 yVel = 2;
 }
 @Override
 protected void onDraw(Canvas canvas) {
 
 canvas.drawColor(Color.WHITE);
 canvas.drawCircle(xPos, yPos, circleRadius, circlePaint);
 }
 
 public void updatePhysics() {
 xPos += xVel;
 yPos += yVel;
 
 if (yPos - circleRadius < 0 || yPos + circleRadius > height) {
 if (yPos - circleRadius < 0) {
 yPos = circleRadius;
 }else{
 yPos = height - circleRadius;
 }
 yVel *= -1;
 }
 if (xPos - circleRadius < 0 || xPos + circleRadius > width) {
 if (xPos - circleRadius < 0) {
 xPos = circleRadius;
 } else {
 xPos = width - circleRadius;
 }
 xVel *= -1;
 }
 }
 
 public void surfaceCreated(SurfaceHolder holder) {
 
 Rect surfaceFrame = holder.getSurfaceFrame();
 width = surfaceFrame.width();
 height = surfaceFrame.height();
 
 xPos = width / 2;
 yPos = circleRadius;
 
 updateThread = new UpdateThread(this);
 updateThread.setRunning(true);
 updateThread.start();
 }
 
 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 }
 
 public void surfaceDestroyed(SurfaceHolder holder) {
 
 boolean retry = true;
 
 updateThread.setRunning(false);
 while (retry) {
 try {
 updateThread.join();
 retry = false;
 } catch (InterruptedException e) {
 }
 }
 }
}

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

Чтобы создать такой Thread, нужно создать еще один класс по имени UpdateThread и наполнить его следующим содержимым:

import android.graphics.Canvas;
import android.view.SurfaceHolder;
 
public class UpdateThread extends Thread {
 private long time;
 private final int fps = 20;
 private boolean toRun = false;
 private MovementView movementView;
 private SurfaceHolder surfaceHolder;
 }

Основная задача, которая будет здесь выполняться - настройка переменных, которые мы будем использовать для обеспечения связи с холстом (объектом Canvas):

public UpdateThread(MovementView rMovementView) {
 movementView = rMovementView;
 surfaceHolder = movementView.getHolder();
}

Теперь добавим еще один простенький метод, суть работы которого в том, что он регулирует разрешение и запрет на работу Thread:

public void setRunning(boolean run) {
 toRun = run;
}

Далее нужно добавить фактически основной метод в данном классе. Он будет выполнять следующий  перечень задач:

     - проверять разрешение на запуск;

     - если оно есть, то оценивать необходимое время для выполнения процесса;

     - если нет, то холст останется пустым;

     - получать ссылку на холст и подготовить его к работе нашим с объектом (шариком);

     - обновлять физику шарика;

     - вырисовывать шарик в новом положении;

     - фиксировать новое состояние холста;

Код метода:

@Override
public void run() {
 
 Canvas c;
 while (toRun) {
 
 long cTime = System.currentTimeMillis();
 
 if ((cTime - time) <= (1000 / fps)) {
 
 c = null;
 try {
 c = surfaceHolder.lockCanvas(null);
 
 movementView.updatePhysics();
 movementView.onDraw(c);
 } finally {
 if (c != null) {
 surfaceHolder.unlockCanvasAndPost(c);
 }
 }
 }
 time = cTime;
 }
}

Полный код класса UpdateThread.java должен выглядеть так:

import android.graphics.Canvas;
import android.view.SurfaceHolder;
 
public class UpdateThread extends Thread {
 
 private long time;
 private final int fps = 20;
 private boolean toRun = false;
 private MovementView movementView;
 private SurfaceHolder surfaceHolder;
 
 public UpdateThread(MovementView rMovementView) {
 movementView = rMovementView;
 surfaceHolder = movementView.getHolder();
 }
 
 public void setRunning(boolean run) {
 toRun = run;
 }
 
 @Override
 public void run() {
 Canvas c;
 while (toRun) {
 
 long cTime = System.currentTimeMillis();
 
 if ((cTime - time) <= (1000 / fps)) {
 
 c = null;
 try {
 c = surfaceHolder.lockCanvas(null);
 movementView.updatePhysics();
 movementView.onDraw(c);
 } finally {
 
 if (c != null) {
 surfaceHolder.unlockCanvasAndPost(c);
 }
 }
 }
 time = cTime;
 }
 }
}

Вот мы и дошли до финала. Запускаем приложение и видим шарик, скачущий по экрану:

 Работающее приложение

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

Категория: Уроки программирования | Просмотров: 2115 | Добавил: Oleg | Теги: движение, анимация, движущийся шар | Рейтинг: 0.0/0
Всего комментариев: 0
avatar