Основы работы с графикой Android (часть 2)

Сентябрь 27th, 2011 § 2 comments

Это очень важный урок т.к. в нем мы организуем постоянное обновление рабочей области, которая будет составлять основу игрового цикла (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);
                    }
                }
            }
        }
    }
}

§ 2 Responses to Основы работы с графикой Android (часть 2)"

  • Сергей пишет:

    почему-то, если выйти из приложения, то при повторном запуске ошибка, а при запуске на телефоне – телефон виснет наглухо. С чем это может быть связано и как тогда это исправить?

    • admin пишет:

      Нужно обрабатывать события onStop, onResume, onRestart, если еще актуально могу описать этот процесс в следующей заметке. Об этом подробно написано в офицальном мануле тут

Оставить комментарий

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

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Смотреть фильмы онлайн