Android canvas setShadowLayer greatly decreases performance - android

I'm writing a canvas app on android and I'm looking to add shadows, but I've noticed a great slow-down when I add them to my paint object. My code is simple it looks like this:
...
Paint paint = new Paint();
paint.setShadowLayer(8.f, 0, 0, 0xff000000); // this line makes terribly slow drawing
canvas.drawRect(left, top, right, bottom, paint);
How can I make this faster?

While digging around to find a way to speed up my large text shadows, I stumbled on this question and answer:
setShadowLayer Android API differences
By using:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
I drastically sped up all the text shadows in my app.
Here is a sample of how I use it:
/**
* Set a backlight (shadow) on the passed TextView.
* #param textView
*/
void setBacklight(TextView textView) {
if (textView != null) {
float textSize = textView.getTextSize();
textView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
textView.setShadowLayer(textSize,0,0,getColor(R.color.color_backlight));
}
}
According to this doc:
https://developer.android.com/guide/topics/graphics/hardware-accel.html
It says that you can disable hardware acceleration for the view.
I don't know why, but somehow this magically speeds up my TextView shadow layers.
I know, I know. That method doesn't exist for the Canvas or Paint classes. So to answer the specific question (so I don't get blasted by everyone...), you could set that on the View that you intend to draw the canvas. Like this:
void inTheShadows(View view) {
float left = 0f;
float top = 0f;
float right = 10f;
float bottom = 10f;
Canvas canvas = new Canvas();
Paint paint = new Paint();
paint.setShadowLayer(8.f, 0, 0, 0xff000000);
canvas.drawRect(left, top, right, bottom, paint);
view.setLayerType(View.LAYER_TYPE_SOFTWARE,null);
view.onDrawForeground(canvas);
}

You can achieve almost the same result using this code instead:
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));

Use an image icon instead of drawing it :)
Yes shadowing is costly.

Related

Drop shadow blur effect on a FrameLayout / Layout in Android

I'm trying to make a drop shadow blur effect WITH COLOR on a rectangular layout view. I've tried to use this code but to no avail.
int glowRadius = 14;
int glowColor = Color.parseColor("#acc5fe");
Paint paint = new Paint();
paint.setColor(glowColor);
paint.setMaskFilter(new BlurMaskFilter(glowRadius, BlurMaskFilter.Blur.OUTER));
RectF rectF = new RectF(mRootView.getHeight(), mRootView.getWidth(),
mRootView.getHeight(), mRootView.getWidth());
Canvas canvas = new Canvas();
canvas.drawRect(rectF, paint);
mRootView.draw(canvas);
It doesn't seem to do anything though.
I also tried to use shadowDx and shadowDy etc. but that does nothing.
How to add a blurred drop shadow to a button? is a very similar question but I don't think the 9-patch is a viable solution since it's a layout and not an image.
How do you create a drop outer shadow blur effect on Android?
Update
Still haven't found a successful answer. I want something like
where the shadow effect is on the email edittext with a different color than black.
try following A library for supporting convex material shadows
https://github.com/harjot-oberai/MaterialShadows
What I understand from your question is first you want to drop a shadow. Here is what you can do to drop a shadow.
Just add this to your parent layout in xml.
android:background="#android:drawable/dialog_holo_light_frame"
Check this answer for more information.
how to add shadow effect in alert dialog box in android
Let me know if this works for you.
Edit:
Please check this answer. This might help you.
Extending Android View class to add a dropshadow
It can be achieved using NinePatchDrawable. Below is a simple example.
protected NinePatchDrawable bg;
protected Paint paint;
protected Rect padding = new Rect();
protected Bitmap bmp;
protected void init() {
// decode the 9patch drawable
bg = (NinePatchDrawable) getResources().getDrawable(R.drawable.balloon);
// get paddings from the 9patch and apply them to the View
bg.getPadding(padding);
setPadding(padding.left, padding.top, padding.right, padding.bottom);
// prepare the Paint to use below
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.rgb(255,255,255));
paint.setStyle(Style.FILL);
// this check is needed in order to get this code
// working if target SDK>=11
if( Build.VERSION.SDK_INT >= 11 )
setLayerType(View.LAYER_TYPE_SOFTWARE, paint);
// set the shadowLayer
paint.setShadowLayer(
padding.left * .2f, // radius
0f, // blurX
padding.left * .1f, // blurY
Color.argb(128, 0, 0, 0) // shadow color
);
}
Also please try this color:
Color.argb(128, 0, 0, 0)

Can I clip a view with a circle using setClipBounds?

