2

I am currently having trouble with drawing to a custom SurfaceView from a thread that is not on my main UI. This SurfaceView takes up the entirety of the screen (Galaxy S3 in full screen) and must be updated from several sources.

The problem is that the custom SurfaceView will not save changes between updates to UI.

The custom SurfaceView class is defined within the activity that uses it, and the thread (let's call it DrawingThread) that runs periodic checks for updates is defined within the custom SurfaceView class. I have done this similar to this tutorial.

Another external thread is updating a list of changes that must be made to the UI. DrawingThread checks this list every 50ms or so, and if the list is not empty, calculates a rectangle that must be redrawn.

// Thread waits for change request to UI or kill order
// Build Rect at point of Touch
try {
    Canvas = _surfaceHolder.lockCanvas(Rect); 
    synchronized (_surfaceHolder) {
        postInvalidate(); //Calls custom SurfaceView's onDraw() with the correct Canvas
    }
} finally {
    if (Canvas != null) {
        _surfaceHolder.unlockCanvasAndPost(Canvas);
    }
}
// Loop

My onDraw() method for the custom SurfaceView is below:

@Override
public void onDraw(Canvas canvas) {
    // Initialize vars for rectangle
    Paint.setStyle(Style.FILL);

    for (MapChanges change : ExternalClass.getMapChanges()) {
        // Build Rect at point of Touch
        // Select Color for Paint   
        canvas.drawRect(Rect, Paint);
        ExternalClass.removeMapChange(change); //Thread safe
    }
}

This works flawlessly except for the fact that it is forgetting changes from any previous onDraw() call. I am currently testing this by using an OnTouchListener.

Code procedure as I understand it:

  • Touch adds a change request for the UI to the list in the ExternalClass.
  • DrawingThread sees the list is no longer empty, safely grabs the Canvas, and postInvalidates() to call custom SurfaceView's onDraw().
  • onDraw() updates the UI and removes the change request from ExternalClass.
  • DrawingThread posts the Canvas and goes back to waiting.

My expected behavior is that I will accumulate dots on my screen with successive touches. Instead, each touch clears the screen, leaving me with the background and exactly one dot where I last touched the screen.

I find this vastly confusing as lockCanvas(Rect) specifically mentions that it will save changes between locks by designating a "dirty" section. Google searches have lead me to believe that most users find the opposite problem: that they want the View redrawn but must do so every update manually.

Why is the program drawing a clean SurfaceView from scratch each UI update despite my designation of a "dirty" section to redraw?

No doubt someone will reference me to this previously answered question where the programmer was advised to use bitmaps. I am trying to avoid this due to how new I am to this and my desire to not work with large amounts of memory (updating the UI is a tiny portion of my code). Working with simple shapes directly on a View would be ideal for me. Additionally it is presented as a workaround. Is this a flaw in the Android API?

Have been searching here and Google for several hours now. Hope I have not missed anything blatantly obvious.

Community
  • 1
  • 1
Bandoth
  • 87
  • 2
  • 10

1 Answers1

0

I managed to find a workaround by taking the OnDraw() out of the equation. New DrawingPanel below:

class DrawingPanel extends SurfaceView implements Runnable, SurfaceHolder.Callback {

    Thread thread = null;
    SurfaceHolder surfaceHolder;
    volatile boolean running = false;

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

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

        surfaceHolder = getHolder();
    }

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


    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {          
        setWillNotDraw(false); //Allows us to use invalidate() to call onDraw()

        if (!running) {
            running = true;
            thread = new Thread(this);
            thread.start();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        try {
            running = false;    //Tells thread to stop
            thread.join();      //Removes thread from mem.
        } catch (InterruptedException e) {}
    }   

    @Override
    public void run() {
        Canvas canvas;
        Rect myRect = new Rect();
        boolean firstRun = true;

        ChangeRequest ChangeReq;

        myPaint.setStyle(Style.FILL);

        while(running) {
            try {
                Thread.sleep(50);
            } catch (Exception e) {}

            canvas = null;

            ChangeReq = getUIChangeRequests();

            if (!ChangeReq.isEmpty() || (firstRun)) {                   
                if(surfaceHolder.getSurface().isValid()) {
                    if (!firstRun) {  

                        // Calculate dirty space for editing
                        x = ChangeReq.getX();
                        y = ChangeReq.getY();

                        try {
                            myRect.set(x*RectSize, y*RectSize, x*RectSize + RectSize, y*RectSize + RectSize);
                            myPaint.setColor(Color.RED);

                            canvas = surfaceHolder.lockCanvas(myRect);

                            synchronized (surfaceHolder) {
                                // Draw
                                canvas.drawRect(myRect, myPaint);

                            }

                            // Remove ChangeRequest
                            ChangeReq.remove();

                        } finally {
                            if (canvas != null) {
                                surfaceHolder.unlockCanvasAndPost(canvas);
                            }
                        }
                    } else {
                        try {
                            // Initialize canvas as all one color
                            myRect.set(0, 0, getWidth(), getHeight());
                            myPaint.setColor(Color.DKGRAY);

                            canvas = surfaceHolder.lockCanvas();

                            synchronized (surfaceHolder) {
                                //Draw
                                canvas.drawRect(myRect, myPaint); 
                            }

                            firstRun = false;
                        } finally {
                            if (canvas != null) {
                                surfaceHolder.unlockCanvasAndPost(canvas);
                            }
                        }
                    }
                }
            }
        }
    }   
}

This solves my drawing problem but brings up an interesting question of why I must initialize using surfaceHolder.lockCanvas(); and only then am able to draw using surfaceHolder.lockCanvas(Rect); I will ask this in another question.

Bandoth
  • 87
  • 2
  • 10