3

I'm trying to draw a red circle every 5 seconds. I have a draw and a run method, and the run method calls the draw method which draws the circles. However, when the draw method is called from the run method, the surface is not valid so I can't draw anything and the screen is black. How can I make the surface valid, or if I can't, how else can I do this? Here is the code and logcat.

public class RandomCircles extends Activity {

    MySurfaceView mySurfaceView;

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


    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        mySurfaceView.onResumeMySurfaceView();
    }

    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();
        mySurfaceView.onPauseMySurfaceView();
    }

    class MySurfaceView extends SurfaceView implements Runnable {

        Thread thread = null;
        SurfaceHolder surfaceHolder;
        volatile boolean running = false;
        Handler handler = new Handler();

        private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Random random;

        public MySurfaceView(Context context) {
            super(context);
            // TODO Auto-generated constructor stub
            surfaceHolder = getHolder();
            random = new Random();
        }

        public void onResumeMySurfaceView() {
            running = true;
            thread = new Thread(this);
            thread.start();
        }

        public void onPauseMySurfaceView() {
            boolean retry = true;
            running = false;
            while (retry) {
                try {
                    thread.join();
                    retry = false;
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void run() {
            System.out.println("The run method is running");
            // TODO Auto-generated method stub
            Draw();
        }

        public void Draw() {
            System.out.println("The draw method is running");
            if (surfaceHolder.getSurface().isValid()) {
                System.out.println("The surface is valid");
                Canvas canvas = surfaceHolder.lockCanvas();
                //... actual drawing on canvas

                int x = random.nextInt(getWidth());

                if (getWidth() - x < 100)
                    x -= 100;
                else if (getWidth() - x > getWidth() - 100)
                    x += 100;

                int y = random.nextInt(getHeight());

                if (getHeight() - y < 100)
                    y -= 100;
                else if (getHeight() - x > getHeight() - 100)
                    y += 100;

                int radius;
                radius = 100;
                Paint paint = new Paint();
                paint.setStyle(Paint.Style.FILL);
                paint.setColor(Color.WHITE);
                canvas.drawPaint(paint);
                // Use Color.parseColor to define HTML colors
                paint.setColor(Color.parseColor("#CD5C5C"));
                canvas.drawCircle(x, y, radius, paint);

                surfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }
}

There are indicators when the run and draw methods are called, but the indicator after the check for if the screen is valid does not display.

Sygnerical
  • 231
  • 1
  • 3
  • 12
  • The code to call the draw method every 5 seconds hasn't been added yet cause this part isn't even working yet – Sygnerical Jun 20 '15 at 15:49

1 Answers1

4

Implement SurfaceHolder.Callback and only render on the Surface when you receive the surfaceCreated(SurfaceHolder holder) callback. For example:

public MySurfaceView(Context context) {
  super(context);
  surfaceHolder = getHolder();
  surfaceHolder.addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                       //stop render thread here
                }

                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                       //start render thread here
                }
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}

         });
}
MadEqua
  • 1,142
  • 9
  • 19
  • What exactly would those methods do and offer? – Sygnerical Jun 20 '15 at 16:55
  • When the creation of the Surface is finished (therefore it will be valid) `surfaceCreated()` is called and you can start to draw on it. The link on the answer is pretty self-explanaotry: `When used with a SurfaceView, the Surface being held is only available between calls to surfaceCreated() and surfaceDestroyed().` – MadEqua Jun 20 '15 at 17:01
  • Oh so if I move the code in the draw method to the surface method, it should work right? But after the surface is destroyed, can you view what you drew earlier? – Sygnerical Jun 20 '15 at 17:17
  • Yes that should work. But the most correct thing to do, is to start the Thread and let it do the drawing. It's your choice. When the surface is destroyed you can't view it anymore, but that will only happen when the activity is destroyed, so it doesn't really matter. – MadEqua Jun 20 '15 at 17:26
  • What do you mean by start the thread and let it do the drawing, since one of the methods will have to end up drawing it anyways right? – Sygnerical Jun 20 '15 at 17:30
  • Basically I'm talking about the code currently have on `onResumeMySurfaceView()` and `onPauseMySurfaceView()`. That starts and pauses your drawing Thread. It's good practice not to do heavy drawing in the main Thread, but if you are only going to draw basic stuff (ie. not a game or something) it's all fine to do it anywhere you want. – MadEqua Jun 20 '15 at 17:44
  • Where exactly should I do the drawing the proper way? Sorry I'm really inexperienced with android. – Sygnerical Jun 20 '15 at 17:48
  • 1
    Move the code inside `onResumeMySurfaceView()` to `surfaceCreated()`, and the code inside `onPauseMySurfaceView()` to `surfaceDestroyed()`. That should do it. – MadEqua Jun 20 '15 at 18:13
  • That works thanks! Now this here would be going the extra mile but I also want to draw another red circle every 5 seconds on the screen, and I read some stuff about postdelayed but I'm not 100% sure how to use it. Can you help with that? – Sygnerical Jun 20 '15 at 18:27
  • Well you can create a `Handler` object and call its `postdelayed` method with a delay of 5 seconds, and with a `Runnable` that draws your red circle. Start with that and open a new question if you get problems. – MadEqua Jun 20 '15 at 18:59