I'm trying to draw the spectrum of an audio file on a circle. Like this:
So on the circle I just want rectangles drawn like you see on the image.
I've got this code:
public void onRender(Canvas canvas, FFTData data, Rect rect) {
canvas.drawCircle(rect.width()/2, rect.height()/2, 200, mPaint);
for (int i = 0; i < data.bytes.length / mDivisions; i++) {
byte rfk = data.bytes[mDivisions * i];
byte ifk = data.bytes[mDivisions * i + 1];
float magnitude = (rfk * rfk + ifk * ifk);
int dbValue = (int) (10 * Math.log10(magnitude));
}
}
Where FFTData is the Fast Fourier Transformation data that Android gives me. Now in my dbValue I got the strength of the signal. mDivisions is how much bars I want. Currently set on 16 because I don't know how much I can set on the circle.
I'm stuck on how I can draw the rectangle with his center on the circle line... So I want a rectangle whose height is based on the dbValue so that I get high and low rectangles. And the center must be placed on my circle line.
Can someone help me on this math formula?
Run a loop over all 360 degrees of the circle (at wanted step), and, for each point, convert Polar (this angle and the radius of the circle) coordinates into Cartesian, as described here, for instance. This way you get the location of the centre of your rectangle.
Translate the system of the coordinates, making origin to be at the wanted point on the circle line and then rotate by the circle angle at that point.
Alternatively, you can build a trapezoid by getting corners at angle +- some offset and radius +- some offset (proportional to your value to plot). It will have shorter inner edge and longer outer edge. Such trapezoids may look better if painted side by side.
i think all you have needed is a pencil and a paper and a little math and also some free time to play :-)
public class MainActivity extends Activity {
ImageView drawingImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawingImageView = (ImageView) this.findViewById(R.id.DrawingImageView);
Paint paint;
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(16);
final Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
int centerX =400;
int centerY =400;
int R = 200;
canvas.drawCircle(centerX, centerY, R, paint);
int h = 100;
paint.setColor(Color.RED);
Path p = new Path();
p.moveTo(centerX + R - h/2, centerY);
p.lineTo(centerX + R + h/2, centerY);
canvas.drawPath(p, paint);
p = mySpectrumDrawer(centerX,centerY,R,h,15);
canvas.drawPath(p, paint);
h = 50;
p = mySpectrumDrawer(centerX,centerY,R,h,30);
canvas.drawPath(p, paint);
h = 60;
p = mySpectrumDrawer(centerX,centerY,R,h,60);
canvas.drawPath(p, paint);
h = 80;
p = mySpectrumDrawer(centerX,centerY,R,h,90);
canvas.drawPath(p, paint);
drawingImageView.setImageBitmap(bitmap);
}
private Path mySpectrumDrawer(int centerX, int centerY,int R,int height, int angel){
Path p = new Path();
int dX = (int) (R*(Math.cos(Math.toRadians(angel))));
int dY = (int) (R*(Math.sin(Math.toRadians(angel))));
int dhx = (int) (height/2*(Math.cos(Math.toRadians(angel))));
int dhy = (int) (height/2*(Math.sin(Math.toRadians(angel))));
p.moveTo(centerX + dX - dhx , centerY - dY + dhy);
p.lineTo(centerX + dX + dhx , centerY - dY - dhy);
return p;
}
}
I'm drawing a circle and I want on the circle line rectangles that are rotated in the right degree. Like on this image:
With the help off someone on SO I've found a way to do that (almost).
I've got this:
Like you see there are 2 things missing:
1) they are not on the circle line
2) They are not rotated
I know how to rotate on a canvas in android but then they are all mixed up.
This is my code:
int r = 200;
canvas.save();
mPaint.setStyle(Paint.Style.STROKE);
canvas.translate(rect.width() / 2, rect.height() / 2);
canvas.drawCircle(0, 0, r, mPaint);
mPaint.setStyle(Paint.Style.FILL);
for (int alpha = 0; alpha <= 360; alpha += 20) {
double x = r/3 * Math.cos(Math.toRadians(alpha));
double y = r/3 * Math.sin(Math.toRadians(alpha));
canvas.translate((float)x, (float)y);
canvas.drawRect(0, 0, 70, 20, mPaint);
}
canvas.restore();
Can someone point me out on what I'm doing wrong, why they aren't showing on the line of the circle? And second how to rotate, because when I do canvas.rotate(alpha) they aren't in a circle anymore.
EDIT:
My code is now like this:
int r = 200;
canvas.save();
mPaint.setStyle(Paint.Style.STROKE);
canvas.translate(rect.width() / 2, rect.height() / 2);
canvas.drawCircle(0, 0, r, mPaint);
mPaint.setStyle(Paint.Style.FILL);
for (int alpha = 0; alpha <= 360; alpha += 20) {
double x = r * Math.cos(Math.toRadians(alpha));
double y = r * Math.sin(Math.toRadians(alpha));
canvas.rotate(20.f);
canvas.drawRect(0, 0, 70, 20, mPaint);
}
canvas.restore();
and it gives me this:
EDIT 2:
code is like:
int r = 200;
canvas.save();
mPaint.setStyle(Paint.Style.STROKE);
canvas.translate(rect.width() / 2, rect.height() / 2);
canvas.drawCircle(0, 0, r, mPaint);
mPaint.setStyle(Paint.Style.FILL);
for (int alpha = 0; alpha <= 360; alpha += 20) {
double x = r * Math.cos(Math.toRadians(alpha));
double y = r * Math.sin(Math.toRadians(alpha));
canvas.rotate(20.f);
canvas.save();
canvas.translate(0, -200);
canvas.drawRect(0, 0, 70, 20, mPaint);
canvas.restore();
}
canvas.restore();
Result:
Doing a rotation with a translation sounds like an impossible task. Draw your rectangles straight and rotate the Canvas with Canvas.rotate() instead.
See: Canvas API
I'm drawing some circles on a canvas. I want to apply a radial gradient to each of this circle.
I'm currently allocating a new gradient for each circle, but i'm guessing this not a very good idea.
protected void onDraw(Canvas canvas)
{
int radius = 6;
int cx = radius;
int cy = radius ;
for(int i = 0; i < nbPage; i++)
{
if(i % 12 == 0 && i > 0) {
cx = radius;
cy += 20;
}
RadialGradient gradient = new RadialGradient(cx, cy, radius, 0xFFFFFFFF,
0xFF000000, android.graphics.Shader.TileMode.CLAMP);
p.setDither(true);
p.setShader(gradient);
canvas.drawCircle(cx, cy, radius, p);
cx += 20; //16px + 4 de marge
}
}
Is there a solution to preallocate the radial gradient knowing that each circle have the same radius but differents coordinates ?
Thanks
Take the RadialGradient object and draw it to a Bitmap, then proceed to draw that Bitmap to the Canvas for each circle.
Bitmap circleBitmap = Bitmap.create((int) (radius * 2.0f), (int) (radius * 2.0f),
Bitmap.Config.ARGB_8888);
Canvas tempCanvas = new Canvas(circleBitmap);
RadialGradient gradient = new RadialGradient(cx, cy, radius, 0xFFFFFFFF,
0xFF000000, android.graphics.Shader.TileMode.CLAMP);
p.setDither(true);
p.setShader(gradient);
tempCanvas.drawCircle(radius, radius, radius, p);
for (int i = 0; i < nbPage; i++)
canvas.drawBitmap(circleBitmap, cx + (i * 20) - radius, cy - radius, p);
I am try to draw text and bitmap images in my app. I would like the text to be drawn above the bitmap icons but am having difficulties in achieving that.
How do i modify or change my code so it displays at the top of each icon
my code:
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
xCanvas = canvas.getWidth();
yCanvas = canvas.getHeight();
Paint textPaint2 = new Paint();
textPaint2.setStyle(Paint.Style.FILL_AND_STROKE);
textPaint2.setAntiAlias(true);
textPaint2.setColor(Color.WHITE);
textPaint2.setTextSize(30);
textPaint2.setTextAlign(Align.CENTER);
destination = new Location("manual");
for (int j = 0; j < placesListItems.size(); j++){
song = placesListItems.get(j);
this.lat = myLat.get(j);
this.lng = myLng.get(j);
this.name=song.get(KEY_NAME);
try {
this.icon = ICON.get(j);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Log.d("the latitude",(String.valueOf(this.lat)));
// Log.d("the longitude",(String.valueOf(this.lng)));
// Double.parseDouble(song.get(KEY_LNG));
destination.setLatitude(this.lat);
destination.setLongitude(this.lng);
//Log.d("Place name",name );
this.location.distanceTo(destination);
// compute rotation matrix
float rotation[] = new float[9];
float identity[] = new float[9];
if (lastAccelerometer != null && lastCompass != null) {
boolean gotRotation = SensorManager.getRotationMatrix(rotation,
identity, lastAccelerometer, lastCompass);
if (gotRotation) {
float cameraRotation[] = new float[9];
// remap such that the camera is pointing straight down the
// Y
// axis
SensorManager.remapCoordinateSystem(rotation,
SensorManager.AXIS_X, SensorManager.AXIS_Z,
cameraRotation);
// orientation vector
orientation = new float[3];
SensorManager.getOrientation(cameraRotation, orientation);
canvas.save();
// Translate, but normalize for the FOV of the camera --
// basically, pixels per degree, times degrees == pixels
float dx = (float) ((canvas.getWidth() / horizontalFOV) * (Math
.toDegrees(orientation[0]) - this.location
.bearingTo(destination)));
float dy = (float) ((canvas.getHeight() / verticalFOV) * Math
.toDegrees(orientation[1]));
// wait to translate the dx so the horizon doesn't get
// pushed off
canvas.translate(0.0f, 0.0f - dy);
// now translate the dx
canvas.translate(0.0f - dx, 0.0f);
canvas.drawText((truncate(this.name,10).concat("...")), canvas.getWidth()/2 - 50, canvas.getHeight() / 2 - 100,
textPaint2);
canvas.drawBitmap(icon, canvas.getWidth()/2 - icon.getWidth()/2, canvas.getHeight()/2 - icon.getHeight()/2, null);
canvas.restore();
}
}
}
}
The trick is to get hold of metrics for the font you're using. You can do this using
Paint.FontMetrics fm = textPaint2.getFontMetrics();
int fontHeight = fm.bottom - fm.top;
(tweaked to use bottom and top which seem to be more accurate)
You can then adjust the vertical location based on the real text size (rather than using arbitrary numbers)
Suppose you have an icon that you want to paint with two lines of text above it (a label and the coordinates), and you want the icon centered at x, y. The following example demonstrates this.
NOTE: You allocate Paint objects inside your draw method - this is really a bad idea, as the objects are always the same and just end up kicking off the garbage collector. A lot. Allocate the once and reuse. I demonstrate this as well.
I draw the crossed lines to point at the target x,y values; you won't need them, but it helps show exactly where the target is relative to the icon and text.
package com.javadude.sample;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontMetrics;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
public static class MyView extends View {
public MyView(Context context) {
super(context);
}
private Paint textPaint;
private Paint linePaint;
private Drawable drawable;
private float textHeight;
private float baselineOffset;
private void drawIconAndText(Canvas canvas, int centerX, int centerY, Drawable drawable, String text) {
// draw the drawable centered on (targetXcenter, targetYcenter)
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
// determine upper-left corner of drawable location
int x = centerX - w/2;
int y = centerY - h/2;
// draw the icon
drawable.setBounds(x, y, x+w, y+h);
drawable.draw(canvas);
float textY = y - baselineOffset;
// note that drawText centers the text at the given location due to Align.CENTER
canvas.drawText(text, centerX, textY, textPaint);
// if you had used Align.LEFT, you would need to offset the start of the text as follows:
// float textWidth = textPaint.measureText(text);
// canvas.drawText(text, centerX - textWidth/2, textY, textPaint);
// draw the coordinates above it
textY = textY - textHeight; // move up a line
canvas.drawText("(" + centerX + "," + centerY + ")", centerX, textY, textPaint);
}
private void initPaint() {
linePaint = new Paint();
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(Color.BLUE);
textPaint = new Paint();
textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
textPaint.setAntiAlias(true);
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(30);
textPaint.setTextAlign(Align.CENTER);
FontMetrics fontMetrics = textPaint.getFontMetrics();
baselineOffset = fontMetrics.bottom;
// bottom is the maximum amount that the text descends
// I'm not sure why this is different from descent...
textHeight = fontMetrics.bottom - fontMetrics.top;
drawable = getResources().getDrawable(R.drawable.ic_launcher);
}
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = canvas.getWidth();
int height = canvas.getHeight();
int targetXcenter = width/2;
int targetYcenter = height/2;
// only allocate objects once it at all possible!
if (textPaint == null)
initPaint();
canvas.drawColor(Color.BLACK); // draw background
// draw lines to show where the target is
canvas.drawLine(0, targetYcenter, width-1, targetYcenter, linePaint);
canvas.drawLine(targetXcenter, 0, targetXcenter, height-1, linePaint);
drawIconAndText(canvas, targetXcenter, targetYcenter, drawable, "Sample Text");
// draw lines to show where the target is
canvas.drawLine(0, 200, width-1, 200, linePaint);
canvas.drawLine(100, 0, 100, height-1, linePaint);
drawIconAndText(canvas, 100, 200, drawable, "More Text");
}
}
}
I have two points in the canvas, now I'm able to draw a line between those points like this below image by using
This code canvas.drawLine(p1.x, p1.y, p2.x, p2.y, paint);
I want to draw the arc between two points like below image.
How can I draw like this.
Finally I got the solution from this code:
float radius = 20;
final RectF oval = new RectF();
oval.set(point1.x - radius, point1.y - radius, point1.x + radius, point1.y+ radius);
Path myPath = new Path();
myPath.arcTo(oval, startAngle, -(float) sweepAngle, true);
To calculate startAngle, use this code:
int startAngle = (int) (180 / Math.PI * Math.atan2(point.y - point1.y, point.x - point1.x));
Here, point1 means where you want to start drawing the Arc. sweepAngle means the angle between two lines. We have to calculate that by using two points like the blue points in my Question image.
Do something like this:
//Initialized paint on a class level object.
Paint p = new Paint();
p.setColor(Color.BLACK);
//Calculate the rect / bounds of oval
RectF rectF = new RectF(50, 20, 100, 80);
#Override
protected void onDraw(Canvas canvas) {
//Do the drawing in onDraw() method of View.
canvas.drawArc (rectF, 90, 45, false, p);
}
first we need to visual how the coordinates are in terms of start and sweep angels then it will become more clear.
so if you wanted just the right top piece of the circle, we could do something like this:
val rect = RectF(0f, 0f, 500f, 300f)
val paint = Paint()
paint.apply {
strokeWidth = 5f
setStyle(Paint.Style.STROKE)
color = COLOR.BLUE
}
path.addArc(rect, 270f, 90f)
..
this starts at 270 (per the diagram above and 'sweeps` 90 degrees forward. you then have this shape:
let's create one more so you get the hang of it. this time let's use a negative value: we want to create a semi half moon (arc) starting from the right side:
path.addArc(rect, 0f, -180f)
here we started at 0 and 'sweeped` -180 degrees.
and the results are:
I was trying to do something a little different and it's all about calculating sweep and start angles.
I wanted to show an arc that represents progress on a circle that goes from top to bottom.
So I had progress value from 0...100 and I want to show an arc that start from top to bottom to fill the circle when the progress is 100.
To calculate the sweepAngle I use:
int sweepAngle = (int) (360 * (getProgress() / 100.f));
Next is to calculate the startAngle
int startAngle = 270 - sweepAngle / 2;
Start Angle is calculated this way because:
It's always going to start from the left side, starting from the top to bottom. So starting angle at the top equals 270 (Note that it goes clockwise and 0 = 3 o'clock, so 12 o'clock equals 270 degrees)
Next I want to calculate how far I'm going to get away from my starting point (270) and to do that I only calculate half of the sweep angle because only half of the arc will be on the left side and the other half on the right side.
So considering I have progress of 25%
sweepAngle = 90 degrees (90 degrees is quarter of a circle)
start angle = 225 (45 degrees away from 270)
If you want the progress to go from other sides (Left to right, right to left etc..) you will only need to replace 270 with the starting the angle.
I may be late to answer but I got more information.
After Android Lollipop there are two ways to address this problem
public void drawArc(RectF oval, float startAngle, float sweepAngle,
boolean useCenter, Paint paint)
public void drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, boolean useCenter, Paint paint)
Usage:
RectF rectF = new RectF(left, top, right, bottom);
// method 1
canvas.drawArc (rectF, 90, 45, true, paints[0]);
// method 2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 0, 45, true, paints[1]);
}
Sweep angle is nothing more than angle of Sector which is drawn clockwise eg. for below code
private void drawArcs(Canvas canvas) {
RectF rectF = new RectF(left, top, right, bottom);
// white arc
canvas.drawArc (rectF, 90, 45, true, paints[0]);
// Green arc
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 0, 45, true, paints[1]);
}
// Red stroked arc
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 180, 45, true, paints[2]);
}
}
Result will look like this
Same can be achieved with the help of defining Paths and then iterating over them in onDraw method as illustrated in this snippet:
public class ArcDrawable extends Drawable {
private int left, right, top, bottom;
private Paint[] paints = new Paint[3];
private HashMap<Path, Paint> pathMap = new HashMap();
public ArcDrawable() {
// white paint
Paint whitePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
whitePaint.setColor(Color.WHITE);
paints[0]= whitePaint;
// green paint
Paint greenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
greenPaint.setColor(Color.GREEN);
paints[1]= greenPaint;
// red paint
Paint redPaint =new Paint(Paint.ANTI_ALIAS_FLAG);
redPaint.setColor(Color.RED);
redPaint.setStyle(Paint.Style.STROKE);
paints[2]= redPaint;
}
#Override
public void draw(Canvas canvas) {
//----------USE PATHS----------
// Define and use custom Path
for (Map.Entry<Path, Paint> entry : pathMap.entrySet()) {
// Draw Path on respective Paint style
canvas.drawPath(entry.getKey(), entry.getValue());
}
// -------OR use conventional Style---------
//drawArcs(canvas);
}
//Same result
private void drawArcs(Canvas canvas) {
RectF rectF = new RectF(left, top, right, bottom);
// method 1
canvas.drawArc (rectF, 90, 45, true, paints[0]);
// method 2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 0, 45, true, paints[1]);
}
// method two with stroke
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 180, 45, true, paints[2]);
}
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
int width = bounds.width();
int height = bounds.height();
left = bounds.left;
right = bounds.right;
top = bounds.top;
bottom = bounds.bottom;
final int size = Math.min(width, height);
final int centerX = bounds.left + (width / 2);
final int centerY = bounds.top + (height / 2);
pathMap.clear();
//update pathmap using new bounds
recreatePathMap(size, centerX, centerY);
invalidateSelf();
}
private Path recreatePathMap(int size, int centerX, int centerY) {
RectF rectF = new RectF(left, top, right, bottom);
// first arc
Path arcPath = new Path();
arcPath.moveTo(centerX,centerY);
arcPath.arcTo (rectF, 90, 45);
arcPath.close();
// add to draw Map
pathMap.put(arcPath, paints[0]);
//second arc
arcPath = new Path();
arcPath.moveTo(centerX,centerY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
arcPath.arcTo (rectF, 0, 45);
}
arcPath.close();
// add to draw Map
pathMap.put(arcPath, paints[1]);
// third arc
arcPath = new Path();
arcPath.moveTo(centerX,centerY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
arcPath.arcTo (rectF, 180, 45);
}
arcPath.close();
// add to draw Map
pathMap.put(arcPath, paints[2]);
return arcPath;
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return 0;
}
}
Complete source code:
https://github.com/hiteshsahu/Arc-Drawable
a sample for draw arc.
public static Bitmap clipRoundedCorner(Bitmap bitmap, float r, boolean tr, boolean tl, boolean bl, boolean br)
{
int W = bitmap.getWidth();
int H = bitmap.getHeight();
if (r < 0)
r = 0;
int smallLeg = W;
if(H < W )
smallLeg = H;
if (r > smallLeg)
r = smallLeg / 2;
float lineStop = r/2;
Path path = new Path();
path.moveTo(0,0);
if(tr)
{
path.moveTo(0, lineStop);
path.arcTo(new RectF(0,0, r,r), 180, 90, false);
}
path.lineTo(W-lineStop, 0);
if(tl)
path.arcTo(new RectF(W-r,0, W,r), 270, 90, false);
else
path.lineTo(W, 0);
path.lineTo(W, H-lineStop);
if(bl)
path.arcTo(new RectF(W-r,H-r, W,H), 0, 90, false);
else
path.lineTo(W, H);
path.lineTo(lineStop, H);
if(br)
path.arcTo(new RectF(0,H-r, r,H), 90, 90, false);
else
path.lineTo(0,H);
if(tr)
path.lineTo(0,lineStop);
else
path.lineTo(0,0);
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
canvas.drawPath(path, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, 0, 0, paint);
return output;
}
A simple solution was suggested here by Langkiller. This draws a cubic line from the start point via the control point to the end point.
Path path = new Path();
float startX = 0;
float startY = 2;
float controlX = 2;
float controlY = 4;
float endX = 4
float endY = 2
conePath.cubicTo(startX, startY, controlX, controlY,endX, endY);
Paint paint = new Paint();
paint.setARGB(200, 62, 90, 177);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint)