简体   繁体   中英

Android : moving bmp leaving a trail ( Surface View and Canvas )

I was trying to animate a sprite for an android app and i've encounter a small problem. I'm drawing on a surfaceView that i add on top of already existing layouts. On this surfaceView, i wanted to animate few sprites so they would walk along a path.

So this is the result i'm facing right now : 在此处输入图片说明

The walking sprite is leaving a trail. So i decided to google this problem and apparently i had to clear the canvas first before drawing on it.
This was my onDraw method before :

public void onDraw(Canvas canvas) {
    update();
    int srcX = currentFrame * width;
    int srcY = directionX * height;
    Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
    Rect dst = new Rect(x, y, x + width, y + height);
    canvas.drawBitmap(bmp, src, dst, null);

}

And this was my onDraw method after

public void onDraw(Canvas canvas) {
    canvas.drawRGB(0, 0, 0);
    update();
    int srcX = currentFrame * width;
    int srcY = directionX * height;
    Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
    Rect dst = new Rect(x, y, x + width, y + height);
    canvas.drawBitmap(bmp, src, dst, null);

}

So i now have this as a result

在此处输入图片说明

which is great because it leaves no more trail BUT i cant see the layouts underneath. Is there anyway i can get the best of both world ? I thought clearing the canvas would work but it obviously doesnt not work as expected.

I'll post my code below

SurfaceView :

public class MonsterView extends SurfaceView {

private Bitmap monsterImg;
private SurfaceHolder holder;
private MonsterThread mainThread;
private Sprite monsterSprite;
private int x;
private int xSpeed = 1;

public MonsterView(Context context) {
    super(context);
    this.mainThread = new MonsterThread(this);
    this.x = 0;


    holder = getHolder();
    holder.setFormat(PixelFormat.TRANSPARENT);

    holder.addCallback(new SurfaceHolder.Callback() {

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            mainThread.setRunning(true);
            mainThread.start();
        }

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

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            boolean retry = true;
            mainThread.setRunning(false);
            while (retry) {
                try {
                    mainThread.join();
                    retry = false;
                } catch (InterruptedException e) {
                }
            }

        }
    });
    setZOrderOnTop(true);
    monsterImg = BitmapFactory.decodeResource(getResources(), R.drawable.fire);
    monsterSprite = new Sprite(this,monsterImg);
}

@Override
public void onDraw(Canvas canvas) {
    if (x == getWidth() - monsterImg.getWidth()) {
        xSpeed = -1;
    }
    if (x == 0) {
        xSpeed = 1;
    }
    x = x + xSpeed;
    monsterSprite.onDraw(canvas);
}

Thread :

public class MonsterThread extends Thread {
private MonsterView monsterSurface;
private boolean running;
static final long FPS = 35;

public MonsterThread(MonsterView monsterSurface){
    this.monsterSurface = monsterSurface;
    this.running = false;
}

@Override
public void run() {
    long ticksPS = 1000 / FPS;
    long startTime;
    long sleepTime;

    while(running){
        Canvas c = null;
        startTime = System.currentTimeMillis();
        try{
            c = monsterSurface.getHolder().lockCanvas();
            synchronized (monsterSurface.getHolder()){

                monsterSurface.onDraw(c);
            }
        } finally {
            if( c!= null){
                monsterSurface.getHolder().unlockCanvasAndPost(c);

            }
        }
        sleepTime = ticksPS-(System.currentTimeMillis() - startTime);
        try {
            if(sleepTime > 0)
                sleep(sleepTime);
            else
                sleep(10);
        } catch (Exception e){}
    }
}

public MonsterView getMonsterSurface() {
    return monsterSurface;
}

public void setMonsterSurface(MonsterView monsterSurface) {
    this.monsterSurface = monsterSurface;
}

public boolean isRunning() {
    return running;
}

public void setRunning(boolean running) {
    this.running = running;
}

Sprite :

public class Sprite {
private static final int BMP_ROWS = 4;
private static final int BMP_COLUMNS = 3;
private int x = 0;
private int y = 0;
private int xSpeed = 5;
private MonsterView monsterView;
private Bitmap bmp;
private int currentFrame = 0;
private int width;
private int height;

private int directionX;

public Sprite(MonsterView monsterView, Bitmap bmp) {
    this.monsterView = monsterView;
    this.bmp=bmp;
    this.width = bmp.getWidth() / BMP_COLUMNS;
    this.height = bmp.getHeight() / BMP_ROWS;
    this.directionX = 2;
}

private void update() {
    if (x > monsterView.getWidth() - width - xSpeed) {
        xSpeed = -5;
        directionX = 1;
    }
    if (x + xSpeed < 0) {
        xSpeed = 5;
        directionX = 2;
    }
    x = x + xSpeed;
    currentFrame = ++currentFrame % BMP_COLUMNS;
    Log.d("test", ""+currentFrame);
}

public void onDraw(Canvas canvas) {
    update();
    int srcX = currentFrame * width;
    int srcY = directionX * height;
    Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
    Rect dst = new Rect(x, y, x + width, y + height);
    canvas.drawBitmap(bmp, src, dst, null);

}

You're using setZOrderOnTop() , which is putting the Surface part of the SurfaceView on a layer above everything else. (By default, it's a separate layer below everything else.) When you clear it with canvas.drawRGB(0, 0, 0) you're setting the entire layer to opaque black, which is going to obscure the View layer beneath it.

If instead you clear it to transparent black, with canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) , you should get the results you want.

FWIW, you shouldn't override onDraw() if you're only drawing on the Surface. The onDraw() method is called by the View hierarchy to draw on the View part of the SurfaceView. If something manages to invalidate the SurfaceView's View, your onDraw() will be called, and you'll end up with a character sprite on the View layer. (Depending on your layout this may not be visible.) Just give the method a different name.

There are a number of ways to handle this, but generally, the way to do this is by maintaining a background layer (which is just another image), and sprite layers. Instead of clearing, at each frame you blit (copy) your background onto the surface (which erases everything), then you blit your sprites.

So you're going to have to draw the background bitmap onto the surface first before drawing your sprite. Right now you're drawing black which is covering your background layouts.

Alternatively you might be able to do canvas.drawRect with a Paint with paint.setColor to Color.Transparent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM