当前位置: 动力学知识库 > 问答 > 编程问答 >

android - Smooth Painting in a SurfaceView

问题描述:

I'm working on a paint-like app, but I've run it to some troubles.

I know I have re-draw all the objects each frame, but this in turn makes performance slow further on when many objects are visible.

I've also noticed that clearing the background only once, then adding objects to be drawn to the surface when painting is active makes the screen flash almost to the point of inducing epilepsy.

So what is the best way to make a paint app 100% smooth no matter how many objects that are drawn?

Epilepsy-inducing code:

public void run()

{

while (running)

{

if (!holder.getSurface().isValid())

continue;

if (drawObjectsToAdd.size() == 0)

continue;

drawing = true;

Canvas c = holder.lockCanvas();

if (redrawBackground)

{

c.drawARGB(255, 255, 255, 255);

redrawBackground = false;

}

drawObjects.addAll(drawObjectsToAdd);

drawObjectsToAdd.clear();

for (DrawObject draw : drawObjects)

{

draw.Draw(c, paint);

}

holder.unlockCanvasAndPost(c);

drawing = false;

}

}

this adds a new object once it's been added outside of the thread, but it makes the screen flash so much it gives me headaches - and I only redraw the background once.

Smooth at first but becomes laggy after a while, probably due to having to add hundreds and hundreds of objects in the end:

public void run()

{

while (running)

{

if (!holder.getSurface().isValid())

continue;

if (drawObjectsToAdd.size() > 0)

{

drawObjects.addAll(drawObjectsToAdd);

drawObjectsToAdd.clear();

redraw = true;

}

if (clear)

{

drawObjectsToAdd.clear();

drawObjects.clear();

redraw = true;

clear = false;

}

if (!redraw)

continue;

drawing = true;

Canvas c = holder.lockCanvas();

c.drawARGB(255, 255, 255, 255);

for (DrawObject draw : drawObjects)

{

try

{

draw.Draw(c, paint);

}

catch (Exception ex) { }

}

holder.unlockCanvasAndPost(c);

drawing = false;

redraw = false;

}

}

I, at least for this app wanna store all the objects that are added so it doesn't matter how it's painted as long as it's smooth all the way. Preferably, add a circle - it will render a new Bitmap on to the surface, instead of having to redraw lots of objects each frame - instead store them but do not add objects already drawn.

UPDATE

Pseudo-Code of how I want it to be:

If no background is drawn

Draw background color

If new items have been added

Draw only new items to the background

Store new items in objects list

This way, we'll only draw the background once. When a new item is added, only draw that item to the existing surface. When the objects increases, looping through every item will reduce performance greatly and it will not be pleasant to work with.

UPDATE 2:

private void Draw()

{

while (running)

{

if (!holder.getSurface().isValid())

continue;

if (picture == null)

{

picture = new Picture();

Canvas c = picture.beginRecording(getWidth(), getHeight());

c.drawARGB(255, 255, 255, 255);

picture.endRecording();

}

if (drawObjectsToAdd.size() > 0)

{

drawObjects.addAll(drawObjectsToAdd);

drawObjectsToAdd.clear();

Canvas c = picture.beginRecording(getWidth(), getHeight());

for (DrawObject draw : drawObjects)

{

draw.Draw(c, paint);

}

picture.endRecording();

drawObjects.clear();

}

Canvas c2 = holder.lockCanvas();

c2.drawPicture(picture);

holder.unlockCanvasAndPost(c2);

}

}

This last method from Update 2 makes it render all the lines like the "Snake game" when adding circles. Looks like a moving snake on a background, where some of it's circles disappear one frame and others don't the next. If I skip to redraw each frame, it will instead vary which of these circles that are visible.

网友答案:

what about that Picture implementation? increase MAX_DRAWERS to some reasonable value and see how it works

class SV extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private static final int MAX_DRAWERS = 8;

    private boolean mRunning = true;
    private List<Picture> mPictures = new LinkedList<Picture>();
    private List<Drawer> mDrawers = new LinkedList<Drawer>();
    private Paint mPaint;

    public SV(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setColor(0xffffff00);
        getHolder().addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        new Thread(this).start();
    }

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

    @Override
    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
        mRunning = false;
        notify();
    }

    @Override
    public synchronized boolean onTouchEvent(MotionEvent event) {
        Drawer drawer = new Drawer(event.getX(), event.getY());
        mDrawers.add(drawer);

        if (mDrawers.size() > MAX_DRAWERS) {
            Picture picture = new Picture();
            Canvas canvas = picture.beginRecording(getWidth(), getHeight());
            mPaint.setAlpha(0xbb);
            for (Drawer dr : mDrawers) {
                dr.draw(canvas, mPaint);
            }
            picture.endRecording();
            mPaint.setAlpha(0xff);
            mPictures.add(picture);
            mDrawers.clear();
            Log.d(TAG, "onTouchEvent new Picture");
        }
        notify();
        return false;
    }

    @Override
    public synchronized void run() {
        SurfaceHolder holder = getHolder();
        while (mRunning) {
//            Log.d(TAG, "run wait...");
            try {
                wait();
            } catch (InterruptedException e) {
            }
            if (mRunning) {
//                Log.d(TAG, "run woke up");
                Canvas canvas = holder.lockCanvas();
                canvas.drawColor(0xff0000ff);
                for (Picture picture : mPictures) {
                    picture.draw(canvas);
                }
                for (Drawer drawer : mDrawers) {
                    drawer.draw(canvas, mPaint);
                }
                holder.unlockCanvasAndPost(canvas);
            }
        }
        Log.d(TAG, "run bye bye");
    }

    class Drawer {
        private float x;
        private float y;
        public Drawer(float x, float y) {
            this.x = x;
            this.y = y;
        }
        public void draw(Canvas canvas, Paint paint) {
            canvas.drawCircle(x, y, 8, paint);
        }
    }
}
网友答案:

You generate your list of objects each and every frame, just to discard it after drawing. Why? Generate your list of draw objects once, then draw them.

That's all you need for your drawing thread...

public void run() {
while (running)
{

    Canvas c = holder.lockCanvas();

    c.drawARGB(255, 255, 255, 255);

    for (DrawObject draw : drawObjects)
    {
        try
        {
            draw.Draw(c, paint);
        }
        catch (Exception ex) { }
    }


    holder.unlockCanvasAndPost(c);

}

}

分享给朋友:
您可能感兴趣的文章:
随机阅读: