Это очень важный урок т.к. в нем мы организуем постоянное обновление рабочей области, которая будет составлять основу игрового цикла (game loop). Мы заменим класс View, классом ViewSurface, создадим еще один поток который будет постоянно обновлять рабочую область (канву). Ну что же меньше слов больше дела.
class Panel extends SurfaceView implements SurfaceHolder.Callback { @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } }
Помимо того что мы наследуем класс SurfaceView нужно еще имплементировать интерфейс SurfaceHolder.Callback, он представлен тремя методами surfaceChanged, surfaceCreated, surfaceDestroyed. Реализовав этот интерфейс мы добавим наш класс в качестве callback’а.
public Panel(Context context) { super(context); getHolder().addCallback(this); }
Следующим важным шагом будет создание класса потока, конструктор которого будет принимать два параметра наш Panel и SurfaceHolder, в самом классе нам еще потребуется булева переменная которая будет показывать, запущен наш поток или нет.
class TutorialThread extends Thread { private SurfaceHolder _surfaceHolder; private Panel _panel; private boolean _run = false; public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) { _surfaceHolder = surfaceHolder; _panel = panel; } public void setRunning(boolean run) { _run = run; } @Override public void run() { } }
Инициализируем класс потока в конструкторе Panel
class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); }
Следующим шагом, будет реализация перегруженных методов интерфейса SrfaceHolder.Callback, а именно surfaceCreated и surfaceDestroyed.
@Override public void surfaceCreated(SurfaceHolder holder) { _thread.setRunning(true); _thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // simply copied from sample application LunarLander: // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; _thread.setRunning(false); while (retry) { try { _thread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } }
В surfaceCreated мы устанавливаем переменную состояния потока в true и запускаем его. В surfaceDestroyed устанавливаем переменную состояния потока в false и завершаем его. Ну, вот мы уже близки к окончанию. Последним что мы сделаем это запишем код обновления в нашем, переопределенном методе потока.
@Override public void run() { Canvas c; while (_run) { c = null; try { c = _surfaceHolder.lockCanvas(null); synchronized (_surfaceHolder) { _panel.onDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { _surfaceHolder.unlockCanvasAndPost(c); } } } }
В данном коде сначала мы получаем нашу канву, блокируем её для отрисовки, после отрисовки разблокируем и она отрисовывается на нашей рабочей области т.к. мы внутри потока, для этого используется синхронизация с surfaceHolder. В общем-то, это все, конечный код приведен ниже. Вы спросите, зачем было проделано столько работы, если в конечном итоге мы получаем тот же результат что и в первой части. Отвечу, это все для того что бы отрисовка экрана происходила постоянно, обязательное условие если у вас будут динамические объекты. Ссылка на оригинал.Еще тут статья про surfaceView, там рекомендуется функцию создания потока вынести не в конструктор, а в surfaceCreated в силу того что, если пользователь нажмет клавишу Home, а потом вернется в приложение он получит IllegalThreadStateException. Так что можно внести следующие изменения.
@Override public void surfaceCreated(SurfaceHolder holder) { _thread = new TutorialThread(getHolder(),this); _thread.setRunning(true); _thread.start(); } public Panel(Context context) { super(context); getHolder().addCallback(this); //_thread = new TutorialThread(getHolder(), this); в конструкторе этот метод убираем setFocusable(true); }
Имейте ввиду указанные замечания, ниже я приведу оригинальный код, но вам ничего не стоит переделать его (закоментировать/удалить одну строку и добавить тоже одну).
package com.droidnova.android.tutorial2d; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; public class Tutorial2D extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new Panel(this)); } class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); } @Override public void onDraw(Canvas canvas) { Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.icon); canvas.drawColor(Color.BLACK); canvas.drawBitmap(_scratch, 10, 10, null); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { _thread.setRunning(true); _thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // simply copied from sample application LunarLander: // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; _thread.setRunning(false); while (retry) { try { _thread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } } } class TutorialThread extends Thread { private SurfaceHolder _surfaceHolder; private Panel _panel; private boolean _run = false; public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) { _surfaceHolder = surfaceHolder; _panel = panel; } public void setRunning(boolean run) { _run = run; } @Override public void run() { Canvas c; while (_run) { c = null; try { c = _surfaceHolder.lockCanvas(null); synchronized (_surfaceHolder) { _panel.onDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { _surfaceHolder.unlockCanvasAndPost(c); } } } } } }
почему-то, если выйти из приложения, то при повторном запуске ошибка, а при запуске на телефоне – телефон виснет наглухо. С чем это может быть связано и как тогда это исправить?
Нужно обрабатывать события onStop, onResume, onRestart, если еще актуально могу описать этот процесс в следующей заметке. Об этом подробно написано в офицальном мануле тут