问题
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, andpostInvalidates()
to call customSurfaceView
'sonDraw()
.onDraw()
updates the UI and removes the change request fromExternalClass
.DrawingThread
posts theCanvas
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.
回答1:
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.
来源:https://stackoverflow.com/questions/15378493/how-to-partially-redraw-custom-surfaceview-using-separate-thread-without-losing