I want to create a shape like this on a canvas. I know how to draw a circle with a stroke but I want a crescent moon kind of an effect on the circle.
Here is the code of the circle:
Bitmap bitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
float mid1;
float min1,fat1,half1,rad1;
mid1 = ImageWidth / 2;
min1 = Math.min(ImageWidth, ImageHeight);
fat1 = min1 / 17;
half1 = min1 / 2;
rad1 = half1 - fat1;
mid1 = mid1 - half1;
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
canvas.drawCircle(mid1 + half1, half1, rad1, paint);
The code is working perfectly for the circle. I know how to draw a stroke too but I cant create a crescent moon effect.
Set a circular clip path and then draw a second circle displaced by some quantity in both x and y.
You can refer to this view,Use Bezier curve to draw, use PorterDuff.Mode.DST_OUT to get the correct partial view (moon view).
While searching for results I came across this
How can I convert a View to a Drawable?
The idea behind this is I can convert TextView into a bitmap first and then combine the two bitmaps. But by converting the TextView into Bitmap, it would make it lose it's transparency which I don't want. I want the TextView over my ImageView but as one image using canvas.
My idea is to create images like these:
Here I want to enclose the text with a drawable shape for that white border.
And then place this Textview on an image and then save everything as a bitmap.
Please help
You can try to draw text and lines on canvas.
Below is example method which draws text on drawable image and returns Bitmap.
You can set custom font by calling .setTypeface() on Paint object.
Call canvas.drawLine() to draw line. To customize your line you can create new Paint object, set its color and width by .setColor() and .setStrokeWidth() and pass it in drawLine() together with line coordinates.
public Bitmap drawTextOnBitmap(Context context, int resId, String text) {
// prepare canvas
Resources resources = context.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, resId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if (bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
// resource bitmaps are immutable, so we need to convert it to mutable one
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
// new antialiased Paint
TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
// text color - #3D3D3D
paint.setColor(Color.rgb(61, 61, 61));
// text size in pixels
paint.setTextSize((int) (bitmap.getHeight() / 10 * scale));
// text shadow
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
// set text width to canvas width minus 16dp padding
int textWidth = canvas.getWidth() - (int) (16 * scale);
// init StaticLayout for text
StaticLayout textLayout = new StaticLayout(text, paint, textWidth,
Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
// get height of multiline text
int textHeight = textLayout.getHeight();
// get position of text's top left corner
float x = (bitmap.getWidth() - textWidth) / 2;
float y = (bitmap.getHeight() - textHeight) / 2;
// draw text to the Canvas center
canvas.save();
canvas.translate(x, y);
textLayout.draw(canvas);
canvas.restore();
return bitmap;
}
Update:
To draw rectangle add this to the method:
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(24);
RectF rectF = new RectF(80, 150, 200, 350);
canvas.drawRect(rectF, p);
Parameters for new RectF():
left
The X coordinate of the left side of the rectangle
top
The Y coordinate of the top of the rectangle
right
The X coordinate of the right side of the rectangle
bottom
The Y coordinate of the bottom of the rectangle
I want to create a rounded graph that will display a range of values from my app. The values can be classified to 3 categories: low, mid, high - that are represented by 3 colors: blue, green and red (respectively).
Above this range, I want to show the actually measured values - in a form of a "thumb" over the relevant range part:
The location of the white thumb over the range arc may change, according to the measured values.
Currently, I'm able to draw the 3-colored range by drawing 3 arcs over the same center, inside the view's onDraw method:
width = (float) getWidth();
height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 3;
} else {
radius = width / 3;
}
paint.setAntiAlias(true);
paint.setStrokeWidth(arcLineWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
center_x = width / 2;
center_y = height / 1.6f;
left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;
oval.set(left, top, right, bottom);
//blue arc
paint.setColor(colorLow);
canvas.drawArc(oval, 135, 55, false, paint);
//red arc
paint.setColor(colorHigh);
canvas.drawArc(oval, 350, 55, false, paint);
//green arc
paint.setColor(colorNormal);
canvas.drawArc(oval, 190, 160, false, paint);
And this is the result arc:
My question is, how do I:
Create a smooth gradient between those 3 colors (I tried using
SweepGradient but it didn't give me the correct result).
Create the overlay white thumb as shown in the picture, so that I'll be able to control where to display it.
Animate this white thumb over my range arc.
Note: the 3-colored range is static - so another solution can be to just take the drawable and paint the white thumb over it (and animate it), so I'm open to hear such a solution as well :)
I would use masks for your first two problems.
1. Create a smooth gradient
The very first step would be drawing two rectangles with a linear gradient. The first
rectangle contains the colors blue and green while the second rectangle contains green
and red as seen in the following picture. I marked the line where both rectangles touch each other
black to clarify that they are infact two different rectangles.
This can be achieved using the following code (excerpt):
// Both color gradients
private Shader shader1 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(101, 172, 242), Shader.TileMode.CLAMP);
private Shader shader2 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(255, 31, 101), Shader.TileMode.CLAMP);
private Paint paint = new Paint();
// ...
#Override
protected void onDraw(Canvas canvas) {
float width = 800;
float height = 800;
float radius = width / 3;
// Arc Image
Bitmap.Config conf = Bitmap.Config.ARGB_8888; // See other config types
Bitmap mImage = Bitmap.createBitmap(800, 800, conf); // This creates a mutable bitmap
Canvas imageCanvas = new Canvas(mImage);
// Draw both rectangles
paint.setShader(shader1);
imageCanvas.drawRect(0, 0, 400, 800, paint);
paint.setShader(shader2);
imageCanvas.drawRect(400, 0, 800, 800, paint);
// /Arc Image
// Draw the rectangle image
canvas.save();
canvas.drawBitmap(mImage, 0, 0, null);
canvas.restore();
}
As your goal is having a colored arc with rounded caps, we next need to define the area of
both rectangles that should be visible to the user. This means that most of both rectangles
will be masked away and thus not visible. Instead the only thing to remain is the arc area.
The result should look like this:
In order to achieve the needed behavior we define a mask that only reveals the arc area within
the rectangles. For this we make heavy use of the setXfermode method of Paint. As argument
we use different instances of a PorterDuffXfermode.
private Paint maskPaint;
private Paint imagePaint;
// ...
// To be called within all constructors
private void init() {
// I encourage you to research what this does in detail for a better understanding
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
imagePaint = new Paint();
imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}
#Override
protected void onDraw(Canvas canvas) {
// #step1
// Mask
Bitmap mMask = Bitmap.createBitmap(800, 800, conf);
Canvas maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setShader(null);
paint.setStrokeWidth(70);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
final RectF oval = new RectF();
center_x = 400;
center_y = 400;
oval.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawArc(oval, 135, 270, false, paint);
// /Mask
canvas.save();
// This is new compared to step 1
canvas.drawBitmap(mMask, 0, 0, maskPaint);
canvas.drawBitmap(mImage, 0, 0, imagePaint); // Notice the imagePaint instead of null
canvas.restore();
}
2. Create the overlay white thumb
This solves your first problem. The second one can be achieved using masks again, though this
time we want to achieve something different. Before, we wanted to show only a specific area (the arc)
of the background image (being the two rectangles). This time we want to do the opposite:
We define a background image (the thumb) and mask away its inner content, so that only
the stroke seems to remain. Applied to the arc image the thumb overlays the colored arc with
a transparent content area.
So the first step would be drawing the thumb. We use an arc for this with the same radius as
the background arc but different angles, resulting in a much smaller arc. But becaus the
thumb should "surround" the background arc, its stroke width has to be bigger than the
background arc.
#Override
protected void onDraw(Canvas canvas) {
// #step1
// #step2
// Thumb Image
mImage = Bitmap.createBitmap(800, 800, conf);
imageCanvas = new Canvas(mImage);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(120);
final RectF oval2 = new RectF();
center_x = 400;
center_y = 400;
oval2.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
imageCanvas.drawArc(oval2, 270, 45, false, paint);
// /Thumb Image
canvas.save();
canvas.drawBitmap(RotateBitmap(mImage, 90f), 0, 0, null);
canvas.restore();
}
public static Bitmap RotateBitmap(Bitmap source, float angle)
{
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}
The result of the code is shown below.
So now that we have a thumb that is overlaying the background arc, we need to define the mask
that removes the inner part of the thumb, so that the background arc becomes visible again.
To achieve this we basically use the same parameters as before to create another arc, but
this time the stroke width has to be identical to the width used for the background arc as
this marks the area we want to remove inside the thumb.
Using the following code, the resulting image is shown in picture 4.
#Override
protected void onDraw(Canvas canvas) {
// #step1
// #step2
// Thumb Image
// ...
// /Thumb Image
// Thumb Mask
mMask = Bitmap.createBitmap(800, 800, conf);
maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(70);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
final RectF oval3 = new RectF();
center_x = 400;
center_y = 400;
oval3.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawBitmap(mImage, 0, 0, null);
maskCanvas.drawArc(oval3, 270, 45, false, paint);
// /Thumb Mask
canvas.save();
canvas.drawBitmap(RotateBitmap(mMask, 90f), 0, 0, null); // Notice mImage changed to mMask
canvas.restore();
}
3. Animate the white thumb
The last part of your question would be animating the movement of the arc. I have no solid
solution for this, but maybe can guide you in a useful direction. I would try the following:
First define the thumb as a ImageView that is part of your whole arc graph. When changing
the selected values of your graph, you rotate the thumb image around the center of the background
arc. Because we want to animate the movement, just setting the rotation of the thumb image would
not be adequate. Instead we use a RotateAnimation kind of like so:
final RotateAnimation animRotate = new RotateAnimation(0.0f, -90.0f, // You have to replace these values with your calculated angles
RotateAnimation.RELATIVE_TO_SELF, // This may be a tricky part. You probably have to change this to RELATIVE_TO_PARENT
0.5f, // x pivot
RotateAnimation.RELATIVE_TO_SELF,
0.5f); // y pivot
animRotate.setDuration(1500);
animRotate.setFillAfter(true);
animSet.addAnimation(animRotate);
thumbView.startAnimation(animSet);
This is far from final I guess, but it very well may aid you in your search for the needed
solution. It is very important that your pivot values have to refer to the center of your
background arc as this is the point your thumb image should rotate around.
I have tested my (full) code with API Level 16 and 22, 23, so I hope that this answer at least
gives you new ideas on how to solve your problems.
Please note that allocation operations within the onDraw method are a bad idea and should
be avoided. For simplicity I failed to follow this advise. Also the code is to be used as
a guide in the right direction and not to be simply copy & pasted, because it makes heavy
use of magic numbers and generally does not follow good coding standards.
I would change a bit of the way you draw your view, by looking on the original design, instead of drawing 3 caps I would draw just 1 line, that way the SweepGradient will work.
This migth be a bit tricky, you have 2 options:
create a Path with 4 arcs
draw 2 arcs- one is the big white (filled with white so you still want to use Paint.Style.STROKE) and another on top of that make it fill transparent, you can achieve it with PorterDuff xfermode, it probably take you couple of tries until you get that without clearing the green circle too.
I imagine you want to animate thumb position, so just use simple Animation that invalidate the view and draw the thumb view position accordingly.
Hopes this helps
Create a gradient than follow a path is not so simple.
So I can suggest you to use some libraries than already did it.
Include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
Create the gauge in XML:
<com.sccomponents.gauges.library.ScArcGauge
android:id="#+id/gauge"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
Your code:
ScArcGauge gauge = this.findViewById(R.id.gauge);
gauge.setAngleSweep(270);
gauge.setAngleStart(135);
gauge.setHighValue(90);
int lineWidth = 50;
ScCopier baseLine = gauge.getBase();
baseLine.setWidths(lineWidth);
baseLine.setColors(Color.parseColor("#dddddd"));
baseLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
ScCopier progressLine = gauge.getProgress();
progressLine.setWidths(lineWidth);
progressLine.setColors(
Color.parseColor("#65AAF2"),
Color.parseColor("#3EF2AD"),
Color.parseColor("#FF2465")
);
progressLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
Your result:
You can find something more complex on this site:
ScComponents
How to draw a filled rectangle with specified bounds and inside that rectangle text to be drawn using Canvas Android ?? I tried
mPaint.setColor(Color.GREEN);
canvas.drawText(mText, x, y, mPaint);
mPaint.setColor(Color.BLACK);
canvas.drawRect(x, y, x + w, y + h, mPaint);
but text is not inside of that rectangle. Can any buddy tell me how to draw a rectangle surrounding specified text with consideration of text size ??
Here i have hardcoded x and y values. You can change them
mpaint= new Paint();
mpaint.setColor(Color.RED);
mpaint.setStyle(Style.FILL);
paint2= new Paint();
paint2.setColor(Color.GREEN);
paint2.setTextSize(50); //set text size
float w = paint2.measureText(s)/2;
float textSize = paint2.getTextSize();
#Override
protected void onDraw(Canvas canvas) {
paint2.setTextAlign(Paint.Align.CENTER);
canvas.drawRect(300-w, 300 - textsize, 300 + w, 300, mpaint);
canvas.drawText(s, 300, 300 ,paint2); //x=300,y=300
}
Edit :
Its bad a idea to call measureText in onDraw. You can do that outside of onDraw.
There is a video on also about performance and why you should avoid allocations in onDraw. https://www.youtube.com/watch?v=HAK5acHQ53E
Resulting snap shot
If you have to center the text inside de rect you have use this code
mpaint= new Paint();
mpaint.setColor(Color.RED);
mpaint.setStyle(Style.FILL);
paint2= new Paint();
paint2.setColor(Color.GREEN);
paint2.setTextSize(50); //set text size
float w = paint2.measureText(s)/2;
float textSize = paint2.getTextSize();
#Override
protected void onDraw(Canvas canvas) {
paint2.setTextAlign(Paint.Align.CENTER);
Rect rect = new Rect(300-w, 300 - textsize, 300 + w, 300);
canvas.drawRect(rect, mpaint);
canvas.drawText(s, rect.centerX(), rect.centerY() ,paint2); // center text inside rect
}
This might be very late for this particular query but I think many will find this answer useful. So, the problem with the Canvas for any CustomView is that, you can get the width for a particular text, but it's not that easy to get the height of the text. Also if you are using canvas.drawText(....) with simple Paint object, you can not draw multi line text. So, use the below code within your onDraw() method.
String displayText = "Hello World";
int mainTextPositionX = getWidth() / 2 ;
int mainTextPositionY = getHeight() / 2;
StaticLayout textStaticLayout;
TextPaint textPaint;
textPaint = new TextPaint();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(Color.BLUE);
textPaint.setAntiAlias(true);
textPaint.setTextSize(convertDpToPixel(30, context));
textPaint.setTextAlign(Paint.Align.CENTER);
highlightedRectPaint = new Paint();
highlightedRectPaint.setStrokeWidth(12);
highlightedRectPaint.setStyle(Paint.Style.STROKE);
highlightedRectPaint.setColor(Color.RED);
highlightedRectPaint.setAntiAlias(true);
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
textStaticLayout = StaticLayout
.Builder
.obtain(displayText, 0, displayText.length(), textPaint, (int) textPaint.measureText(displayText))
.build();
}else{
textStaticLayout = new StaticLayout(
displayText, textPaint, (int)textPaint.measureText(displayText), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
}
Rect highlightedTextBorderRect = new Rect();
highlightedTextBorderRect.top = mainTextPositionY-20;
highlightedTextBorderRect.left = mainTextPositionX-
((int)textPaint.measureText(displayText)/2)-20;
highlightedTextBorderRect.right = mainTextPositionX+
((int)textPaint.measureText(displayText)/2) + 20;
highlightedTextBorderRect.bottom = mainTextPositionY+
(int)textStaticLayout.getHeight()+20;
canvas.save();
canvas.translate(mainTextPositionX, mainTextPositionY);
textStaticLayout.draw(canvas);
canvas.restore();
canvas.drawRect(highlightedTextBorderRect,highlightedRectPaint);
just make sure that, you declare all the objects and variable outside of the draw() method. And this will draw a rectangle outline around the text with multi line support. If you want the rectangle to have a fill, then just use the highlightedRectPaint and change the setStyle(Paint.Style.FILL). Hope that helps.
How do you cut (remove) a section from a bitmap???
I want that section/shape to be removed.. leave transparent in place of section..
Say shape is cercle or square..
You should be able do this with a Porter-Duff color filter and a Canvas:
public void punchHole(Bitmap bitmap, float cx, float cy, float radius) {
Canvas c = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColorFilter(new PorderDuffColorFilter(0, PorderDuff.Mode.CLEAR));
c.drawCircle(cx, cy, radius, paint);
}
Well, that was wrong. However, using a Porter-Duff transfer mode does work:
public void punchHole(Bitmap bitmap, float cx, float cy, float radius) {
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawCircle(cx, cy, radius, paint);
}
(The bitmap passed as an arg needs to be modifiable, of course.)
Use Bitmap.setPixel(x,y,Color) function to set the desired pixels to transparent
for example:
Bitmap bmp = ...;
bmp.setPixel (100,100,Color.TRANSPARENT);
for the pixel at x/y offset 100,100. Though you'll find this potentially slow to do this on many pixels...
Did you try drawing a circle with a transparent color,
ARGB = 0,0,0,0 ?