Android draw shadow below view: dispatchDraw called often - any cache missed - android

I'm trying to drop a smooth shadow under a view element. Here's is what I've done so far:
Subclass FrameLayout (my ShadowViewport), which overrides dispatchDraw(...) and does:
Call dispatchDraw of superclass and save output as bitmap
Extract alpa from this bitmap
Blur
Draw blurred extracted alpha with some offset
Draw original view bitmap
#Override
protected void dispatchDraw(Canvas canvas) {
Log.d("ShadowViewport", "dispatchDraw");
final Bitmap output = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
final Canvas sCanvas = new Canvas(output);
// Draw internal view in canvas
super.dispatchDraw(sCanvas);
final Bitmap original = output.copy(Config.ARGB_8888, true);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0x55000000);
paint.setMaskFilter(new BlurMaskFilter(SHADOW_SIZE, Blur.NORMAL));
canvas.drawBitmap(output.extractAlpha(), 0, SHADOW_SIZE, paint);
canvas.drawBitmap(original, 0, 0, null);
}
Result
Find image here.
As you can see, the left box has a nice generated shadow, even for rounded corners ;)
The question
The on dispatchDraw function is called very often, for example while scrolling. Is there any way for caching?

Enabling the android:hardwareAccelerated="true" attribute in the AndroidManifest will enable automatic caching of the image. Ithe dispatchDraw method is not called for example, during scrolling.
I also think it is possible to use the setDrawingCacheEnabled on your Layout Class. To make that work you should call getDrawingCache first, and the the returned bitmap if it is not null. This seems a bit like a "poor man's" optimization, but is probably a good idea to get performance out of older phones.

Related

Draw image inside a View without subclassing

Can I draw a image inside a view without subclassing ? I have tried:
TextView texto = (TextView) viewGroup.findViewWithTag("text");
Paint p = new Paint();
p.setColor(Color.WHITE);
Bitmap b = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_lanc);
Bitmap b2 = b.copy(Bitmap.Config.ARGB_8888, true);
Canvas c = new Canvas(b2);
c.drawBitmap(b, 1, 1, p);
textView.draw(c);
Someone can help me?
You cannot do that directly -- calling draw(Canvas) on a view will cause that View to perform its drawing commands into the canvas. So in your case that would draw the TextView onto the Canvas you provided (not the other way around).
If you needed to do so, you could set a subclass of drawable on the view which does custom drawing itself, and then that would draw within the View's canvas, but whatever you're trying to do here, there's likely an easier way. Without knowing more about your requirements it's impossible to say.
Drawing does not work like that. The textview's draw() method will be called by android, with a canvas instance provided by the system. When you call it yourself with a canvas that you created, the textview is going to draw itself on your canvas instance, but that has nothing to do with what appears on the screen.

Android - canvas drawing doubts

I am looking at one of the sample applications from Google, which deals with touch drawing using canvas:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/TouchPaint.html
I have a few doubts:
I am not able to understand what's the role of Canvas versus the role
of the bitmap.
In the drawPoint function, I am not able to
understand this code snippet:
mCanvas.drawCircle(x, y, radius, mPaint);
mRect.set((int) (x - radius - 2), (int) (y - radius - 2),
(int) (x + radius + 2), (int) (y + radius + 2));
invalidate(mRect);
If the circle is already drawn into the canvas above, then what happens in the onDraw function where the following code is given:
canvas.drawBitmap(mBitmap, 0, 0, null);
Canvas vs Bitmap
A Bitmap is what the name suggests: A normal image as a bitmap. The Canvas class is an editor for bitmaps. You use it to change the bitmap data, it holds all drawing methods. This principle behaves similar to the shared preferences (if you already worked with them), you have a SharedPreferences class that holds the preferences, and an Editor class to change things.
Drawing the circles
This code does something similar to double buffering. drawPoint() basically draws a circle into the mBitmap object¹. But this bitmap object is not yet visible. It exists in the memory. When onDraw() is called, it has a Canvas argument that represents the drawing surface of the view. All that drawBitmap() does here is use the prepared bitmap from the memory and draw it inside the views graphical representation to make it visible.
¹ The used canvas mCanvas is tied to mBitmap inside onSizeChanged()
if you go to the developper refference:
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
Draw the
specified bitmap, with its top/left corner at (x,y), using the
specified paint, transformed by the current matrix.
Then if you see that mBitmap doesn't exist in the class , thats cause that var comes from the extend from another activity .
Canvas also has a setBitmap(Bitmap bitmap) function . Then the solution is that that paint in canvas if you have set into it a bitmap object.
From the Android SDK:
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
I'm assuming you're referring to this snippet:
#Override protected void onDraw(Canvas canvas) {
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
Well it looks like an override of an inherited onDraw method which by default probably 'does nothing', hence the override to actually give it some behaviour, in this case given a non-null Bitmap instance, make the canvas draw it.

Draw text on canvas and make it visible on screen

