TextView adding gradient AND shadow - android

I have a problem. I would like to have a textview with a gradient as color. And a black shadow behind it. The problem is that the shadow is using the color of the gradient in stead of using the called color (Color.BLACK)
My code is:
numberTextView = (TextView)findViewById(R.id.something);
Shader textShaderTop = new LinearGradient(0, 30, 0, 60,
new int[]{Color.parseColor("#A6A6A6"), Color.parseColor("#E8E8E8"), Color.parseColor("#A6A6A6")},
new float[]{0, 0.5f, 1}, TileMode.CLAMP);
numberTextView.getPaint().setShader(textShaderTop);
numberTextView.setShadowLayer(
0.1f, //float radius
20f, //float dx
20f, //float dy
Color.BLACK //this is not black on the screen, but it uses the gradient color!?
);
Does anybody knows what to do

I had exactly the same problem.
I managed to fix it by extending TextView and overriding onDraw method.
Here is how it looks like
#Override
protected void onDraw(Canvas canvas) {
// draw the shadow
getPaint().setShadowLayer(1, 1, 1, 0xbf000000); // or whatever shadow you use
getPaint().setShader(null);
super.onDraw(canvas);
// draw the gradient filled text
getPaint().clearShadowLayer();
getPaint().setShader(new LinearGradient(0, getHeight(), 0, 0, 0xffacacac, 0xffffffff, TileMode.CLAMP)); // or whatever gradient/shader you use
super.onDraw(canvas);
}
However this method probably won't work if you want to use colors with transparency in your gradient.

Thank you for sidon's answer. It helped me.
And I add this answer because I found a way to use colors with transparency in gradient.
So, please refer to sidon's answer first and upvote his one.
I found below at "setShadowLayer" Method Description from here.
The alpha of the shadow will be the paint's alpha if the shadow color is opaque, or the alpha from the shadow color if not.
So, point is shadowColor must be not opaque to use colors with transparency in gradient.
Here is code.
#Override
protected void onDraw(Canvas canvas) {
// draw the shadow
getPaint().setShader(null);
setTextColor(0x00ffffff); // set the paint's alpha by 00
getPaint().setShadowLayer(3.0f, 1.5f, 1.8f, shadowColor); // shadowColor must be not opaque
super.onDraw(canvas);
// draw the gradient filled text
getPaint().clearShadowLayer();
setTextColor(0xffffffff); // set the paint's alpha by ff
getPaint().setShader(new LinearGradient(0, 0, getWidth(), getHeight(), 0x7fff8809, 0x7f09ffff, Shader.TileMode.CLAMP)); // or whatever gradient/shader you use
super.onDraw(canvas);
}
If shadowColor is opaque, you may change it as not opaque by reduce the alpha by one.
if((shadowColor >>> 24) == 0xff)
shadowColor &= 0xfeffffff;
Thank you once again for sidon's answer.
2018-12-16 Edit:
If you have the same alpha value about colors, below code would better.
public class TextView_Gradient extends TextView {
public TextView_Gradient(Context context) {
super(context);
setTextColor(0x3fffffff); // set the paint's alpha by 3f
}
#Override
protected void onDraw(Canvas canvas) {
// draw the shadow
getPaint().setShader(null);
// shadowColor must be opaque.
getPaint().setShadowLayer(3.0f, 1.5f, 1.8f, shadowColor);
super.onDraw(canvas);
// draw the gradient filled text
getPaint().clearShadowLayer();
// gradient colors must be opaque, too.
getPaint().setShader(new LinearGradient(0, 0, getWidth(), getHeight(), 0xffff8809, 0xff09ffff, Shader.TileMode.CLAMP));
super.onDraw(canvas);
}
}
Because the alpha of the shadow will be the paint's alpha if the shadow color is opaque. And the alpha of the gradient colors is the paint's alpha if the alpha of the gradient colors are ff(opaque).
(Or the final alpha of text can be scaled again by the gradient color's alpha value provided that the paint's alpha is ff.)
Output:

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)

What do PorterDuff source and destination refer to when drawing on canvas?

