Работа с SurfaceView в Android

Здравствуйте, Хабравчане!
При работе с 2D графикой в Android отрисовку можно выполнять используя Canvas. Проще всего это сделать с помощью своего класса, унаследованного от View. Необходимо просто описать метод onDraw(), и использовать предоставленный в качестве параметра canvas для выполнения всех необходимых действий. Однако этот подход имеет свои недостатки. Метод onDraw() вызывается системой. Вручную же можно использовать метод invalidate(), говорящий системе о необходимости перепрорисовки. Но вызов invalidate() не гарантирует незамедлительного вызова метода onDraw(). Поэтому, если нам необходимо постоянно делать отрисовку (например для какой-либо игры), вышеописанный способ вряд ли стоит считать подходящим.

Существует еще один подход — с использованием класса SurfaceView. Почитав официальное руководство и изучив несколько примеров, решил написать небольшую статью на русском, которая возможно поможет кому-то быстрее освоиться с данным способом выполнения отрисовки. Статья рассчитана на новичков. Никаких сложных и хитрых приемов здесь не описано.

Класс SurfaceView


Особенность класса SurfaceView заключается в том, что он предоставляет отдельную область для рисования, действия с которой должны быть вынесены в отдельный поток приложения. Таким образом, приложению не нужно ждать, пока система будет готова к отрисовке всей иерархии view-элементов. Вспомогательный поток может использовать canvas нашего SurfaceView для отрисовки с той скоростью, которая необходима.

Вся реализация сводится к двум основным моментам:
  1. Создание класса, унаследованного от SurfaceView и реализующего интерфейс SurfaceHolder.Callback
  2. Создание потока, который будет управлять отрисовкой.

Создание класса


Как было сказано выше, нам потребуется свой класс, расширяющий SurfaceView и реализующий интерфейс SurfaceHolder.Callback. Этот интерфейс предлагает реализовать три метода: surfaceCreated(), surfaceChanged() и surfaceDestroyed(), вызываемые соответственно при создании области для рисования, ее изменении и разрушении.
Работа с полотном для рисования осуществляется не напрямую через созданный нами класс, а с помощью объекта SurfaceHolder. Получить его можно вызовом метода getHolder(). Именно этот объект будет предоставлять нам canvas для отрисовки.
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    public MySurfaceView(Context context) {
        super(context);
        getHolder().addCallback(this);
    }

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

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
}

В конструкторе класса получаем объект SurfaceHolder и с помощью метода addCallback() указываем что хотим получать соответствующие обратные вызовы.
В методе surfaceCreated(), как правило, необходимо начинать выполнять отрисовку, а в surfaceDestroyed() наоборот завершать ее. Оставим пока тела этих методов пустыми и реализуем поток, отвечающий за отрисовку.

Реализация потока отрисовки


Создадим класс, унаследованный от Thread. В конструкторе он будет принимать два параметра: SurfaceHolder и Resources для загрузки картинки, которую мы будем рисовать на экране. Также в классе нам понадобится переменная-флаг, указывающая на то, что производится отрисовка и метод для установки этой переменной. Ну и, конечно, потребуется переопределить метод run().
В итоге получим следующий класс:

class DrawThread extends Thread{
    private boolean runFlag = false;
    private SurfaceHolder surfaceHolder;
    private Bitmap picture;
    private Matrix matrix;
    private long prevTime;

    public DrawThread(SurfaceHolder surfaceHolder, Resources resources){
        this.surfaceHolder = surfaceHolder;

        // загружаем картинку, которую будем отрисовывать
        picture = BitmapFactory.decodeResource(resources, R.drawable.icon);

        // формируем матрицу преобразований для картинки
        matrix = new Matrix();
        matrix.postScale(3.0f, 3.0f);
        matrix.postTranslate(100.0f, 100.0f);

        // сохраняем текущее время
        prevTime = System.currentTimeMillis();
    }

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

