Android: Unable to clear initial draw to SurfaceView - android

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.

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.

Can you draw multiple Bitmaps on a single View Method?

currently I am trying to make an animation where some fish move around. I have successfully add one fish and made it animate using canvas and Bitmap. But currently I am trying to add a background that I made in Photoshop and whenever I add it in as a bitmap and draw it to the canvas no background shows up and the fish starts to lag across the screen. I was wondering if I needed to make a new View class and draw on a different canvas or if I could use the same one? Thank you for the help!
Here is the code in case you guys are interested:
public class Fish extends View {
Bitmap bitmap;
float x, y;
public Fish(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.fish1);
x = 0;
y = 0;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, x, y, null);
if (x < canvas.getWidth())
{
x += 7;
}else{
x = 0;
}
invalidate();
}
}
You can draw as many bitmaps as you like. Each will overlay the prior. Thus, draw your background first, then draw your other images. Be sure that in your main images, you use transparent pixels where you want the background to show through.
In your code, don't call Invalidate() - that's what causes Android to call onDraw() and should only be called from somewhere else when some data has changed and needs to be redrawn.
You can do something like this, where theView is the view containing your animation:
In your activity, put this code in onCreate()
myAnimation();
Then
private void myAnimation()
{
int millis = 50; // milliseconds between displaying frames
theView.postDelayed (new Runnable ()
{
#Override public void run()
{
theView.invalidate();
myAnimation(); // you can add a conditional here to stop the animation
}
}, millis);
}

How to use a SurfaceView from an Adapter (Android)

