Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android SurfaceView canvas drawing with a thread

I am experimenting with drawing on a canvas using a thread to create a simple game engine but I'm having some weird issues I cannot explain. The purpose of this "game" is to draw a circle every second on the canvas. This works, but not the way I want it to work, it seems the app is switching between two canvasses and adding a circle to each canvas so you get a switch between two canvasses every second with the same number of circles but in a different place on the canvas.

I don't know what I'm doing wrong, but I'm not that familiar with Treadding, has it something to do with how many cores my android device has or something like that?

My code is shown below, so I just use a launchthread which uses a layoutfile that links to the animationthread which starts a thread and draws a circle on the canvas every second. (You can ignore the touchevent, it isn't uses yet).

The project exists out of a main launchthread:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

which uses this layout file:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">     
        <com.androidtesting.AnimationView
            android:id="@+id/aview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"/>
</FrameLayout>

And my Surfaceview class with an inner Thread class:

class AnimationView extends SurfaceView implements SurfaceHolder.Callback {
    private boolean touched = false;
    private float touched_x, touched_y = 0;
    private Paint paint;
    private Canvas c;
    private Random random;
    private AnimationThread thread;

    public AnimationView(Context context, AttributeSet attrs) {
        super(context, attrs);

        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

        thread = new AnimationThread(holder);
    }

    class AnimationThread extends Thread {
        private boolean mRun;       
        private SurfaceHolder mSurfaceHolder;        

        public AnimationThread(SurfaceHolder surfaceHolder) {
            mSurfaceHolder = surfaceHolder;
            paint = new Paint();
            paint.setARGB(255,255,255,255);
            paint.setTextSize(32);
        }

        @Override
        public void run() {
            while (mRun) {
                c = null;
                try {
                    c = mSurfaceHolder.lockCanvas(null);
                    synchronized (mSurfaceHolder) {                 
                        doDraw(c);
                        sleep(1000);
                    }
                } catch (Exception e) {                 
                    e.printStackTrace();
                }finally {
                    if (c != null) {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }

        private void doDraw(Canvas canvas) {
            //clear the canvas
            //canvas.drawColor(Color.BLACK);                        

            random = new Random();
            int w = canvas.getWidth();
            int h = canvas.getHeight();
            int x = random.nextInt(w-50); 
            int y = random.nextInt(h-50);
            int r = random.nextInt(255);
            int g = random.nextInt(255);
            int b = random.nextInt(255);
            int size = 20;
            canvas.drawCircle(x,y,size,paint);            
            canvas.restore();
        }
        public void setRunning(boolean b) {
            mRun = b;
        }
    }

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

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
           touched_x = event.getX();
           touched_y = event.getY();

           int action = event.getAction();

           switch(action){
                case MotionEvent.ACTION_DOWN:           
                    touched = true;
                    break;
                case MotionEvent.ACTION_MOVE:
                    touched = true;
                    break;        
                default:
                    touched = false;
                    break;
           }

           return true;
    }

    public void surfaceCreated(SurfaceHolder holder) {
        thread.setRunning(true);
        thread.start();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        thread.setRunning(false);
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {
            }
        }
    }
}
like image 375
randomizer Avatar asked Oct 15 '12 12:10

randomizer


People also ask

Can we draw directly on canvas in Android studio?

To draw onto a canvas in Android, you will need four things: A bitmap or a view — to hold the pixels where the canvas will be drawn. Canvas — to run the drawing commands on. Drawing commands — to indicate to the canvas what to draw.

When should I use SurfaceView?

Views are all drawn on the same GUI thread which is also used for all user interaction. So if you need to update GUI rapidly or if the rendering takes too much time and affects user experience then use SurfaceView .

How SurfaceView works?

When you render with SurfaceView, SurfaceFlinger directly composes buffers to the screen. Without a SurfaceView, you need to composite buffers to an offscreen surface, which then gets composited to the screen, so rendering with SurfaceView eliminates extra work.

What is the Surface View?

SurfaceView provides a dedicated drawing surface embedded inside of a view hierarchy. You can control the format of this surface and, if you like, its size; the SurfaceView takes care of placing the surface at the correct location on the screen.


1 Answers

it seems the app is switching between two canvasses

Yes, this is how it works. It is called double buffering and you need to redraw all the frame each time:

The content of the Surface is never preserved between unlockCanvas() and lockCanvas(), for this reason, every pixel within the Surface area must be written.

So you need this line canvas.drawColor(Color.BLACK) to be uncommented in your code.

And you shouldn't call Thread.sleep(1000) while canvas is locked, it will cause starvation issue.

like image 60
Andrei Mankevich Avatar answered Oct 05 '22 02:10

Andrei Mankevich