    @Override
    public void run() {
        Canvas canvas;
        while (runFlag) {
            // получаем текущее время и вычисляем разницу с предыдущим 
            // сохраненным моментом времени
            long now = System.currentTimeMillis();
            long elapsedTime = now - prevTime;
            if (elapsedTime > 30){
                // если прошло больше 30 миллисекунд - сохраним текущее время
                // и повернем картинку на 2 градуса.
                // точка вращения - центр картинки
                prevTime = now;
                matrix.preRotate(2.0f, picture.getWidth() / 2, picture.getHeight() / 2);
            }
            canvas = null;
            try {
                // получаем объект Canvas и выполняем отрисовку
                canvas = surfaceHolder.lockCanvas(null);
                synchronized (surfaceHolder) {
                    canvas.drawColor(Color.BLACK);
                    canvas.drawBitmap(picture, matrix, null);
                }
            } 
            finally {
                if (canvas != null) {
                    // отрисовка выполнена. выводим результат на экран
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }
}

Для того, чтобы результат не выглядел совсем уж пресно, загруженную картинку увеличим в три раза, и немного сместим к центру экрана. Сделаем это при помощи матрицы преобразований. Также, не чаще чем раз в 30 миллисекунд, будем поворачивать картинку на 2 градуса вокруг ее центра. Само рисование на canvas лучше, конечно, вынести в отдельный метод, но в данном случае мы всего лишь очищаем экран и рисуем изображение. Так что можно оставить как есть.

Начало и завершение отрисовки


Теперь, после того как мы написали поток, управляющий отрисовкой, вернемся к редактированию нашего класса SurfaceView. В методе surfaceCreated() создадим поток и запустим его. А в методе surfaceDestroyed() завершим его работу. В итоге класс MySurfaceView примет следующий вид:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    private DrawThread drawThread;

    public MySurfaceView(Context context) {
        super(context);
        getHolder().addCallback(this);
    }

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

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        drawThread = new DrawThread(getHolder(), getResources());
        drawThread.setRunning(true);
        drawThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        // завершаем работу потока
        drawThread.setRunning(false);
        while (retry) {
            try {
                drawThread.join();
                retry = false;
            } catch (InterruptedException e) {
                // если не получилось, то будем пытаться еще и еще
            }
        }
    }
}

Следует отметить, что создание потока должно выполняться в методе surfaceCreated(). В примере LunarLander из официальной документации создание потока отрисовки происходит в конструкторе класса, унаследованного от SurfaceView. Но при таком подходе может возникнуть ошибка. Если свернуть приложение нажатием клавиши Home на устройстве, а затем снова открыть его, то возникнет исключительная ситуация IllegalThreadStateException.

Activity приложения может выглядеть следующим образом:

public class SurfaceViewActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MySurfaceView(this));
    }
}


Результат работы программы выглядит так (из за вращения изображение немного размазано, но на устройстве выглядит вполне приемлемо):


Заключение


Вышеописанный способ отрисовки графики хоть и несколько сложнее чем рисование на canvas простого View, но в некоторых случаях является более предпочтительным. Разницу можно заметить, если в приложении необходимо очень часто перерисовывать графику. Вспомогательный поток помогает лучше контролировать этот процесс.
И в заключении хотелось бы привести ссылки на некоторые из ресурсов, которыми пользовался при создании статьи:
  1. Пример игры LunarLander из официальной документации
  2. Статья, описывающая использование SurfaceView
Поделиться публикацией