I've been trying to figure this out all night, but answers found on Google relate to very specific problems regarding Android's canvas and I haven't found any 101 explanations on this topic. Even Android documentation uses bitmaps instead of drawing shapes.
Specific problem:
I need to draw an oval and a path on canvas. And according to documentation colour source out with one colour, destination out another colour and overlapping area, either source in or destination in, a third colour. I'm trying to do all this in an offscreen canvas. but problems arise with some of the steps above and get worse when trying to combine them in any way.
Code -
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888);
Canvas c = new Canvas (bmp);
Paint paint = new Paint ();
paint.SetARGB (255, 255, 0, 0);
c.DrawOval (200, 200, 520, 520, paint); //assumed destination
paint.SetARGB (255, 0, 0, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.*)); //replace mode here
paint.SetStyle (Paint.Style.Fill);
Path path = new Path ();
path.MoveTo (c.Width / 2f, c.Height / 2f);
foreach (var m in measurements) {
//calculations
float x = xCalculatedValue
float y = yCalculatedValue
path.LineTo (x, y);
}
path.LineTo (c.Width / 2f, c.Height / 2f);
c.DrawPath (path, paint); //assumed source
Source out -
This instead draws what XOR is supposed to draw.
Destination out -
This works as expected.
Source in -
This draws what source atop should.
Destination in -
This draws what destination should.
More general question:
What do source and destination refer to in this context? Intuitively I would assume that destination is the current state of the canvas bitmap and source is the matrix added by canvas.Draw* and Paint PortedDuff.Mode. But that doesn't seem to be the case.
EDIT: This is basically the effect I'm after, where the "star" is a dynamic path. Coloured three different colours depending on overlap.
Crude drawing
EDIT 2: York Shen did a great job answering the actual question. But for anyone wanting to get a similar effect here's the final code.
Bitmap DrawGraphBitmapOffscreen ()
{
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888);
Canvas c = new Canvas (bmp);
// Replace with calculated path
Path path = new Path ();
path.MoveTo (c.Width / 2f, c.Height / 2f);
path.LineTo (263, 288);
path.LineTo (236, 202);
path.LineTo (312, 249);
path.LineTo (331, 162);
path.LineTo (374, 240);
path.LineTo (434, 174);
path.LineTo (431, 263);
path.LineTo (517, 236);
path.LineTo (470, 312);
path.LineTo (557, 331);
path.LineTo (479, 374);
path.LineTo (545, 434);
path.LineTo (456, 431);
path.LineTo (483, 517);
path.LineTo (407, 470);
path.LineTo (388, 557);
path.LineTo (345, 479);
path.LineTo (285, 545);
path.LineTo (288, 456);
path.LineTo (202, 483);
path.LineTo (249, 407);
path.LineTo (162, 388);
path.LineTo (240, 345);
path.LineTo (174, 285);
path.LineTo (263, 288);
path.Close ();
Paint paint = new Paint ();
paint.SetARGB (255, 255, 0, 0);
paint.SetStyle (Paint.Style.Fill);
c.DrawPath (path, paint);
paint.SetARGB (255, 0, 0, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.SrcIn));
c.DrawOval (200, 200, 520, 520, paint);
paint.SetARGB (255, 255, 255, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.DstOver));
c.DrawOval (200, 200, 520, 520, paint);
return bmp;
}
What do PorterDuff source and destination refer to when drawing on canvas?
After some in-depth study, I write a few demo to explain this deeply. To help you understand what is source and destination refer to.
First, look at the following code :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Draw a blue rectangle
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
I override the OnDraw method, set a green background, then draw a yellow circle and a blue rectangle, effect :
Above is the normal procedure when we draw a Canvas, I didn't use any PorterDuffXfermode,let's analyse its process :
First, we call canvas.DrawARGB(255, 139, 197, 186) method draw the whole Canvas with a single color, every pixels in this canvas has the same ARGB value : (255, 139, 197, 186). Since the alpha value in ARGB is 255 instead of 0, so every pixels is opaque.
Second, when we execute canvas.DrawCircle(r, r, r, paint) method, Android will draw a yellow circle at the position you have defined. All pixels which ARGB value is (255,139,197,186) in this circle will be replaced with yellow pixels.
The yellow pixels is source and the pixels which ARGB value is (255,139,197,186) is destination. I will explain later.
Third, after execute the canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint) method, Android will draw a blue rectangle, all pixels in this rectangle is blue, and these blue pixels will replac other pixels in the same position. So the blue rectangle can be draw on Canvas.
Second, I use a mode of Xfermode, PorterDuff.Mode.Clear :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Use Clear as PorterDuffXfermode to draw a blue rectangle
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear));
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
paint.SetXfermode(null);
this.SetLayerType(LayerType.Software, null);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//I found that PorterDuff.Mode.Clear doesn't work with hardware acceleration, so you have add this code
}
Effect :
Let's analyse its process :
First, we call canvas.DrawARGB(255, 139, 197, 186) method to draw the whole Canvas as a single color, every pixels is opaque.
Second, we call canvas.DrawCircle(r, r, r, paint) method to draw a yellow
circle in Canvas.
Third, execute paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear)), set the paint PorterDuff model to Clear.
Forth, call canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint) to draw a blue rectangle, and finally it shows a white rectangle.
Why it display a white rectangle? Usually, when we call canvas.DrawXXX() method we will pass a Paint parameter, when Android execute draw method it will check whether the paint has a Xfermode mode. If not, then the graphics will directly covers the pixels that in Canvas at the same position. Otherwise, it will update the pixels in Canvas according to the Xfermode mode.
In my example, when execute canvas.DrawCirlce() method, Paint didn't has a Xfermode model, so the yellow circle directly cover the pixels in Canvas. But when we call canvas.DrawRect() to draw a rectangle, Paint has a Xfermode value PorterDuff.Mode.Clear. Then Android will draw a rectangle in memory, the pixels in this rectangle has a name : Source. The rectangle in memory has a corresponding rectangle in Canvas, the corresponding rectangle is called :
destination .
The value of the ARGB of the source pixel and the value of the ARGB of the destination pixel are calculated according to the rules defined by Xfermode, it will calculate the final ARGB value. Then update the ARGB value of the target pixel with the final ARGB value.
In my example, the Xfermode is PorterDuff.Mode.Clear, it require destination pixels ARGB becomes (0,0,0,0), that means it is transparent. So we use canvas.DrawRect() method draw a transparent rectangle in Canvas, since Activity itself has a white background color, so it will show an white rectangle.
EDIT :
To implement the feature you post in the picture, I write a demo :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
paint.SetARGB(255, 255, 0, 0);
RectF oval2 = new RectF(60, 100, 300, 200);
canvas.DrawOval(oval2, paint);
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.*));
Path path = new Path();
paint.SetStyle(Paint.Style.Fill);
paint.SetARGB(255, 0, 0, 255);
path.MoveTo(180, 50);
path.LineTo(95, 240);
path.LineTo(255, 240);
path.Close();
this.SetLayerType(LayerType.Software, null);
canvas.DrawPath(path, paint);
paint.SetXfermode(null);
}
When use different Xfermode, their effect :
Xor, SrcOut, Screen, Lighten, Darken, Add.
As you can see, you could use different color and different Xfermode to achieve your effect.