I have a GridView. I would like each item to be an object derived form SurfaceView. I have created a class which extends SurfaceView, and an Adapter whose getView() returns an instance of this.
All I get is a little black square for each grid item. The SurfaceView's draw() is never called (shown by adding trace) and neither is its surfaceCreated().
How do I do this in a way which allows me to draw stuff on the SurfaceView please? I think the crux of the problem is that surfaceCreated() is never called. Why not?
This is my Adapter:
public class LevelAdapter extends BaseAdapter
{
...
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LevelNumberView number = new LevelNumberView(m_context);
number.setLayoutParams(new GridView.LayoutParams(70, 70));
number.setPadding(1, 1, 1, 1);
number.masDraw(); // Experiment - but it doesn't work
return number;
}
}
Then my SurfaceView class:
public class LevelNumberView extends SurfaceView
implements SurfaceHolder.Callback
{
...
#Override
public void surfaceCreated(SurfaceHolder holder)
{
m_surfaceHolder = holder;
}
#Override
public void draw(Canvas canvas)
{
Log.i("LevelNumberView","Draw");
doDraw(canvas, Color.YELLOW);
}
public void masDraw()
{
Log.i("LevelNumberView","masDraw");
if (null != m_surfaceHolder)
{
Canvas canvas = m_surfaceHolder.lockCanvas();
if (null != canvas)
{
doDraw(canvas, Color.GREEN);
m_surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
private void doDraw(Canvas canvas, int colour)
{
int w = canvas.getWidth();
int h = canvas.getHeight();
int min = Math.min(w,h);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(colour);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(w/2, h/2, min, paint);
}
}
Thanks!
-Mark
Overriding draw() probably isn't what you want. If you want to draw on the View, override onDraw(). If you want to draw on the Surface, that's usually done from a separate thread. (The SurfaceView has two parts; the surface part is a completely separate layer that is drawn behind the layer with the View-based UI.)
If you haven't called SurfaceHolder().addCallback(), no object will be receiving callbacks -- the SurfaceView doesn't send callbacks to itself, so subclassing SurfaceView doesn't really do much for you.
Perhaps what you want is simply a custom View?

Drawing over a view and all it's children

I'm trying to apply a visual effect to a viewgroup. My idea is to grab a bitmap of the viewgroup, shrink it down, expand it back up, and draw it over the viewgroup to give it a blocky, low quality effect.
I've got most of the way there using this code:
public class Blocker {
private static final float RESAMPLE_QUALITY = 0.66f; // less than 1, lower = worse quality
public static void block(Canvas canvas, Bitmap bitmap_old) {
block(canvas, bitmap_old, RESAMPLE_QUALITY);
}
public static void block(Canvas canvas, Bitmap bitmap_old, float quality) {
Bitmap bitmap_new = Bitmap.createScaledBitmap(bitmap_old, Math.round(bitmap_old.getWidth() * RESAMPLE_QUALITY), Math.round(bitmap_old.getHeight() * RESAMPLE_QUALITY), true);
Rect from = new Rect(0, 0, bitmap_new.getWidth(), bitmap_new.getHeight());
RectF to = new RectF(0, 0, bitmap_old.getWidth(), bitmap_old.getHeight());
canvas.drawBitmap(bitmap_new, from, to, null);
}
}
I simply pass in the canvas to draw on and a bitmap of what needs to be scaled down+up and it works well.
public class BlockedLinearLayout extends LinearLayout {
private static final String TAG = BlockedLinearLayout.class.getSimpleName();
public BlockedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
applyCustomAttributes(context, attrs);
setup();
}
public BlockedLinearLayout(Context context) {
super(context);
setup();
}
private void setup() {
this.setDrawingCacheEnabled(true);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// block(canvas); If I call this here, it works but no updates
}
#Override
public void onDraw(Canvas canvas) {
// block(canvas); If I call this here, draws behind children, still no updates
}
private void block(Canvas canvas) {
Blocker.block(canvas, this.getDrawingCache());
}
}
The problem I'm having is in my viewgroup. If I run the block method in the viewgroup's draw, it draws over everything but doesn't ever update when child views change. I've traced function calls with Log, and the draw method seems to be running, but nothing changes.
I've also tried implementing this in onDraw. This draws the bitmap behind all the children views, and again they aren't updating.
Can anyone explain how I would go about fixing this?
Try this:
#Override
protected void dispatchDraw(Canvas canvas) {
// call block() here if you want to draw behind children
super.dispatchDraw(canvas);
// call block() here if you want to draw over children
}
And call destroyDrawingCache() and then, buildDrawingCache() each time you change a child.
Draw() method will work well for you.
I'm now trying to make a count time view in a circle shape, when time is passing, the view will reducing his angle. It's used to cover profile photo(a circle shape photo).
Starting with Android API 23, you can use onDrawForeground(Canvas) to draw on top of child views: https://developer.android.com/reference/android/view/View#onDrawForeground(android.graphics.Canvas)
Unlike onDraw() though, be sure to call through to the super class:
#Override
public void onDrawForeground(final Canvas canvas) {
super.onDrawForeground(canvas);
// Your code here
}

Change Interpolation method in scaled SurfaceView

It is possible to render a view at a low resolution, and then scale it up to fit the actual size of your view using the setFixedSize() method of a SurfaceHolder. However, the scaling is done with some kind of interpolation, causing everything to blur.
Is there any method for changing the method of interpolation to nearest neighbour or just turning it off?
Here is an example of what I mean, Made with a 4x4 surface in a fullscreen-view:
Left image: This is how I want the result to look (here achieved by drawing a nonfiltered bitmap)
Right image: This is what happens when the 4x4 canvas is scaled to fullscreen.
Sample code for generating the right image if anyone's interested:
public class ScaleView extends SurfaceView implements SurfaceHolder.Callback {
private final static float[] points = {0,0, 2,0, 4,0, 1,1, 3,1, 0,2, 2,2, 4,2, 1,3, 3,3};
private Paint white;
public ScaleView(Context context) {
super(context);
white = new Paint();
white.setColor(0xFFFFFFFF);
getHolder().addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
Canvas c = holder.lockCanvas();
try{
c.drawPoints(points, white);
}finally{
holder.unlockCanvasAndPost(c);
}
}
#Override
public void surfaceCreated(SurfaceHolder holder){
holder.setFixedSize(4, 4);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder){}
}
Note: The square pattern above is just an example of the effect I want to avoid. I am looking for a general solution that can be applied to any view (with a surfaceholder) with any content.
Sorry, you can't control this. It is not defined what kind of scaling will be done on these surfaces -- it depends on the hardware and how it is configured. Also, this kind of extreme scaling really should be avoided, since in some cases hardware can't do it so you will end up in slower paths. (For example if surfaces are being put into overlays, many hardware overlay engines can't do that kind of extreme scaling.)

Categories

Resources