I have the following code to delimit the area of a view to be drawn:
Rect rect = new Rect();
rect.set(0, 0, 100, 100);
View.setClipBounds(rect);
This will draw my view only on the specified rectangle (or square, in this case). However, I wanted the view to be clipped to a circle. Is there any way to somehow round the corners of a Rect object?
In this case, you've to subclass that view and add some extra logic to it.
Add these codes to its constructor method, or wherever you would like to initialize the view.
final Path path = new Path();
path.addRoundRect(new RectF(0,0,getWidth(),getHeight()),10,10,Direction.CW);
Using these codes, you're defining a path along which your view is going to be drawn (the area inside the boundaries of the patch).
Add this method to the class to apply this mask on your view.
#Override
protected void dispatchDraw(Canvas canvas){
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
Credits: https://stackoverflow.com/a/7559233/1841194
Try to use
RectF r = new RectF(10,100,200,400);
canvas.drawRoundRect(r, 0, 0, mPaint);
about
or square case of it.
The other approach is to use clipping mask. The concept of this idea is to use PorterDuffXfermode or PorterDuff .
This is an example for the rounded corner view. I don't know what directly you want that's why I just can give to base methods I've used. The other example.
Try this:
val circlePath = Path().apply {
addCircle(x, y, radius, Path.Direction.CW)
}
canvas.clipPath(circlePath)

Drawing a small Rect at the point where onTouch is detected

I'm performing a very small test to debug something larger, and am having trouble figuring out what's causing this issue.
I have an ImageView, a Canvas and a Bitmap. I initialize the ImageView and set it to the Bitmap, and then set the Canvas to the bitmap, as such:
_image.setImageBitmap(bitmap);
Canvas canvas = new Canvas(bitmap);
I then attach an onTouchListener to _image and listen for MotionEvent.ACTION_UP to be detected. At that point I draw a Rect on the Canvas, as such:
Rect rect = new Rect((int)event.getX(),event.getY(),event.getX()+20,event.getY()+20);
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawRect(rect,paint);
I've tried this with bitmap images of different sizes, to smaller than the full display, to very nearly the exact size, to larger, and I can never get the Rect to draw right where my finger touches the screen. I've tried different conversions on getX(), attempting to convert to dp, from dp, used getRawX/Y() instead; basically a bunch of different solutions, none of which have worked. I've tried getting the ratio of the bitmap's size to the screen size and multiplying/dividing by that; nothing has been able to solve this very simple issue.
Quite possibly I've tried the right approach at some point and simply implemented it incorrectly. I'd really appreciate some advice on what's causing the Rect to draw where it is, what conversion is needed to get it to draw right where the touch occurs, and why. The 'why' is because I want to learn from this.
Edit: This is an approximation of what happens. The red rectangle is where the actual touch occurs, and the blue is where it is drawn. It scales the further from X=Y=0 you get. Also, I just noticed that it also scales depending on the width and height of the image you're using.
So, I believe I've figured out the math which allows me to transform the onTouch coordinates onto the Canvas.
The reason I really wanted to go this route (as opposed to a custom subclass for ImageView) is because I'm not really that interested in the drawing of the rectangles: I'm more interested in accurately mapping onTouch coordinates onto specific OCRed text strings in the bitmap. The reason I was performing this exercise was to figure out why the touches weren't being attributed to the correct lines of text, and I was using drawn Rects to estimate where the touches were being recorded.
So, on to the math, which turned out to be a lot simpler than I'd feared:
int x1 = (int)(event.getX() * ((float) canvas.getWidth() / _image.getRight()));
int y1 = (int)(event.getY() * ((float) canvas.getHeight() / _image.getBottom()));
I multiply the onTouch coordinates by the ratio of the Canvas's dimensions to the ImageView's dimensions, which I cast to a float. Painting this as follows:
rect = new Rect(x1-10,y1-10,x1+10,y1+10);
//I took the advice about centering the Rect around the touch for clarity
paint = new Paint();
paint.setColor(Color.RED);
canvas.drawRect(rect,paint);
And the Rect is exactly where the touch was, regardless of bitmap size.
I found it interesting that it didn't cause any problems that I had called
_image.setAdjustViewBounds(true);
earlier in my program, which I did in order to remove some padding from the bitmap (which was showing up for some reason, possible due to the scaling). Also interesting is the fact that the ImageView is contained in a ScrollLayout, but even if the image is large enough to scroll though, it doesn't seem to need to take the scroll displacement into account.
Thanks to kcoppock for taking the time to help me out, and to the other user (whose comment was deleted when he realized he had misunderstood me) for taking the time as well.
I'd imagine your actual Bitmap is larger than the View and is scaled down. Since you're drawing in the coordinates of your bitmap, and that bitmap is being scaled down, the location will also be scaled down to match.
What you more likely want to do is make a subclass of ImageView, and then draw the Rect in onDraw() based on the location received in onTouchEvent(). Alternatively, you could do some math to figure out how much the bitmap is being scaled down relative to the view size, and scale up your coordinates by the inverse. A quick implementation of this touchable ImageView would be something like this:
public class DrawableImageView extends ImageView {
private final int mRectSize;
private final Paint mPaint;
private final Rect mRect = new Rect();
public DrawableImageView(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
mRectSize = (int) (getResources().getDisplayMetrics().density * 20);
}
// Other constructors omitted for brevity
#Override public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
final int x = (int) event.getX();
final int y = (int) event.getY();
mRect.set(x, y, x + mRectSize, y + mRectSize);
mRect.offset(-(mRectSize / 2), -(mRectSize / 2));
invalidate();
return true;
} else {
return super.onTouchEvent(event);
}
}
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mRect, mPaint);
}
}

