Android Show/Hide custom SurfaceView - android

I'm trying to implement an easy "show/hide" function of a custom SurfaceView.
Many of suggested methods on this forum doesn't work for me...
for example:
1) surfaceView.setVisibility(SurfaceView.GONE);// or INVISIBLE or VISIBLE
2) relativeLayout.removeView(surfaceView); it works but, destroy the view.
3) surfaceView.setZOrderOnTop(true) or false, dinamically changed doesn't works
4) surfaceView.getDrawingCache(): this method return the SurfaceView as a bitmap so , the sequence is
Hide
get the bitmap from surfaceView and save it (Bitmap btm = surfaceView.getDrawingCache())
create new dummy canvas with saved bitmap, dummyCanvas = new Canvas(btm);
clear the canvas ( Canvas instance is in the MainActivity and initialized in the run() mehtod of the SurfaceView thread.)
Show
surfaceView.draw(dummyCanvas);
Also the last one method don't work, it return a black(jpeg) or empty(png) image.
This is a part of the code:
class CustomSurfaceView extends SurfaceView implements Runnable{
Thread thread = null;
SurfaceHolder surfaceHolder;
volatile boolean running = false;
public CustomSurfaceView(Context context) {
super(context);
this.setWillNotDraw(false);
this.setBackgroundColor(Color.TRANSPARENT);
this.setZOrderOnTop(true);
this.setDrawingCacheEnabled(true);
this.buildDrawingCache();
// get the holder to create the canvas into the Thread.run() method:
// "canvas = surfaceHolder.lockCanvas(null)"
surfaceHolder = getHolder();
surfaceHolder.setFormat(PixelFormat.TRANSPARENT);
}
//getDrawingCache() seems to be the only way to get a bitmap from the
//SurfaceView, because my canvas is not "Canvas c = new Canvas(Bitmap)
//BUT this method only return EMPTY BUT NOT NULL image.
public Bitmap getBitmap(){
return this.getDrawingCache();
}

Related

One canvas, multiple threads

I'm trying to develop a game using a transparent canvas and a surfaceView, I use the canvas to paint and it works fine.
Now I want to add objects that will move on the screen, for example bouncing balls.
I want to use a new thread for the balls, (because otherwise if it's the same thread of the surfaceView it doesn't move smoothly ).
So I'm having trouble of doing this.
Can you please advise me how and when should I send the same canvas to a new ball object and then return it back to the surfaceview.
I'm not sure what the best way to handle this is,
I tried using a ball as a view in the xml layout, and it worked perfect on another thread but it doesn't make sense to create 1000 views when I can just draw them on a canvas.
Any insight will be helpful!
This is my code:
public class SurfaceMyPaint extends SurfaceView implements Runnable
{
Thread t;
SurfaceHolder holder;
Bitmap brush;
boolean isItOk = false;
public SurfaceMyPaint(Context context)
{
super(context);
holder = getHolder();
initial();
}
public void initial()
{
brush= BitmapFactory.decodeResource(getResources(), R.drawable.brush2);
}
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction()== MotionEvent.ACTION_DOWN)
{
x= event.getX();
y= event.getY();
}
if(event.getAction()== MotionEvent.ACTION_MOVE)
{
x = event.getX();
y= event.getY();
}
return true;
}
public void run()
{
while (isItOk == true)
{
if (!holder.getSurface().isValid())
continue;
myCanvas_w = getWidth();
myCanvas_h = getHeight();
if (result == null)
result = Bitmap.createBitmap(myCanvas_w, myCanvas_h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(result);
canvas = holder.lockCanvas(null);
c.drawBitmap(brush, x - (brush.getWidth() / 2), y - (brush.getWidth() / 2), null);
canvas.drawBitmap(result, 0, 0, null);
// How can I add this here: Ball ball = new Ball (canvas)????
holder.unlockCanvasAndPost(canvas);
}
}
public void pause(){
isItOk = false;
while (true){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
t=null;
}
public void resume()
{
isItOk = true;
t = new Thread(this);
t.start();
}
}
Summarizing your rendering code:
Create a screen-sized off-screen bitmap, which you hold in a variable called "result" that isn't declared in the code you show. I assume this is a field, and the allocation only happens once?
Each time, you create a new Canvas for that Bitmap.
You draw on the Bitmap, with drawBitmap().
You copy the Bitmap to the Surface, with drawBitmap().
You want to add a step #5, "draw Ball".
The obvious place to do the ball drawing is in step #3, but I'm going to assume you're not doing it there because that has some relatively static content that you update infrequently but want to blend in every frame.
That being the case, you should just draw the balls onto the Canvas you got back from SurfaceHolder. What you must not do is store a reference to the Surface's Canvas in the Ball object itself, which it appears you want to do (based on new Ball(canvas)). Once you call unlockCanvasAndPost() you must not use that Canvas anymore.
I don't really see an advantage to using multiple threads with your current code. If the "background" Bitmap were also changing you could render that with a different thread, and merge in the current version when you draw, but I suspect that will be more trouble than it's worth -- you will have to deal with the synchronization issues, and I don't think it'll solve your non-smoothness.

Android: Unable to clear initial draw to SurfaceView

I've just started playing around with developing an Android app and have run into some troubles.
The intention of this testing app is to draw a square to the screen that moves towards the bottom right of the screen. Simple as that.
MainActivity class (current entry point) looks like so:
Main Activity class (current entry point) looks like so:
public class MainActivity extends Activity {
Canvas canvas;
GameLoopThread gameThread;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //Super constructor
gameThread=new GameLoopThread(); //Create GameLoop instance
//Create mySurfaceView instance and pass it the new gameloop
MySurfaceView sView=new MySurfaceView(this,gameThread);
//Without this only the bit I cant remove is drawn
sView.setBackgroundColor(Color.TRANSPARENT);
setContentView(sView); //Set the current ContentView to the one we created
//Pass the GameThread the MySurfaceView to repeatedly update
gameThread.setSurfaceView(sView);
gameThread.start(); //Start the thread
}
}
GameLoopThread looks like so:
public class GameLoopThread extends Thread {
protected volatile boolean running;
private MySurfaceView view;
public GameLoopThread(){
}
public void setSurfaceView(MySurfaceView view){
this.view=view;
}
#Override
public void run() {
running=true;
while(running){
Canvas c = null;
c = view.getHolder().lockCanvas(); //Get the canvas
if (c!=null) {
synchronized (view) {
view.draw(c); //Run the doDraw method in our MySurfaceView
}
}
try {
sleep(30, 0); //Throttle
}
catch(InterruptedException e){
e.printStackTrace();
}
if (c != null) {
view.getHolder().unlockCanvasAndPost(c); //Lock and post canvas
}
}
}
public void terminate(){
running=false;
}
}
And finally MySurfaceView looks like so:
public class MySurfaceView extends SurfaceView {
private Bitmap bmp;
private SurfaceHolder holder;
private final GameLoopThread gameLoop;
Paint paint;
float x=0;
float y=0;
public MySurfaceView(Context c, GameLoopThread gThread){
super(c);
holder=getHolder();
gameLoop=gThread;
paint=new Paint();
paint.setColor(Color.CYAN);
paint.setStrokeWidth(10);
holder.addCallback(new CallBack());
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
paint.setColor(paint.getColor() - 1);
canvas.drawRect(x, y, x + 50, y + 50, paint);
x++;
y++;
}
private class CallBack implements SurfaceHolder.Callback{
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
gameLoop.terminate();
while (true) {
try {
gameLoop.join();
break;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
}
}
The Issue
This all works, except that one of the initial few draws to the view "sticks". It remains over top of anything drawn at a later date. See below:
I cannot fathom why this is happening. No amount of clearing fixes the problem. If I stop drawing the 'new' square the 'stuck' square remains. You can see I'm varying the color of the 'new' square to test if it changes the 'stuck' one which would indicate it was being redraw. Clearly it isn't.
Drawing nothing for around 4 loops with a 30ms pause in between each draw results in no 'stuck' square. Starting drawing after those initial 4 results in a square that moves across the screen as it should.
Varying the pause time changes how many loops must be waited, but the relationship doesn't appear to be proportional.
Other Info
This is being run on a Samsung Galaxy SIII Mini
SDK verson 4.0.3
A SurfaceView has two parts, a Surface and a View. When you get a Canvas with lockCanvas(), you're getting a Canvas for the Surface part. The Surface is a separate layer, independent of the layer used for all of the View elements.
You've subclassed SurfaceView and provided an onDraw() function, which the View hierarchy uses to render onto the View portion. My guess is that the View hierachy gets an invalidate and decides to draw on the View part of the SurfaceView. Your experiment of skipping the rendering for the first few loop iterations works because you're skipping the render that happens on the invalidate. Because the Surface layer is drawn behind the View layer, you see the onDraw()-rendered square on top of the other stuff you're rendering.
Normally you don't draw on the View; it's just a transparent place-holder, used by the layout code to leave a "hole" in the View layer where the Surface will show through.
Rename onDraw() to doDraw(), and drop the #override. I don't think there's a reason to subclass SurfaceView at all. That should prevent the SurfaceView from drawing on its View.
Of course, if you want to have a mask layer, perhaps to add rounded corners or a "dirt" effect, drawing on the View is an easy way to accomplish it.
See the Graphics Architecture doc for the full story.

Android - invalidate() in onDraw()

I created two class - one is a class which extends SurfaceView and has a method onDraw(), inside which I'm drawing an image on my canvas:
public class Board extends SurfaceView{
public BitmapFactory myBitmapFactory = new BitmapFactory();
public Bitmap myBitmap = new Bitmap;
public Board(Context context) {
super(context);
setFocusable(true);
}
//here i implement all the abstract classes, which are not important now
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
myBitmap = Bitmap.createScaledBitmap(myBitmapFactory.decodeResource(getResources(), R.drawable.image), 90, 90, false);
Paint paint = new Paint();
canvas.drawBitmap(myBitmap, 20, 20, paint);
}
public void setBitmap(Bitmap b){
this.myBitmap = b;
}
}
And I have also my activity class, where after a button click which runs the method changeImage. I want two change this image which i put on my canvas
public class MyActivity extends Activity {
public Context context = this;
public Board board;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_game);
}
public void changeImage(View view){
Bitmap tmpBitmap = new Bitmap();
BitmapFactory tmpBitmapFactory = new BitmapFactory();
tmpBitmap = Bitmap.createScaledBitmap(tmpBitmapFactory.decodeResource(getResources(), R.drawable.image2), 130, 130, false);
board.setBitmap(tmpBitmap);
board.invalidate();
}
}
But after calling method changeImage nothing is changing - the image is the same as at the beginning. I tried to use invalidate() also inside setBitmap() method, but it also doesn't work. Is this because every time when I call invalidate() I'm creating a new Bitmap or I'm doing something else wrong?
---EDIT---
So ofc, wrong was that:
myBitmap = Bitmap.createScaledBitmap(myBitmapFactory.decodeResource(getResources(), R.drawable.image), 90, 90, false);
was setting all the time the same image. I changed it, but still invalidate() thosent work. I mean the image is not changing. I was looking for the result in other topics, but I did't find any solution, now my classes looks like this:
public class Board extends SurfaceView{
public BitmapFactory myBitmapFactory = new BitmapFactory();
public Bitmap myBitmap = Bitmap.createScaledBitmap(myBitmapFactory.decodeResource(getResources(), R.drawable.image), size, size, false);
public int tmp = 0;
public Paint paint = new Paint();
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
canvas.drawBitmap(myBitmap, 50, 50, paint);
}
public void setBitmaps(Bitmap one){
this.paint = new Paint();
this.myBitmap = one;
this.tmp=4;
this.invalidate();
}
And the method changeImage() inside my activity:
public void changeImage(View view){
Bitmap tmpBitmap1 = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.blue), 230, 230, false);
myGameBoard.setBitmaps(tmpBitmap1);
}
I tried to use: invalidate(), postInvalidate(), but it doesn't work and i hae now idea what am I doing wrong
You update myBitmap in onDraw():
myBitmap = Bitmap.createScaledBitmap(myBitmapFactory.decodeResource(getResources(), R.drawable.image), 90, 90, false);
So your updated image is still not used as you directly override it with another image...
(old answer, which is now obsolete due to fixed question)
There seems something else off in your example code. In setBitmap() you store the new bitmap in pawnBitmap. In the onDraw() method, you do not use pawnBitmap. So it is expected that nothing changes, as you do not use the changed values...
(old answer, which is now obsolete due to change in question)
You are trying to call a static class method with:
Board.setBitmap(tmpBitmap);
Board.invalidate();
(I am wondering why you did not get compile errors... Or did you forgot to mention them?)
As Board is the name of your class. Instead you need to call the instance method.
Board board = ...
board.setBitmap(tmpBitmap);
board.invalidate();
This obviously requires the board instance, which you need to available in your activity class.
Note: if you are calling invalidate() from a non-UI thread, you need to use postInvalidate().

