I am trying to programmatically draw a parking icon to place as the drawable for an itemized overlay on a map.
The icon consists of a blue square with a white 'P' in its centre of which I would like to programmatically change the colour of the square to denote different parking types.
I've tried creating it via the canvas using drawRect & drawText but I cannot find a simple way of centering the text in the square and I cannot find a way to center the canvas on the coordinates - it keeps wanting to anchor from the top left hand corner.
I've alternatively tried creating an XML layout to convert to a drawable but cannot achieve this either.
Is there an elegant solution for what I am trying to achieve?
public class TextDrawable extends Drawable {
private final static int TEXT_PADDING = 3;
private final static int ROUNDED_RECT_RADIUS = 5;
private final String text;
private final Paint textPaint;
private final Rect textBounds;
private final Paint bgPaint;
private final RectF bgBounds;
public TextDrawable(String text, String backgroundColor, int textHeight) {
this.text = text;
// Text
this.textPaint = new Paint();
this.textBounds = new Rect();
textPaint.setColor(Color.WHITE);
textPaint.setARGB(255, 255, 255, 255);
textPaint.setAntiAlias(true);
textPaint.setSubpixelText(true);
textPaint.setTextAlign(Paint.Align.CENTER); // Important to centre horizontally in the background RectF
textPaint.setTextSize(textHeight);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
// Map textPaint to a Rect in order to get its true height
// ... a bit long-winded I know but unfortunately getTextSize does not seem to give a true height!
textPaint.getTextBounds(text, 0, text.length(), textBounds);
// Background
this.bgPaint = new Paint();
bgPaint.setAntiAlias(true);
bgPaint.setColor(Color.parseColor(backgroundColor));
float rectHeight = TEXT_PADDING * 2 + textHeight;
float rectWidth = TEXT_PADDING * 2 + textPaint.measureText(text);
//float rectWidth = TEXT_PADDING * 2 + textHeight; // Square (alternative)
// Create the background - use negative start x/y coordinates to centre align the icon
this.bgBounds = new RectF(rectWidth / -2, rectHeight / -2, rectWidth / 2, rectHeight / 2);
}
#Override
public void draw(Canvas canvas) {
canvas.drawRoundRect(bgBounds, ROUNDED_RECT_RADIUS, ROUNDED_RECT_RADIUS, bgPaint);
// Position the text in the horizontal/vertical centre of the background RectF
canvas.drawText(text, 0, (textBounds.bottom - textBounds.top)/2, textPaint);
}
#Override
public void setAlpha(int alpha) {
bgPaint.setAlpha(alpha);
textPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
bgPaint.setColorFilter(cf);
textPaint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Make several png images & put them in res\drawable, if you have more than like 5 colors then think about using less. It's confusing for the user.
Related
Background
I have an ImageView which is used to display previews of a file.
I would like to have the ImageView with a checkerboard background, so that when a file with transparency is rendered on top (such as PNG and SVG files) the checkerboard shows through on the transparent parts.
I have found lots of questions on StackOverflow on how to create the checkered background and this question is not entirely specific to that.
I am currently doing it in code. I create a 2 by 2 bitmap (top left/bottom right are one colour, top right, bottom left are the other colour) where the size of each box is specified. Then i create the main bitmap by drawing this small bitmap repeatedly.
int checkeredBackgroundSquareSize= 16;
private static Bitmap getCheckeredBitmap(int size) {
size = (size > 0) ? size : DEFAULT_SQUARE_SIZE;
int colorOne = ContentApplication.appCtx().getColor(R.color.checkerboard_background_color_one);
int colorTwo = ContentApplication.appCtx().getColor(R.color.checkerboard_background_color_two);
// width/height is twice the size of the individual squares
Bitmap squareBitmap = Bitmap.createBitmap(size*2, size*2, Bitmap.Config.ARGB_8888);
Paint bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmapPaint.setStyle(Paint.Style.FILL);
Canvas canvas = new Canvas(squareBitmap);
// draw 2 rectangles on 2 rows
// top left and bottom right are the first colour
// top right and bottom left are the second colour
// set colour for top left/bottom right squares
bitmapPaint.setColor(colorOne);
// Square 1 : top left
Rect rect = new Rect(0, 0, size, size);
canvas.drawRect(rect, bitmapPaint);
// Square 2 : bottom right
rect.offset(size, size);
canvas.drawRect(rect, bitmapPaint);
// change colour for top right/bottom left squares
bitmapPaint.setColor(colorTwo);
// Square 3 : top right
rect.offset(-size, 0);
canvas.drawRect(rect, bitmapPaint);
// Square 4: bottom left
rect.offset(size, -size);
canvas.drawRect(rect, bitmapPaint);
return squareBitmap;
}
I then create a Bitmap the size of my preview image, and use the checkered background bitmap to repeatedly draw on the canvas before the preview image is added on top.
// Create a Bitmap to render our SVG to
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
// Create a Canvas to use for rendering
Canvas canvas = new Canvas(bitmap);
// If we don't specify a viewport box, then AndroidSVG will use the bounds of the Canvas
// as the viewport. So a scale of 1.0 corresponds to that size
canvas.scale(scaling,scaling );
// create the checkered background, indicating transparency
Bitmap square = getCheckeredBitmap(checkeredBackgroundSquareSize);
BitmapShader shader = new BitmapShader(square, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
Paint paint = new Paint();
paint.setShader(shader);
// in your draw method
canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint);
The issue
My previews can be different sizes, for example 100x100, 6000x2000 etc As i am creating the initial bitmap on these sizes, the final image for the files all render looking like the squares on the checkered background are different sizes.
I need to have the checkerboard look exactly the same regardless of the overlaid image's size.
Is there a way to set a background image for an ImageView to be an image. I can only see how to set it to a drawable and I can not see how to define a checkboard as an xml drawable.
Mike M gave the answer that solved my issue. See Mike's comments to the first post
Create your own Drawable that renders the checkerboard
public class CheckerboardDrawable extends Drawable {
// I inadvertently ran the example image with Paint.ANTI_ALIAS_FLAG, but
// we actually don't want that 'cause we're looking for crisp, clean lines.
private final Paint paint = new Paint();
private int checkeredBackgroundSquareSize = 16;
private int colorOne = Color.LTGRAY;
private int colorTwo = Color.WHITE;
#Override
public void draw(#NonNull Canvas canvas) {
final Rect bounds = getBounds();
final int squareSize = checkeredBackgroundSquareSize;
final int columns = bounds.width() / squareSize + 1;
final int rows = bounds.height() / squareSize + 1;
canvas.translate(bounds.left, bounds.top);
for (int c = 0; c < columns; c++) {
for (int r = 0; r < rows; r++) {
paint.setColor((c + r) % 2 == 0 ? colorOne : colorTwo);
final int x = c * squareSize;
final int y = r * squareSize;
canvas.drawRect(x, y, x + squareSize, y + squareSize, paint);
}
}
canvas.translate(-bounds.left, -bounds.top);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
#Override
public void setAlpha(int alpha) {}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {}
}
Then add the drawable to the ImageView
final ImageView imageView = findViewById(R.id.image);
imageView.setBackground(new CheckerboardDrawable());
I'm trying to implement a custom drawable which should have the shape of a speechbubble. Therefore I use two paths, one draws the rect and the other should draw the triangle for the bubble.
My class looks like the following:
public class SpeechBubbleView extends Drawable {
private Paint mBubblePaint;
private Paint mBubblePaint2;
private Path mRectPath;
private Path mBubblePath;
public SpeechBubbleView() { }
public void initPaint() {
mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBubblePaint.setStyle(Paint.Style.FILL);
mBubblePaint.setColor(Color.GREEN);
mBubblePaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mBubblePaint2.setStyle(Paint.Style.FILL);
mBubblePaint2.setColor(Color.RED);
int width = getBounds().width();
int height = getBounds().height();
mRectPath = new Path();
mRectPath.addRoundRect(new RectF(0, 0, width, height), 8, 8, Path.Direction.CW);
mRectPath.close();
mBubblePath = new Path();
mBubblePath.moveTo(50, height);
mBubblePath.lineTo(100, height + 50);
mBubblePath.lineTo(150, height);
mBubblePath.close();
}
#Override
public void draw(Canvas canvas) {
if(mRectPath == null && mPathValues == null) {
initPaint();
}
canvas.drawPath(mRectPath, mBubblePaint);
canvas.drawPath(mBubblePath, mBubblePaint2);
}
#Override
public void onBoundsChange(Rect bounds) {
Rect customBound = new Rect(0, 0, bounds.width(), bounds.height() + 50);
super.onBoundsChange(customBound);
}
The problem now is, that I take the width and height from the drawable to draw the rect of the speechbubble. The full space of the canvas is taken and there is no more room for the triangle to display below the rect.
My question now is: Is it possible to change the size of canvas or the drawable, so that I am able to display the small triangle below the rect?
I already tried the method onBoundsChange, but it takes no effect. In the draw-method the size is still the same.
If possible, it would be nice to change the size directly in the custom drawable class, shown above, because I do not have the size of the view, when I call it. Also I cannot make the size of the rect smaller, because in the drawable there is content and if the rect is smaller, some of the content will be outside of the drawable. I use a drawable, so that I can simple call setBackgroundDrawable of my layout or TextView and it matches always the content size.
If anyone of you got an idea on how to do the size change, this would be very great. Thank you :D
What steps are required to create a shape e.g. rectangle with a shadow from scratch using a Canvas?
Adding a shadow layer to the paint used to draw the rectangle yielded no success.
No need for a Bitmap, just needed to set the layer type to LAYER_TYPE_SOFTWARE the original approach worked.
public class TestShapeShadow extends View
{
Paint paint;
public TestShapeShadow(Context context)
{
super(context);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setShadowLayer(12, 0, 0, Color.YELLOW);
// Important for certain APIs
setLayerType(LAYER_TYPE_SOFTWARE, paint);
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawRect(20, 20, 100, 100, paint);
}
}
I followed the ideas in #pskink's answer and found a solution.
I put the code snippet here for anyone in need.
public class MyViewWithShadow extends View {
Paint paint;
int mainColor;
int shadowColor;
// shadow properties
int offsetX = -25;
int offsetY = 30;
int blurRadius = 5;
public MyViewWithShadow(Context context)
{
super(context);
mainColor = Color.RED;
shadowColor = Color.BLACK; // this color can also have alpha
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
}
#Override
protected void onDraw(Canvas canvas)
{
// Create paint for shadow
paint.setColor(shadowColor);
paint.setMaskFilter(new BlurMaskFilter(
blurRadius /* shadowRadius */,
BlurMaskFilter.Blur.NORMAL));
// Draw shadow before drawing object
canvas.drawRect(20 + offsetX, 20 + offsetY, 100 + offsetX, 100 + offsetY, paint);
// Create paint for main object
paint.setColor(mainColor);
paint.setMaskFilter(null);
// Draw main object
canvas.drawRect(20, 20, 100, 100, paint);
}
}
[Link is now broken:]
If you wonder what shadow properties are, you can refer to this tester:
https://okawa-h.github.io/box-shadow_tester/~
create. a Path, add some elements to it
set BlurMaskFilter to a Paint
draw a path with dx, dy shadow offset
unset mask filter
draw a path again with no. offset
I am trying to move a rectangle that i created to the center of the screen as I am using an accelerometer to allow it to move, is there a way to move the shape to the center of the screen without using android xml?
public class CustomDrawableView extends View
{
static final int width = 150;
static final int height = 250;
public CustomDrawableView(Context context)
{
super(context);
mDrawable = new ShapeDrawable(new RectShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
protected void onDraw(Canvas canvas)
{
RectF rect = new RectF(AccelActivity.x, AccelActivity.y, AccelActivity.x + width, AccelActivity.y
+ height); // set bounds of rectangle
Paint p = new Paint(); // set some paint options
p.setColor(Color.BLUE);
canvas.drawRect(rect, p);
invalidate();
}
}
}
Yes, check this example, no use of xml there: How can I use the animation framework inside the canvas?
Use on sizeChanged to find the actual size of your canvas...
I have a View which draws a rectangle with a line of text inside of it. The view uses break text to ensure that no text extends outside of the rectangle; it ignores any text that does. This works fine for some characters, but often Strings made up of 'l's and 'f's extend outside of the rectangle. So, I'm in need of a sanity check here: Is there some obvious flaw in my below code, or is it possible that Paint.breakText(...) is inaccurate?
public void onDraw(Canvas canvas)
{
int MARGIN = 1;
int BORDER_WIDTH = 1;
Paint p = new Paint();
p.setAntiAlias(true);
p.setTextSize(12);
p.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL));
RectF rect = getRect();
float maxWidth = rect.width() - MARGIN - BORDER_WIDTH * 2;
String str = getText();
char[] chars = str.toCharArray();
int nextPos = p.breakText(chars, 0, chars.length, maxWidth, null);
str = str.substring(0, nextPos);
float textX = MARGIN + BORDER_WIDTH;
float textY = (float) (Math.abs(p.getFontMetrics().ascent) + BORDER_WIDTH + MARGIN);
canvas.drawText(str, textX, textY, p);
p.setStrokeWidth(BORDER_WIDTH);
p.setStyle(Style.STROKE);
canvas.drawRect(rect, p);
}
This was fixed by: Paint.setSubpixelText(true);
The problem might be how you draw your rectangle. Strokes are not outside of the rectangle, half of the stroke is inside, half is outside.