Using canvas and bitmap in Android , how to get this image?

I am new in android. I am trying to draw this image(match statistic)
and fill the image with color with 10% to 100% . I tried this much and this is image
this is the code
public class DrawView extends View {
Paint paint = new Paint();
public DrawView(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
paint.setColor(Color.BLACK);
paint.setStrokeWidth(3);
canvas.drawRect(30, 30, 100, 100, paint);
paint.setStrokeWidth(0);
paint.setColor(Color.GRAY);
canvas.drawRect(33, 60, 97, 97, paint);
paint.setColor(Color.WHITE);
canvas.drawRect(33, 33, 97, 60, paint);
}
Any Suggestion will be much helpful for me.
Thanks in advance.
I would prepare two images - fully filled and not filled (only stroke). Having that, load them as two Bitmap objects and then draw like that:
float fillProgress = 0.1f; // let's say image is 10% filled
canvas.drawBitmap(onlyStroke, 0f, 0f, null); // draw just stroke first
canvas.save();
canvas.clipRect(
0f, // left
getHeight() - fillProgress * getHeight(), // top
getWidth(), // right
getHeight() // bottom
);
canvas.drawBitmap(filled, 0f, 0f, null); // region of filled image specified by clipRect will now be drawn on top of onlyStroke image
canvas.restore();
Using two images, outlined and filled e.g. below.
The code above does the following:
draw outline.
apply clip (crop) area.
draw filled shape with crop applied.
remove clip, image as desired.
Applying different clip sizes, you can get the % of fill you require. e.g.

Android Color (setColor in Paint) Needs Negative Integer?

I'm simply trying to paint/draw to the Canvas in Android. However, when I set the color using hex values or using the setARGB method, it doesn't work. But when I use Color.x (eg, Color.GREEN) it works. Here's the code:
Bitmap image = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(image);
Paint paintBackground = new Paint();
int green = Color.argb(0, 0, 255, 0); // 65280 (Won't work)
green = 0x0000ff00; // 65280 (Won't work)
paintBackground.setARGB(0, 0, 255, 0);
green = paintBackground.getColor(); // 65280 (Won't work)
green = Color.GREEN; // -16711936 (Works!)
paintBackground.setColor(green);
green = paintBackground.getColor(); // -16711936
paintBackground.setStyle(Paint.Style.FILL);
canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paintBackground);
So basically Color.GREEN returns -16711936 - and this WORKS.
However, the hex value is 65280 - and this DOES NOT WORK. That is, it doesn't paint the green rectangle.
I need to use hex values because I need to set the color to 0x00ffff00 here and then later to a different hex value.
Does Android Color (setColor in Paint) Need a Negative Integer?
The problem is that 0x0000ff00 is not green, but fully transparent green. Fully opaque green would be 0xff00ff00 which is, as you have already noticed, -16711936. Similarly, when using setARGB you need to specify 255 for alpha for the color to be fully opaque.
Color holds 4 fields, alpha, red, green and blue. Whenever anything is mostly opaque it is negative. 50.2% transparent green is positive (0x7F00FF00/2,130,771,712) and 49.8% transparent green is negative (0x8000FF00/-2,147,418,368)
You can also call Color.rgb(0, 255, 0). With rgb() alpha is by default 255, fully opaque.