Passing arguments to SurfaceView via Constructor

This might not be the best practice for this but its seems like it would be the easiest. I need to pass a bitmap to a SurfaceView so its ready with onDraw is called. But I cant seem to figure out the syntax for when its assigned. Note the extra bitmap in the constructor.
public class SampleView extends SurfaceView implements SurfaceHolder.Callback {
SurfaceHolder holder;
Bitmap mBitmap = null;
public SampleView (Context context, AttributeSet attrs, Bitmap bitmap) {
super(context, attrs);
if(bitmap != null)
mBitmap = bitmap
SurfaceHolder holder = getHolder();
holder.addCallback(this);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setFilterBitmap(true);
double aspectRatio = ((double) mBitmap.getWidth()) / mBitmap.getHeight();
Rect dest = new Rect(0, 0, this.getWidth(),(int) (this.getHeight() / aspectRatio));
canvas.drawBitmap(mBitmap, null, dest, paint);
}
Then i'm trying to assign it, but I don't have a clue how to set the view and pass the bitmap
setContentView(R.layout.sample); //has my custom view
mSampleView = (SampleView) findViewById(R.id.sampleView);
This maybe me not understanding the ways you can init a view. I have seen examples using "addview()". The base goal of this is to display a bitmap in a surfaceview. Based on my googling you cant access the canvas other than the onDraw so I would need to pass it in when its init (i think). I'm also not sure that the constructor is called when I set the var "mSampleView" or when I use setContentView() to call the layout. If its the latter all this mute and makes me wonder if there is a way to access the canvas or target the main activity. If I could target the main activity i could just access it there when needed but I dont have a very good grasp on scope in android/java.
Is it a bad practice to pass data this way? Would be there a more standard way to passdata other than a global datastore.
To clarify my comment a bit more...You can't pass another object directly to the constructor of a class when its inherited class doesn't support that object in the constructor call.
You would have to pass the bitmap afterwards in another method call, either via the value, or by passing a reference to the object.
Your getting extremely confused with the way the onDraw method works with the objects that it uses. I know because I had trouble understanding it for the first time also.
Here is an example of something you could do.
First construct the view and give the view a bitmap
private Bitmap myReferencedBitmap;
public SampleView (Context context, AttributeSet attrs) {
super(context, attrs);
}
Now pass in the bitmap object you want to use.
public fooBitmap(Bitmap b) {
myReferencedBitmap = b;
}
Now use onDraw with the referenced bitmap
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(myReferencedBitmap, null, null, paint);
}
Now whenever you modify the object b that you passed into the SampleView class, and then invalidate the view, the onDraw method will use the new object, because myReferencedBitmap holds a reference to the (object) value.
Another thing to look at is byref vs byval in Java.
Good luck.

