I find many SurfaceView demos use hasSurface. But I can't understand it. What is the meaning of hasSurface? Is there anybody help me?
import android.content.Context;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private SurfaceHolder holder;
private MySurfaceViewThread mySurfaceViewThread;
private boolean hasSurface;
MySurfaceView(Context context) {
super(context);
init();
}
private void init() {
// Create a new SurfaceHolder and assign this
// class as its callback.
holder = getHolder();
holder.addCallback(this);
hasSurface = false;
}
public void resume() {
// Create and start the graphics update thread.
if (mySurfaceViewThread == null) {
mySurfaceViewThread = new MySurfaceViewThread();
if (hasSurface == true)
mySurfaceViewThread.start();
}
}
public void pause() {
// Kill the graphics update thread
if (mySurfaceViewThread != null) {
mySurfaceViewThread.requestExitAndWait();
mySurfaceViewThread = null;
}
}
public void surfaceCreated(SurfaceHolder holder) {
hasSurface = true;
if (mySurfaceViewThread != null)
mySurfaceViewThread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
hasSurface = false;
pause();
}
public void surfaceChanged(SurfaceHolder holder, int format,
int w, int h) {
if (mySurfaceViewThread != null)
mySurfaceViewThread.onWindowResize(w, h);
}
class MySurfaceViewThread extends Thread {
private boolean done;
MySurfaceViewThread() {
super();
done = false;
}
#Override
public void run() {
SurfaceHolder surfaceHolder = holder;
// Repeat the drawing loop until the thread is stopped.
while (!done) {
// Lock the surface and return the canvas to draw onto.
Canvas canvas = surfaceHolder.lockCanvas();
// TODO: Draw on the canvas!
// Unlock the canvas and render the current image.
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
public void requestExitAndWait() {
// Mark this thread as complete and combine into
// the main application thread.
done = true;
try {
join();
} catch (InterruptedException ex) { }
}
public void onWindowResize(int w, int h) {
// Deal with a change in the available surface size.
}
}
}
The Surface is created while the SurfaceView's window is visible so you need to know if your code can or cannot access it yet. You should implement surfaceCreated() and surfaceDestroyed() to be informed when the Surface is created and destroyed as the window is shown and hidden, so basically hasSurface (or whatever name you use) keeps the last known status of your surfrace for simplicity.
We cannot acquire the Canvas from the SurfaceHolder as long as the Surface is not yet valid. However, we can check whether the Surface has been created or not via the following statement:
boolean isCreated = surfaceHolder.getSurface().isValid();
I assume hasSurface is similar to getSurface().isValid().
#Override
public void run() {
SurfaceHolder surfaceHolder = holder;
// Repeat the drawing loop until the thread is stopped.
while (!done) {
if(!holder.getSurface().isValid())
continue;
// Lock the surface and return the canvas to draw onto.
Canvas canvas = surfaceHolder.lockCanvas();
// TODO: Draw on the canvas!
// Unlock the canvas and render the current image.
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
Related
I have a SurfaceView and a GameThread which updates game state and draw a bitmap on canvas. surface.update() below updates game variables outside the lock and creates a bitmap that has to be drawn on canvas. After locking surface.doDraw draws that bitmap on canvas. Update() is outside UI thread locking is to improve performance. Is this approach wrong? Do I have to update under the lock?
public class GameThread extends Thread {
private SurfaceHolder surfaceHolder;
private GameViewSurface surface;
private boolean running = false;
public GameThread(SurfaceHolder holder, GameViewSurface GameSurface) {
surfaceHolder = holder;
surface = GameSurface;
}
public void setRunning(boolean run) {
running = run;
}
public boolean getRunning() {
return running;
}
#Override
public void run() {
while (running) {
Canvas canvas=null;
try {
if(!surfaceHolder.getSurface().isValid())
continue;
surface.update();
canvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
surface.doDraw(canvas);
}
} catch(Exception p){
}finally {
if (canvas != null) {
try {
surfaceHolder.unlockCanvasAndPost(canvas);
}catch (Exception e){}
}
}
}
}
}
To preface, I'm probably going to work with Views instead of SurfaceView given this weird interaction, but my curiosity is getting the better of me and I want to know what's going on.
So I have a matrix class variable. I run matrix.setTranslate() once, and every other frame I say matrix.setTranslate(). After this I call canvas.drawBitmap(, matrix, null). After 100 frames, I stop drawing the bitmap.
Here's the code:
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// matrix = new Matrix(); //Doesn't matter if I add this.
// matrix.reset(); //This doesn't matter either.
if (!once) {
matrix.setTranslate(100, 100);
} else {
matrix.setTranslate(800,800);
}
once = true;
if (++timer < 100) {
canvas.drawBitmap(ball, matrix, null);
}
}
What I expect to happen: Three possibilities.
Only the bottom right bitmap is visible since the entire screen was invalidated
Both bitmaps are visible because SurfaceView was smart with the dirty rectangle
Nothing is shown because nothing was drawn.
What actually happens: The top left bitmap blinks, bottom right bitmap displays solidly.
I'm pretty sure I have all of my bases covered:
All class variables are properly set in the constructor.
SurfaceView.onDraw() is called every 33 millis in its own thread.
Thread calls lockCanvas, then onDraw, then unlockCanvasAndPost
So, what's going on here? And bonus question, do these ghost images consume any extra resources and how can I clear them?
Rest of the code, mostly boilerplate:
public class FullscreenActivity extends Activity {
GameSurface ball;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ball = new GameSurface(this);
setContentView(ball);
}
}
class GameSurface extends SurfaceView implements SurfaceHolder.Callback {
GameThread thread;
boolean once = false;
Bitmap ball;
Matrix matrix;
int timer = 0;
public GameSurface(Context context) {
super(context);
ball = BitmapFactory.decodeResource(getResources(),R.drawable.football);
matrix = new Matrix();
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// matrix = new Matrix(); //Doesn't matter if I add this.
// matrix.reset(); //This doesn't matter either.
if (!once) {
matrix.setTranslate(100, 100);
} else {
matrix.setTranslate(800,800);
}
//matrix.postRotate(timer); //For even more weirdness...
once = true;
if (++timer < 100) {
canvas.drawBitmap(ball, matrix, null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new GameThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
class GameThread extends Thread {
private SurfaceHolder surfaceHolder;
private GameSurface gameView;
private boolean run = false;
public GameThread(SurfaceHolder surfaceHolder, GameSurface gameView) {
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
public void setRunning(boolean run) {
this.run = run;
}
#Override
public void run() {
Canvas c;
while (run) {
c = null;
try {
Thread.sleep(33);
}
catch(InterruptedException e) {}
try {
c = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
gameView.onDraw(c);
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
I have this android application.
It use a SurfaceView, from where I get the Surface through the SurfaceHolder.
It also use ExoPlayer to stream videos. However I have instantiated an ImageReader, getting its Surface and passing to the ExoPlayer.
Now, I am in the ImageReader.OnImageAvailableListener#onImageAvailable and I access the latest Image.
I want to manipulate the Image and send the new data to the "SurfaceView" Surface.
How can I "draw" an android.media.Image to an android.view.Surface ?
The question is not clear to me.
The way to get android.media.Image is by the Camera2 API, and there you can provide a surface and the "camera" will draw over it. Please refer to Camera2Video example
Another way to get the Image object is from ImageReader (while decoding video for example). In this case you want to draw the image, but you can not provide a surface to the ImageReader(there is an internal surface that is not displayed). In this case you can draw the Image on a SurfaceView.
Assuming this is the case, you need to convert an Image to a Bitmap objects.
You have discussion about how perform this here
Possible duplicate of: how to draw image on surfaceview android
First get your canvas by using lockCanvas() (see here), second get your image and make it a drawable using:
my_bitmap = Bitmap.createBitmap(
MediaStore.Images.Media.getBitmap(getContentResolver(), uri),
0,0,90, 90);
drawable=new BitmapDrawable(my_bitmap);
After that you can draw the drawable to the locked canvas and use unlockCanvasAndPost (Canvas canvas) to post the updated canvas back to the surfaceview.
here is the answer for your question.
MainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mySurfaceView mySurfaceView = new mySurfaceView(getApplicationContext());
setContentView(mySurfaceView);
}
}
mySurfaceView.java
public class mySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private TutorialThread _thread;
public mySurfaceView(Context context) {
super(context);
getHolder().addCallback(this);
_thread = new TutorialThread(getHolder(), this);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap _scratch = BitmapFactory.decodeResource(getResources(),
R.drawable.icon);
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(_scratch, 10, 10, null);
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
public void surfaceCreated(SurfaceHolder arg0) {
_thread.setRunning(true);
_thread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
boolean retry = true;
_thread.setRunning(false);
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
class TutorialThread extends Thread {
private SurfaceHolder _surfaceHolder;
private mySurfaceView _panel;
private boolean _run = false;
public TutorialThread(SurfaceHolder surfaceHolder, mySurfaceView panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.onDraw(c);
}
} finally {
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
public class CannonView extends SurfaceView implements SurfaceHolder.Callback {
CannonThread cannonThread;
private Paint blockerPaint;
public CannonView(Context context, AttributeSet attrs) {
super(context, attrs);
blockerPaint = new Paint();
blockerPaint.setStrokeWidth(10.0f);
getHolder().addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
cannonThread = new CannonThread(holder);
cannonThread.running(true);
cannonThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
cannonThread.running(false);
while (retry) {
try {
cannonThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
private class CannonThread extends Thread {
boolean setRunning;
SurfaceHolder surfaceHolder;
public CannonThread(SurfaceHolder holder) {
setRunning = true;
surfaceHolder = holder;
}
public void running(boolean isRunning) {
setRunning = isRunning;
}
#Override
public void run() {
Canvas canvas = null;
while (setRunning) {
try {
canvas = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
canvas.drawLine(0, 0, 100, 100, blockerPaint);
}
} finally {
if (canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
Although above code is very simple
but it is not drawing anything on my activity ..
Logcat says i am doing too much work on main thread ..62 frame skipped ..
Please help
Well this isn't my style of coding, so I decided that you need to simplify things a bit. You used the android api guides but they suck.
Watch the following videos and you should be fine.
http://www.youtube.com/watch?v=wUmId0rwsBQ&list=SP2F07DBCDCC01493A&index=67
http://www.youtube.com/watch?v=0wy907WZFiA&list=SP2F07DBCDCC01493A
http://www.youtube.com/watch?v=ZMcYbf9Hhe4&list=SP2F07DBCDCC01493A
http://www.youtube.com/watch?v=yowNavIDzzE&list=SP2F07DBCDCC01493A
I'd like to open up a SurfaceView with a icon placed in the center of the screen when an application is first started. I'm evoking icon creation using an implementation of SurfaceHolder.Callback to track when the 'Canvas' object is ready. My question is is there a better way? Are there less cumbersome methods of starting a SurfaceView with some Drawables loaded on creation without having to resort to placing draw logic within a callback object?
Here's my code for reference. First the object which does drawing:
public class CanvasDraw{
protected final SurfaceHolder mHolder;
protected final Drawable mDrawable;
public interface DrawLogic{
void draw(Rect _surface);
}
public CanvasDraw(SurfaceView _view, Drawable _drawable){
mHolder = _view.getHolder();
mDrawable = _drawable;
}
public void draw(DrawLogic _logic){
Canvas canvas = null;
try{
canvas = mHolder.lockCanvas();
if( canvas != null ){
Log.i("DRAWABLE", "Drawing " + mDrawable.toString());
_logic.draw( mHolder.getSurfaceFrame() );
mDrawable.draw(canvas);
}
else{
Log.i("DRAWABLE", "Canvas null valued");
}
}
finally{
if( canvas != null ){
mHolder.unlockCanvasAndPost(canvas);
}
}
}
}
and then my private callback object:
private class DrawOnceCallback implements SurfaceHolder.Callback {
private final SurfaceHolder mHolder;
public DrawOnceCallback(SurfaceHolder _holder ){
mHolder = _holder;
mHolder.addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("SURFACEHOLDER","Surface created. Canvas available.");
mDrawTiles.draw( new CanvasDraw.DrawLogic(){
#Override
public void draw(Rect _surface) {
mTiles.setBounds(
_surface.centerX() - mDrawWidth/2,
_surface.centerY() - mDrawHeight/2,
_surface.centerX() + mDrawHeight/2,
_surface.centerY() + mDrawHeight/2);
}
});
mHolder.removeCallback(this);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {Log.i("SURFACEHOLDER","Surface destroyed.");}
}
I did something like this simply by assigning the image I wanted to a view in the layout XML file. If you've got to wait until your canvas and all that is ready anyways why not just use a default property? My opening splash screen was just a view with
android:background="#drawable/splash"
added to its layout.