Draw ring with black shadow

I'm trying to draw a ring with black shadow border. I'm able to achieve this with custom view when I use any color like RED, but what I want is a transparent circle with black shadow border.
Paint mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setShadowLayer(5.5f, 6.0f, 6.0f, Color.BLACK);
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(70, 70, 50, mPaint); }
What's happening here is that it's drawing a filled in transparent circle, and setting the shadow layer to that. Since the circle is transparent, you see the whole shadow of the object through it, not just the edges you're looking for. The shadow is black, so it looks like the whole circle is black.
Try setting the Paint style to Stroke. That should leave the middle transparent, and just draw the shadow of the outer ring. It may draw the shadow in both directions, though(inner and outer), so you may have to adjust the shadow radius accordingly.
mPaint.setStyle(Paint.Style.STROKE);
Are you targeting Android SDK 11 or higher? Since HoneyComb shadow rendring with hardware support had been disabled, you habe to turn on software rendering for this layer. You have to annotate your function for setting up the paint like this:
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setUpPaint(){
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setShadowLayer(5.5f, 6.0f, 6.0f, 0x80000000);
/* --- for android:minSdkVersion="11" --- */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_SOFTWARE, mPaint);
}
}
You'll need to effectively draw the shadow using a fully transparent colour, as you've found so far, then remove that coloured center using PorterDuff.Mode.CLEAR - leaving just the shadow on the outside.
PorterDuffXfermode mXferMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
// draw the src/dst example into our offscreen bitmap
int sc = canvas.saveLayer(0, 0, 70 + 50, 70 + 50, null,
Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
canvas.drawCircle(70, 70, 50, shadowPaint);
shadowPaint.setXfermode(mXferMode);
canvas.drawCircle(70, 70, 50, shadowPaint);
shadowPaint.setXfermode(null);
canvas.restoreToCount(sc);

Categories

Resources