Drawing Bitmap in Android?

My project is about image processing in android.I have a bitmap that I have loaded from a resource file (an PNG image). I want to draw it. But I couldn't.
Here my code snippet:
mB = BitmapFactory.decodeResource(getResources(), R.drawable.picture);
Canvas c = new Canvas(mB);
Paint p = new Paint();
c.drawBitmap(mB,0,0,p);
it didn't work. Is the code true? .Is there any thing more that I must do?
You should use an ImageView instead and load it by
imageView.setImageResource(R.drawable.picture);
If you want to manually draw it with a Canvas, you have to use a canvas that is passed into a draw() method and implement a Custom View.
Update to add example CustomView:
public class CustomView extends View {
private Paint mPaint;
private Drawable mDrawable;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mDrawable = context.getResources().getDrawable(R.drawable.some_drawable);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawable.draw(canvas);
}
}
There are a couple of things you are missing.
First, I think you're misunderstanding the Canvas(Bitmap b) constructor. The Bitmap passed in there is one that the Canvas will draw into. This could be just a new Bitmap that you constructed.
Second, it is recommended that you use the Canvas that is passed to you in your View's onDraw method. Presumably that View is one from your Activity, either fetched from your XML layout via findViewById or constructed and passed to setContentView in the Activity's onCreate() method.
So, you are going to have to subclass View and override the onDraw method to get your drawing done. Something like:
public class MyView extends View {
#Override
public void onDraw (Canvas c) {
Bitmap mB = BitmapFactory.decodeResource(this.getContext().getResources(), R.drawable.picture);
c.drawBitmap(mB, 0, 0, null);
}
}
Then, in your Activity, you'll need to create an instance of your new View and pass it to the Activity via setContentView:
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mv = new MyView(this);
setContentView(mv);
}
You can instead call the setContentView(View v, ViewGroup.LayoutParameters lp) overload if you want to set up the LayoutParameters.
I haven't tested any of this, but it should at least get you on the right path.

Categories

Resources