This code was supposed to convert text to image
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
paint.setTextSize(16);
paint.setAntiAlias(true);
paint.setTypeface(Typeface.MONOSPACE);
Bitmap bm = Bitmap.createBitmap(16, 16, Bitmap.Config.ALPHA_8);
float x = bm.getWidth();
float y = bm.getHeight();
Canvas c = new Canvas(bm);
c.drawText("Test", x, y, paint);
}
Is this code ok? If yes, how can I make this new bitmap visible on screen? I tried this code which produced an error
setContentView(c); //<- ERROR!
I am confused with the element Canvas as there is not such element in XML which I can use in the code.
setContentView(View) takes a View and Canvas is not a View.
I am not sure that you want to create a Canvas on your own. There are ways to get a Canvas passed to you from the Android Framework though. One way you can do this is by creating a custom View. To do this, you will need to create a new class that extends View.
When overriding a View class, you will have the ability to override the onDraw(Canvas) method. This is probably where you want to do what you are attempting to do in your onCreate() method in the code you posted.
This link gives a good overview of what is required to create your own custom view.
First: If you draw your text at the x and y position you specified, you draw it
at the lower right corner, starting with exactly that pixel. Nothing will be drawn on your canvas. Try bm.getWidth()/2, for height the same for test drawing. You can optimize that later.
Second: Canvas is not a View (does not extend the View class). You can only set Views via set ContentView(). What I recommend here is writing a XML layout containing only a single ImageView and set that via setContentView(R.layout.mylayout).
After that, you can use findViewById() to grab that ImageView and use ImageView.setImageBitmap(bm) to show your bitmap on it.
You dont have to do anything with the canvas, once you created it with your bitmap. Everything you draw inside the canvas from that point on is found in the Bitmap immediately.
Therefore you can't specify the Canvas in XML. It's just an "Editor" to edit pictures, so to speak and not an actual UI element.

How to set an image to fill a canvas

I am trying to set a png image to fill the background of my canvas while still maintaining its aspect ratio. I start by converting it to a Bitmap:Then set the back ground using the setBitmap method from the Canvas class:
http://developer.android.com/reference/android/graphics/Canvas.html#Canvas(android.graphics.Bitmap)
public class PlayOn extends View{
Bitmap board;
public PlayOn(Context gamecontext) {
super(gamecontext);
board=BitmapFactory.decodeResource(getResources(),R.drawable.board_rev1);
}
#Override
protected void onDraw(Canvas mycanvas) {
super.onDraw(mycanvas);
mycanvas.setBitmap(board);
}
}
But once I go to the Activity that calls this extended View class I get an error saying my application stopped unexpectedly.
I've also tried playing around with some of the other functions in the Canvas and Bitmap class but nothing seems to work.
Please what is the best way to do this? I read on the android developer site that there is a way to set an image so that it is the canvas and other images can then be drawn inside it but I wasn't able to figure out how to do that.
Thanks!
You might want to add a Log.d to check that the board bitmap returned from
BitmapFactory.decodeResource(getResources(),R.drawable.board_rev1);
isn't null. But I am using the following to draw bitmaps to full screen views in onDraw in several applications, so if the bitmap is non-null that should work fine.
canvas.drawBitmap(mBitmap, 0, 0, null);
And there's a version of drawBitmap which scales, namely
void canvas.drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
Draw the specified bitmap, scaling/translating automatically to fill the
destination rectangle.
You might want to try that?

How to blit() in android?

I'm used to handle graphics with old-school libraries (allegro, GD, pygame), where if I want to copy a part of a bitmap into another... I just use blit.
I'm trying to figure out how to do that in android, and I got very confused.
So... we have these Canvas that are write-only, and Bitmaps that are read-only? It seems too stupid to be real, there must be something I'm missing, but I really can't figure it out.
edit: to be more precise... if bitmaps are read only, and canvas are write only, I can't blit A into B, and then B into C?
The code to copy one bitmap into another is like this:
Rect src = new Rect(0, 0, 50, 50);
Rect dst = new Rect(50, 50, 200, 200);
canvas.drawBitmap(originalBitmap, src, dst, null);
That specifies that you want to copy the top left corner (50x50) of a bitmap, and then stretch that into a 150x150 Bitmap and write it 50px offset from the top left corner of your canvas.
You can trigger drawing via invalidate() but I recommend using a SurfaceView if you're doing animation. The problem with invalidate is that it only draws once the thread goes idle, so you can't use it in a loop - it would only draw the last frame. Here are some links to other questions I've answered about graphics, they might be of use to explain what I mean.
How to draw a rectangle (empty or filled, and a few other options)
How to create a custom SurfaceView for animation
Links to the code for an app with randomly bouncing balls on the screen, also including touch control
Some more info about SurfaceView versus Invalidate()
Some difficulties with manually rotating things
In response to the comments, here is more information:
If you get the Canvas from a SurfaceHolder.lockCanvas() then I don't think you can copy the residual data that was in it into a Bitmap. But that's not what that control is for - you only use than when you've sorted everything out and you're ready to draw.
What you want to do is create a canvas that draws into a bitmap using
Canvas canvas = new Canvas(yourBitmap)
You can then do whatever transformations and drawing ops you want. yourBitmap will contain all the newest information. Then you use the surface holder like so:
Canvas someOtherCanvas = surfaceHolder.lockCanvas()
someOtherCanvas.drawBitmap(yourBitmap, ....)
That way you've always got yourBitmap which has whatever information in it you're trying to preserve.
In android you draw to the canvas, and when you want it to update you call invalidate which will the redraw this canvas to the screen. So I'm guessing you have overridden the onDraw method of your view so just add invalidate();
#Override
public void onDraw(Canvas canvas) {
// Draw a bitmap to the canvas at 0,0
canvas.drawBitmap(mBitmap, 0, 0, null);
// Add in your drawing functions here
super.onDraw(canvas);
// Call invalidate to draw to screen
invalidate();
}
The above code simply redraws the bitmap constantly, of course you want to add in extra thing to draw and consider using a timing function that calls invalidate so that it is not constantly running. I'd advice having a look at the lunarlander sources.

Categories

Resources