Комментарии 20

    0
    Быстродействие? Или хотя бы количество fps и конфигурация тестовой тушки.
      0
      Тестировал на HTC Desire HD. Система ограничивает fps до 60. На эмуляторе подтормаживает, но это скорее всего из за самого эмулятора.
      На практике сталкивался со следующей проблемой: В приложении рисование делал с помощью стандартного view. Все рисовалось довольно плавно и fps тоже был около 60. В определенный момент в иерархию view-элементов добавлялся TextView, анимированный с помощью стандартных средств Android (tween animation). Эта анимация длилась 2 секунды и после этого TextView удалялся. И вот на эти 2 секунды, пока шла анимация, вся отрисовка начинала тормозить. По завершении анимации, всё опять становилось плавно. После того, как начал использовать SurfaceView вместо простого View, данная проблема возникать перестала.
        0
        TextView сам по себе тяжеловатый элемент.

        Можете добавить еще штук 20 вращаемых элементов и провести тест на живой машинке? Очень интересны результаты.
          +1
          Добавил массив матриц и рисую картинку с помощью каждой из них. При 30-ти вращающихся картинках fps также остается равным 60. Если их число увеличить до 50 штук — частота кадров уменьшается примерно до 50.
            0
            Неплохо тянет. Подойдет даже для простых игр.
      +1
      Спасибо за статью — пока ни разу не приходилось использовать данный компонент.
      Один минорный комментарий по коду (может кому пригодятся для будущей реализации):

      Лучше, чтобы передача UI поля потоку происходила посредством WeakReference (или где-то устанавливался бы референс в null). Иначе, в случае зависания потока будет мемори лик целой активити со всем наполнением потому как потоки не обязательно сразу удаляются мусоросборщиком, а могут еще жить неопределенное время. В этом случае, если запускать приложение несколько раз, размер свободного пространства на хипе может быстро сократиться, что может привести к ООМ эксепшену.

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

      Однако не мог не заметить одну маленькую деталь, а именно очень очень много холостых прогонов в цикле while (run). Это очень сильно будет нагружать процессор, расходовать батарею. Возможно, стоило бы воспользоваться какими-нибудь средствами блокировки потока?
        0
        Народ, а подскажите, если кто знает: вообще, игры на SurfaceView нынче делают, или все перешли на GL? У меня есть желание попробовать что-нибудь этакое сделать. Начать то начал, но сразу заметил следующую проблему: если при обновлении экрана сперва перерисовывать фон, а затем — все спрайты,
        ощутимо «дергаются» и мельтешат объекты. Нутром чую — это крайне неверный подход. А как сделать корректно — найти не могу. Пример простой, битмапный фон и мячик летает по нему.

        Вот код в моем «потомке» SurfaceView
            public void run() {
                while (mRunning) {
                    Canvas canvas = null;
                    try {
                        canvas = mSurfaceHolder.lockCanvas();
                        synchronized (mSurfaceHolder) {
                            updateObjects(); 
                            refreshCanvas(canvas); 
        
                        }
                        sleep(20);
                    } catch (Exception e) {
                    } finally {
                        if (canvas != null) {
                            mSurfaceHolder.unlockCanvasAndPost(canvas);
                        }
                    }
                }
            }
        
            private void refreshCanvas(Canvas canvas) {
                canvas.drawBitmap(mBackground, 0, 0, null);
                mBall.draw(canvas);
            }
        


        Работает это довольно некрасиво, дергано, и по карям мячика — мельтешение от перерисовки фона.
          0
          Так, что-то я не могу никак привести мысли в порядок.
          п.1) Есть у меня класс-модель
          п.2) Есть у меня класс-представление, которое знает как «рисовать» класс-модель
          п.3) Есть фоновый поток, который, обрабатывая данные «из вне», передает в представление итоговый объект класса-модели и вызывает у представления invalidate().

          Чтобы переработать исходное приложение в соответствии с указанной статьей:

          а) теперь DrawThread должен знать как «рисовать» класс-модель, а не класс-представление. Меня как-то это напрягает. Ведь если DrawThread действительно знает как «рисовать» класс-модель, то зачем нужен SurfaceView?
          б) теперь нужно из фонового потока п.3 вызывать DrawThread c переданным в него объектом класса-модели, чтоб тот его и нарисовал.

          Я правильно все понимаю? Это даст прироста производительности действительно?

            0
            Короче, все переделал в соответствии с постом и выявил такой баг. Если перерисовывается весь экран, то есть если происходит в потоке вызов onDraw(locked_canvas), то все хорошо, а если дорисовывать на канве еще что-то, используя locked_canvas.draw(...), то экран мерцает.

            зачем оно тогда все это нужно, если можно аналогично перерисовывать все, используя invalidate() и обычный View?! Поговаривают, это происходит на версии Андройда ниже 2.2.
              0
              У меня происходит так — поток просто крутится и вызывает методы update и render из SurfaceView, т.е. он вовсе не знает, как и что рисовать. View я не использовал, но мне кажется метод с SurfaceView дает больше гибкости — например позволяет установить FPS и реализовать механизм пропуска кадров(если кадр не успевает перерисоваться за заданный ему промежуток времени, на следующей итерации отрисовка не вызывается, идет вызов только метода update, а как только «наверстали» отставание перерисовываем актуальное состояние экрана)
                0
                «поток просто крутится и вызывает методы update и render из SurfaceView»
                да, именно так сейчас и сделано в одном из вариантов, так как именно SurfaceView должен знать как «правильно» рисовать, а вот когда ему рисовать, это уже определяет этот фоновый поток рисования.

                Задача была какая — Нужно чтоб на канве оставалось все ранее нарисованное, а при новом вызове просто дорисовывалось поверх. при этом, во всех моих уже «рабочих» вариантах тестовой программы (которые не меорцают) приходится
                1) перерисовывать весь канвас. то есть хранить все ранее нарисованные элементы в стеке объектов и при перерисовывании формы их всех последовательно и рисовать.
                2) поток рисования работает в пустую — «маслает» себе и «маслает» бесконечно, я вот не пойму не уже ли никак нельзя асинхронно вызвать какой-то метод рисования на канве, то есть не просто поток висит и рисует что-то в соответствии с изменившимся стеком объектов, а вызывать по-требованию. сейчас я просто по требованию вызываю новый поток, который и перерисовывает канву в соответствии с хранящимся стеком объектов и завершается по окончании перерисовывания.

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

                а вот смежная задача — если я хочу чтобы несколько потоков рисовали на одной канве одновременно кривые линии, как будто это два человека рисуют мелом — красным и синим например. мне что — стек всех точек хранить и перерисовывать? ужос :) Да, использование Path для отрисовки «кривой» по точкам подходит, но опять же прийдется хранить стек точек. Эту задачу я приводил лишь как пример смежной с моей задачи. А если параллельно рисоваться должны несколько линий независимо (несколькими фоновыми потоками), то это нужно уже отслеживать массив стеков точек этих кривых. Но все равно это очень как-то неправильно. Ведь для того чтоб просто дорисовать очередную точку из фонового процесса без торможения интерфейса — нужно хранить весь стек и его перерисовывать.

                Сейчас pvshnik мне предложил, наверно, единственно верное решение от холостой работы фонового потока рисования — использование Android Message System в фоновом потоке. Попробую внедрить его.

                Но вот сама задача фонового «дорисовывания» на канве объекта, вынуждающая хранить стек всех нарисованных ранее объектов, как-то у меня не решается без мерцания экрана. При чем при обычном подходе к этой задаче, как в вышеописанном примере, мерцание делится на четное и нечетное, при нечетной блокировке рисуются все объекты нечетного вызова, при четной — только четного вызова. А вот так чтоб рисовались все и «четные» и «нечетные» на одном экране — не выходит, выходит только если хранить их стек и перерисовывать все каждый раз заново.

                Пример программы с различной логикой вариантов рисования на экране скину позже (фоновый поток есть/нет, стек есть/нет, фоновый поток один/каждый раз новый). Логика механизма рисования определяться будет значением конфигурационной переменной.
                  0
                  Итак, вот пример программы с различной логикой вариантов рисования на экране. Смотрите комментарии и меняйте значение mySurfaceView.JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT
                  В комментариях отписал, что и как вызывается и что мне не очень нравится в том или ином варианте.
                  MainActivity
                  package com.example.surfaceviewexample;
                  
                  import android.os.Bundle;
                  import android.app.Activity;
                  import android.graphics.Canvas;
                  import android.view.Gravity;
                  import android.view.Menu;
                  import android.view.MenuItem;
                  import android.view.SubMenu;
                  import android.widget.Toast;
                  
                  public class MainActivity extends Activity {
                  
                  	MySurfaceView mySurfaceView;
                  	BackgroundThread backgroundThread;
                      @Override
                      public void onCreate(Bundle savedInstanceState) {
                          super.onCreate(savedInstanceState);
                          mySurfaceView = new MySurfaceView(this);
                          setContentView(mySurfaceView); 
                          
                      }    
                  
                  	//BackgroundThread knows about only MainActivity and call this.parent.draw
                      //but MainActivity knows about MySurfaceView and delegate draw to its "draw" method 
                      public void draw(int number) {
                      	switch(mySurfaceView.JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT){
                  			case(1):
                  			case(2):
                  			case(3):
                  			case(5):
                  			case(6):{
                  				mySurfaceView.draw(number);
                  				break;			
                  			}
                  			case(4):{				
                  				final int i = number;
                  				new Thread(new Runnable(){
                  					public void run(){
                  						mySurfaceView.post(new Runnable(){
                  							public void run(){
                  								mySurfaceView.draw(i);
                  							}
                  						});
                  					}
                  				}).start();			
                  			}
                      	}	
                      	
                  	}
                      @Override
                      protected void onStart() {
                  
                        	super.onStart();
                          backgroundThread = new BackgroundThread(this);
                          backgroundThread.start();
                      }
                      @Override
                      protected void onStop() {
                        	super.onStop();
                        	if(backgroundThread!=null){
                        		backgroundThread.stop();
                        		backgroundThread = null;
                        	}
                      	
                      }
                      @Override
                      protected void onDestroy() {
                      	super.onDestroy();  	
                    		if(backgroundThread!=null){
                    			backgroundThread.destroy();
                    			backgroundThread = null;
                    		}      
                      }
                  }
                  

                  BackgroundThread
                  package com.example.surfaceviewexample;
                  
                  public class BackgroundThread extends Thread {
                  	//It knows about only MainActivity and call this.parent.draw
                  	MainActivity parent;
                  	int index = 1;
                  	public BackgroundThread(MainActivity parent) {
                  		this.parent = parent;
                   	}
                  
                  	public void run() {
                  		try {
                  			Thread.sleep(1000);
                  		} catch (InterruptedException e1) {
                  			e1.printStackTrace();
                  		}		
                  		while (true) {
                  			this.parent.draw(index++);
                  			try {
                  				Thread.sleep(1000);
                  			} catch (InterruptedException e) {
                  				e.printStackTrace();
                  			}
                  		}
                  	}
                  }
                  

                  MySurfaceView
                  package com.example.surfaceviewexample;
                  
                  import java.util.ArrayList;
                  
                  import android.content.Context;
                  import android.graphics.Canvas;
                  import android.graphics.Color;
                  import android.graphics.Paint;
                  import android.view.SurfaceHolder;
                  import android.view.SurfaceView;
                  
                  public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
                  	DrawingThread drawingThread;
                  	ArrayList<Integer> stack = new ArrayList<Integer>();
                  	public int JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT = 6;///1,2,3,4,5,6
                  	//1 - start drawingThread.draw - drawingThread is only one - not work - canvas blinks
                  	//1 - запуск в лоб drawingThread.draw - drawingThread один - экран мерцает четный/нечетный
                  	//2 - start drawingThread.draw in new Thread - it may be very difficult for comprehension - drawingThread is only one - not work - canvas blinks
                  	//2 - запуск в новой нити исполнения drawingThread.draw - это сложно для понимания, но кто-то так просто посоветовал попробовать - drawingThread один - экран мерцает четный/нечетный
                  	//3 - create every time new DrawingThread and start him  - drawingThread not declared and there are many DrawingThread objects - not work - canvas blinks
                  	//3 - каждый раз создается и стартуется новый DrawingThread - drawingThread вообще не объявляется - экран мерцает четный/нечетный
                  	//4 - using existing method "post" of SurfaceView of Runnable interface, see MySurfaceView.draw case 4 - экран мерцает четный/нечетный
                  	//4 - используется существующий метод "post" суперкласса SurfaceView, см. MySurfaceView.draw вариант 4 - экран мерцает четный/нечетный
                  	//5 - work well - there is one DrawingThread object and it have to store stack of all drawing Objects - I dont't like existing stack and idling process, not call on demand
                  	//5 - работает по интерфейсу пользователя как надо - и drawingThread только один, и есть стек отображаемых объектов - Мне не нравится, что для того чтоб дорисовать объект, нужно хранить и перерисовывать весь стек, есть холостой ход фонового потока рисования, а хочется отрисовку объекта по требованию
                  	//6 - work well - but there are more DrawingThread objects and it have to store stack of all drawing Objects - I dont't like existing stack. There is call on demand, although some people say that it is no good use multithread access to UI.
                  	//6 - работает по интерфейсу пользователякак надо - каждый раз создается и стартуется новый DrawingThread, и есть стек всех отображаемых объектов - Мне не нравится, что для того чтоб дорисовать объект, нужно хранить и перерисовывать весь стек. Но реализована отрисовка по требованию, хотя многие разработчики предостеригают от создания множества нитей, используемых для рисования на одной канве.
                  	//7 шутки ради
                  	public MySurfaceView(Context context){
                  		super(context);
                  		getHolder().addCallback(this);		
                  	}
                  
                  	public void surfaceChanged(SurfaceHolder holder, int format, int width,
                  			int height) {		
                  	}
                  
                  
                  	//BackgroundThread knows about only MainActivity and call this.parent.draw
                      //but MainActivity knows about MySurfaceView and delegate draw to its "draw" method
                      //but MySurfaceView knows how to draw object well, but delegates draw to "DrawingThread.draw" method
                  	public void draw(int index){
                  		switch(this.JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT){
                  			case(1):
                  			case(4):{
                  				drawingThread.draw(index);
                  				break;			
                  			}
                  			case(2):{
                  				final int i = index;
                  				new Thread(new Runnable(){
                  					public void run(){
                  						drawingThread.draw(i);
                  					}
                  				}).start();
                  				break;			
                  			}
                  			case(3):{
                  				(new DrawingThread(getHolder(),this,index)).start();
                  				break;			
                  			}			
                  			case(5):{
                  				stack.add(index);
                  				break;			
                  			}			
                  			case(6):{
                  				stack.add(index);
                  				(new DrawingThread(getHolder(),this)).start();
                  				break;			
                  			}
                  		}			
                  	}
                  	public void draw(Canvas canvas, int number){
                  		//super.onDraw(canvas);
                  		switch(this.JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT){
                  			case(1):
                  			case(2):
                  			case(3):
                  			case(4):{
                  				Paint p = new Paint();
                  				p.setTextSize(canvas.getHeight()/50);
                  				p.setColor(Color.RED);
                  				canvas.drawText(""+number, p.getTextSize(), p.getTextSize()*(number), p);
                  				break;			
                  			}
                  			case(5):
                  			case(6):{	
                  				//in case 5 variable "number" is not using - this is stack 
                  				Paint p = new Paint();
                  				p.setTextSize(canvas.getHeight()/50);
                  				p.setColor(Color.RED);
                  				for(Integer int_item : stack){
                  					canvas.drawText(""+int_item, p.getTextSize(), p.getTextSize()*(int_item), p);
                  				}
                  				break;
                  			}
                  		}
                  	}
                  	
                  	public void surfaceCreated(SurfaceHolder holder) {
                  		switch(this.JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT){
                  			case(1):
                  			case(2):
                  			case(4):
                  			case(5):{
                  				drawingThread = new DrawingThread(getHolder(),this);
                  				drawingThread.start();
                  				break;			
                  			}
                  			case(3):
                  			case(6):{
                  				//Nothing else matter
                  				break;
                  			}
                  		}
                  	}
                  
                  	public void surfaceDestroyed(SurfaceHolder holder) {		
                  		switch(this.JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT){
                  			case(1):
                  			case(2):
                  			case(4):
                  			case(5):{
                  				drawingThread.destroy();
                  				break;			
                  			}
                  			case(3):
                  			case(6):{
                  				//Nothing else matter
                  				break;
                  			}
                  		}		
                  	}
                  }
                  

                  DrawingThread
                  package com.example.surfaceviewexample;
                  
                  import android.graphics.Canvas;
                  import android.graphics.Color;
                  import android.graphics.Paint;
                  import android.view.SurfaceHolder;
                  
                  public class DrawingThread extends Thread {
                  	SurfaceHolder surfaceHolder;
                  	MySurfaceView mySurfaceView;
                  	int number = 0;
                  	public DrawingThread(SurfaceHolder surfaceHolder,MySurfaceView mySurfaceView){
                  		super();
                  		this.surfaceHolder = surfaceHolder;
                  		this.mySurfaceView = mySurfaceView;
                  	}
                  	public DrawingThread(SurfaceHolder surfaceHolder,MySurfaceView mySurfaceView,int number){
                  		super();
                  		this.surfaceHolder = surfaceHolder;
                  		this.mySurfaceView = mySurfaceView;
                  		this.number = number;
                  	}
                  	//BackgroundThread knows about only MainActivity and call this.parent.draw
                      //but MainActivity knows about MySurfaceView and delegate draw to its "draw" method
                      //but MySurfaceView knows how to draw object well, but delegates draw to "DrawingThread.draw" method
                      //DrawingThread dose not know how to draw object well, it blocks canvas and call necessary MySurfaceView.draw(Canvas canvas, int number)
                  	public void draw(int number){
                  		
                  		this.number = number;
                  		this.draw();
                  	}
                  	protected void draw(){
                  		Canvas canvas = this.surfaceHolder.lockCanvas();
                  		try{
                  			synchronized (surfaceHolder) {
                  				//in case 5 or 6 variable "this.number" is not using - this is stack 
                  				mySurfaceView.draw(canvas, this.number);
                  			}									
                  		}
                  		finally{			
                  			if(canvas!=null){
                  				surfaceHolder.unlockCanvasAndPost(canvas);
                  			}			
                  		}
                  	}
                  	public void run(){
                  		switch(mySurfaceView.JUST_CHANGE_DEFAULT_DRAWING_LOGIC_VARIANT){
                  			case(1):
                  			case(2):{
                  				while(true){}	
                  			}
                  			case(3):{
                  				this.draw();
                  				break;
                  			}
                  			case(4):{
                  				//Nothing else matter
                  				break;
                  			}
                  			case(5):{
                  				//Nothing else matter
                  				while(true){this.draw();}	
                  			}
                  			case(6):{
                  				//Nothing else matter
                  				this.draw();	
                  				break;
                  			}
                  		}			
                  	}
                  
                  }
                  


                  Если кто-то все же будет разгребать и у него не пойдет, пишите в личку, скину исходник архивом.
                    0
                    Честно, не смотрел. Но вижу два варианта(если правильно понял задачу):
                    1) (имхо самый правильный) хранить в памяти не стек, а Bitmap, тогда логика такая:
                    — Создаем битмап в памяти(по умолчанию пустой)
                    — Рисуем на нем
                    — Выводим его на экран
                    — С каждой новой итерацией дорисовываем на нем, что надо, и снова выводим
                    2) (я делаю так. когда ставлю игру на паузу) В поток добавляется переменная state и метод setState, через него изменяем state на true/false (прорисовка и пауза соотв). При добавлении объекта для отрисовки ставим в true, он дорисовывается наверх, и флаг скидывается потоком. Если же стоит false, то draw не вызывается
          0
          Так, ни у кого исключение по выходу из программы не выдает?
            0
            Неа, не выдавало, только когда решил поэкспериментировать и вместо SurfaceView поставил GlSurfaceView — работало но по выходе исключение было
            0
            Короче, все решаемо, но только через костыли.Мигание экрана можно решить 2 способами
            1)либо блокировать именно ту часть экрана, где что-то нужно нарисовать, но если области не хватит, то нарисуется обрезанный объект и если на фоне объекта было уже что-то то это что-то затрется прямоугольной областью (которую собственно мы и блокируем и в которую вписывается рисуемый новый объект). пробовал устанавливать прозрачность канвы, но не выходит пока.
            2)либо воспользоваться следующим мегарешением — все минусы и особенности первого варианта просто напрочь отсутствуют, не надо ничего рассчитывать, нужно просто лишний раз производить блокировку — "холостую блокировку" канвы «lockCanvas(new Rect(0,0,0,0))+unlock», а до или после уже блокировать lockCanvas(null) и рисовать, тогда четные и нечетные рисуютя на одном экране. и тогда кстати можно что хочешь дорисовывать по требованию.

            вот такой костыль, не знаю — баг это или фича SDK или эмулятора.Но костыль этот учтите. Отчитался. всем спасибо.
              0
              Может кто-нибудь знает, можно ли по готовому инстансу Surface создать SurfaceHolder?
              Или готовый SurfaceHolder сделать Parcelable?

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

              Самое читаемое