Draw Circle at Center of Canvas

I'm just getting into basic drawing with Android. I'm starting off with a few simple shapes but I'm having a few issues. I'd like to draw a circle at the center of a canvas. I looked at a few examples but can't seem to make it work. I think it's because I don't really understand what variables go where.
Could someone please explain the proper way to draw my circle at the center of my screen. Here is my code:
public class Circle extends View{
int width = this.getWidth();
int height = this.getHeight();
public Circle(Context context) {
super(context);
setFocusable(true);
}
protected void onDraw(Canvas canvas){
canvas.drawColor(Color.CYAN);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
//canvas.drawCircle(100, 100, 50, paint);
canvas.drawCircle(width/2, height/2, 100, paint);
Display disp = ((WindowManager)this.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
float radius = 0;//Radius caused an error so I initialized this variable
canvas.drawCircle(disp.getWidth()/2, disp.getHeight()/2, radius, paint);
}
}
width and height of the view have not been yet initialized when getWidth() and getHeight() are called, just use getWidth() and getHeight() in onDraw:
canvas.drawCircle(getWidth()/2, getHeight()/2, 100, paint);
You can also override onSizeChanged and get view width and height.
PS: do not create anything in onDraw, create the paint object in the constructor.
public void drawCircle(Graphics2D g, int x, int y, int radius) {
x = x-(radius/2);
y = y-(radius/2);
g.fillOval(x,y,radius,radius);
}
here x,y is the position of canvas where you want to draw circle and you can find it with motion listener if you want to set x,y position dynamically hope this will help you
There are some links which are very useful for us and I hope they will work for you and other.
https://github.com/swapgo20/Android-Hand-Drawing
https://github.com/codepath/android_guides/wiki/Basic-Painting-with-Views
https://github.com/Korilakkuma/CanvasView
I hope above links are very useful to draw shapes on canvas.
I suggest you use third link and use only Path class (http://developer.android.com/reference/android/graphics/Path.html) of android to draw shapes.

ImageView with rounded corners and inner shadow

I need to make a thumbnail view with rounded corners and inner shadow. Usually I'm making ImageView frames with 9patches, which have served me well so far, but this time the effect I need requires drawing the inner shadow on top of the image (and not just around it). This lead me to extend the ImageView class and override the onDraw() method.
public class ThumbnailImageView extends ImageView {
After many tutorials (thanks StackOverflow!), I ended up with this code for the onDraw() method:
#Override
protected void onDraw(Canvas canvas) {
if (mBitmap == null) {
return;
}
int radius = 4;
int padding = 2;
int bleed = 2;
RectF frame = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xFF000000);
canvas.drawRoundRect(frame, radius, radius, mPaint);
Shader bitmapShader = new BitmapShader(mBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xFF000000);
mPaint.setMaskFilter(new BlurMaskFilter(bleed, Blur.INNER));
mPaint.setShader(bitmapShader);
canvas.drawRoundRect(frame, radius, radius, mPaint);
}
What I'm basically doing, is drawing a black rounded rectangle first and then drawing a rounded-corners bitmap with fading edges (with the BlurMaskFilter) on top of it. The result is what I want:
The mBitmap value is initialized in the ImageView constructor like this:
mDrawable = getDrawable();
if (mDrawable != null) {
mBitmap = ((BitmapDrawable) mDrawable).getBitmap();
}
The problem is that I am overriding onDraw() completely (no super.onDraw()) is called, so I have to pre-scale all images to the desired thumbnail size (e.g. 96x96) or else only the top-left corner of the image is drawn. What I want to be able to do is take advantage of all the scaling the framework is doing when I assign the following xml values to the ThumbnailImageView:
android:id="#+id/thumb"
android:layout_width="96dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
To do this, I thought I should somehow call super.onDraw() while getting the effects I need at the same time. I have managed to get the rounded rectange by adding a clipping path to the canvas, but I can't find a way to add the inner shadow. This is the new onDraw() code:
#Override
protected void onDraw(Canvas canvas) {
int radius = 4;
int padding = 4;
RectF frame = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
Path clipPath = new Path();
clipPath.addRoundRect(frame, radius, radius, Path.Direction.CW);
canvas.clipPath(clipPath);
super.onDraw(canvas);
// add inner shadow
}
I can see two alternatives:
1) To properly pre-scale the ImageView's bitmap. But where is the best place to do it? In it's constructor? In the onDraw() method where the framework seems to be doing it? Is the framework even resizing any bitmap or is there another way to draw a scaled image on the canvas without being bad for performance?
2) To add the inner shadow layer on top of what the super.onDraw() is drawing so far, but I'm running out of ideas on how to do this.
Any help would be appreciated.
Thanks!
Take a look at Eric's (from squareup) presentation material from Oreilly's AndoridOpen Conference last year in his lecture titled Beautiful Android
It has a ton of info that should help you out.
I wish they had the video of his presentation somewhere. I could not find it. So sorry.
EDIT : Thanks to #mykola for the yt link

Categories

Resources