I have a little drawing app and want to use "complex" shapes as brushes, i.e. a star.
Drawing with a simple brush already works with this code:
remotePath.reset();
remotePath.moveTo(start_x, start_y);
float dx = Math.abs(end_x - start_x);
float dy = Math.abs(end_y - start_y);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
remotePath.quadTo(start_x, start_y, (end_x + start_x) / 2, (end_y + start_y) / 2);
}
remotePath.lineTo(end_x, end_y);
// commit the path to our offscreen
mCanvas.drawPath(remotePath, remotePaint);
// kill this so we don't double draw
remotePath.reset();
invalidate();
I basically want the same functionality using this bitmap:
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.brush_star);
My solution currently is using a list of points (coordinates) to draw the bitmap. The problem with that solution is that it only draws bitmaps at the given points resulting in having gaps between each drawn bitmap. I rather would like to get a smooth line while drawing like with a simple brush without any gaps in between.
Current code for the bitmap drawing:
protected void onDraw(Canvas canvas) {
// Make canvas white
canvas.drawColor(Color.WHITE);
// Paintable area
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
for (Point point : points) {
canvas.drawBitmap(complexBrush, point.x, point.y, p);
}
}
What's the best way to do so?
Thanks for any help!
I use this
Point's class:
public class Point implements Serializable {
float x, y;
float dx, dy;
}
Paint object:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setColor(Color.RED);
paint.setAntiAlias(true);
draw on canvas:
private void drawCanvas(Canvas canvas, List<Point> pts){
if (pts.size() > 1){
Path path = new Path();
final int SMOOTH_VAL = 6;
for(int i = pts.size() - 2; i < pts.size(); i++){
if(i >= 0){
Point point = pts.get(i);
if(i == 0){
Point next = pts.get(i + 1);
point.dx = ((next.x - point.x) / SMOOTH_VAL);
point.dy = ((next.y - point.y) / SMOOTH_VAL);
}
else if(i == pts.size() - 1){
Point prev = pts.get(i - 1);
point.dx = ((point.x - prev.x) / SMOOTH_VAL);
point.dy = ((point.y - prev.y) / SMOOTH_VAL);
}
else{
Point next = pts.get(i + 1);
Point prev = pts.get(i - 1);
point.dx = ((next.x - prev.x) / SMOOTH_VAL);
point.dy = ((next.y - prev.y) / SMOOTH_VAL);
}
}
}
boolean first = true;
for(int i = 0; i < pts.size(); i++){
Point point = pts.get(i);
if(first){
first = false;
path.moveTo(point.x, point.y);
}
else{
Point prev = pts.get(i - 1);
path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y);
}
}
canvas.drawPath(path, paint);
} else {
if (pts.size() == 1) {
Point point = pts.get(0);
canvas.drawCircle(point.x, point.y, 2, paint);
}
}
}
Draw on bitmap canvas:
private void drawBitmap(Bitmap bmp, List<Point> pts) {
Canvas c = new Canvas(bmp);
drawCanvas(c, pts);
}
Related
I want to draw a resizable box. It should be a square as I move my finger.
I am using the following code for my android app, for re-sizable box drawing with onTouch Event.
http://chintanrathod.com/resizable-rectangle-overlay-on-touch-in-android/
This code exactly meets my requirement with only one exception. It is not limited square. I want to limit it to square as I move my finger. I have tried to limit the calculations by calculating the delta but haven't got succeed.
public class DrawBoxView extends View {
Point[] points = new Point[4];
private OnBoxTouchListener onBoxTouchListener;
Point startMovePoint;
/**
* point1 and point 3 are of same group and same as point 2 and point4
*/
int groupId = -1;
private ArrayList<ColorBall2> colorballs = new ArrayList<ColorBall2>();
// array that holds the balls
private int balID = 0;
// variable to know what ball is being dragged
Paint paint;
Canvas canvas;
public DrawBoxView(Context context) {
super(context);
paint = new Paint();
setFocusable(true); // necessary for getting the touch events
canvas = new Canvas();
}
public DrawBoxView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public DrawBoxView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
setFocusable(true); // necessary for getting the touch events
canvas = new Canvas();
}
// the method that draws the balls
#Override
protected void onDraw(Canvas canvas) {
if (points[0] == null) {
//initialize rectangle.
points[0] = new Point();
points[0].x = 50;
points[0].y = 20;
points[1] = new Point();
points[1].x = 200;
points[1].y = 20;
points[2] = new Point();
points[2].x = 200;
points[2].y = 170;
points[3] = new Point();
points[3].x = 50;
points[3].y = 170;
balID = 2;
groupId = 1;
// declare each ball with the ColorBall class
int count = 0;
for (Point pt : points) {
colorballs.add(new ColorBall2(getContext(), R.drawable.gray_circle, pt,count++));
}
}
if(points[3]==null) //point4 null when user did not touch and move on screen.
return;
/**My modification to calculate delta**/
int diffWidth = Math.abs(colorballs.get(1).getX() - colorballs.get(0).getX());
int diffHeight = Math.abs(colorballs.get(1).getY() - colorballs.get(0).getY());
/**End of My modification**/
int left, top, right, bottom;
left = points[0].x;
top = points[0].y;
right = points[0].x;
bottom = points[0].y;
for (int i = 1; i < points.length; i++) {
left = left > points[i].x ? points[i].x : left;
top = top > points[i].y ? points[i].y : top;
int x_delta = Math.abs(points[0].x - points[1].x);
right = right < points[i].x ? points[i].x : right;
bottom = bottom < points[i].y ? points[i].y : bottom;
/**My modification to calculate delta**/
int y_delta = Math.abs(points[1].y - points[2].y);
if(x_delta != y_delta)
{
int bottom_delta = Math.abs(points[1].y - bottom);
if(bottom_delta < y_delta)
{
bottom = bottom + bottom_delta;
}
else if(bottom_delta > y_delta)
{
}
}
}
/**end of my code**/
Log.e("Left", String.valueOf(left));
Log.e("top", String.valueOf(top));
Log.e("right", String.valueOf(right));
Log.e("bottom", String.valueOf(bottom));
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(5);
//draw stroke
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(8);
canvas.drawRect(
left + colorballs.get(0).getWidthOfBall() / 2,
top + colorballs.get(0).getWidthOfBall() / 2,
right + colorballs.get(2).getWidthOfBall() / 2,
bottom + colorballs.get(2).getWidthOfBall() / 2, paint);
//fill the rectangle
paint.setStyle(Paint.Style.FILL);
paint.setColor(getResources().getColor(R.color.app_color_red_transparent));
paint.setStrokeWidth(0);
canvas.drawRect(
left + colorballs.get(0).getWidthOfBall() / 2,
top + colorballs.get(0).getWidthOfBall() / 2,
right + colorballs.get(2).getWidthOfBall() / 2,
bottom + colorballs.get(2).getWidthOfBall() / 2, paint);
//draw the corners
BitmapDrawable bitmap = new BitmapDrawable();
// draw the balls on the canvas
paint.setColor(Color.BLUE);
paint.setTextSize(18);
paint.setStrokeWidth(0);
for (int i =0; i < colorballs.size(); i ++) {
ColorBall2 ball = colorballs.get(i);
canvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(),
paint);
//canvas.drawText("" + (i+1), ball.getX(), ball.getY(), paint);
}
}
// events when touching the screen
public boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
int X = (int) event.getX();
int Y = (int) event.getY();
switch (eventaction) {
case MotionEvent.ACTION_DOWN: // touch down so check if the finger is on
// a ball
if (points[0] == null) {
//initialize rectangle.
points[0] = new Point();
points[0].x = X;
points[0].y = Y;
points[1] = new Point();
points[1].x = X;
points[1].y = Y + 30;
points[2] = new Point();
points[2].x = X + 30;
points[2].y = Y + 30;
points[3] = new Point();
points[3].x = X +30;
points[3].y = Y;
balID = 2;
groupId = 1;
// declare each ball with the ColorBall class
int count = 0;
for (Point pt : points) {
colorballs.add(new ColorBall2(getContext(), R.drawable.gray_circle, pt,count++));
}
} else {
//resize rectangle
balID = -1;
startMovePoint = new Point(X,Y);
groupId = -1;
for (int i = colorballs.size()-1; i>=0; i--) {
ColorBall2 ball = colorballs.get(i);
// check if inside the bounds of the ball (circle)
// get the center for the ball
int centerX = ball.getX() + ball.getWidthOfBall();
int centerY = ball.getY() + ball.getHeightOfBall();
paint.setColor(Color.CYAN);
// calculate the radius from the touch to the center of the
// ball
double radCircle = Math
.sqrt((double) (((centerX - X) * (centerX - X)) + (centerY - Y)
* (centerY - Y)));
if (radCircle < ball.getWidthOfBall()) {
balID = ball.getID();
if (balID == 1 || balID == 3) {
groupId = 2;
} else {
groupId = 1;
}
invalidate();
break;
}
invalidate();
}
}
break;
case MotionEvent.ACTION_MOVE: // touch drag with the ball
if (balID > -1) {
// move the balls the same as the finger
colorballs.get(balID).setX(X);
colorballs.get(balID).setY(Y);
paint.setColor(Color.CYAN);
/*if (groupId == 1) {
colorballs.get(1).setX(colorballs.get(0).getX());
colorballs.get(1).setY(colorballs.get(2).getY());
colorballs.get(3).setX(colorballs.get(2).getX());
colorballs.get(3).setY(colorballs.get(0).getY());
} else {
colorballs.get(0).setX(colorballs.get(1).getX());
colorballs.get(0).setY(colorballs.get(3).getY());
colorballs.get(2).setX(colorballs.get(3).getX());
colorballs.get(2).setY(colorballs.get(1).getY());
}
*/
if (groupId == 1) {
colorballs.get(1).setX(colorballs.get(0).getX());
colorballs.get(1).setY(colorballs.get(2).getY());
colorballs.get(3).setX(colorballs.get(2).getX());
colorballs.get(3).setY(colorballs.get(0).getY());
} else {
colorballs.get(0).setX(colorballs.get(1).getX());
colorballs.get(0).setY(colorballs.get(3).getY());
colorballs.get(2).setX(colorballs.get(3).getX());
colorballs.get(2).setY(colorballs.get(1).getY());
}
invalidate();
}else{
if (startMovePoint!=null) {
paint.setColor(Color.CYAN);
int diffX = X - startMovePoint.x;
int diffY = Y - startMovePoint.y;
startMovePoint.x = X;
startMovePoint.y = Y;
colorballs.get(0).addX(diffX);
colorballs.get(1).addX(diffX);
colorballs.get(2).addX(diffX);
colorballs.get(3).addX(diffX);
colorballs.get(0).addY(diffY);
colorballs.get(1).addY(diffY);
colorballs.get(2).addY(diffY);
colorballs.get(3).addY(diffY);
if(groupId==1)
canvas.drawRect(points[0].x, points[2].y, points[2].x, points[0].y,
paint);
else
canvas.drawRect(points[1].x, points[3].y, points[3].x, points[1].y,
paint);
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
// touch drop - just do things here after dropping
break;
}
// redraw the canvas
invalidate();
return true;
}
public OnBoxTouchListener getOnBoxTouchListener() {
return onBoxTouchListener;
}
public void setOnBoxTouchListener(OnBoxTouchListener onBoxTouchListener) {
this.onBoxTouchListener = onBoxTouchListener;
}
public Rect shade_region_between_points() {
if (points[0] == null && points[2] == null) {
return new Rect(0, 0, 0, 0);
}
else if (points[0] == null) {
return new Rect(0, points[2].y, points[2].x, 0);
}
else if (points[2] == null) {
return new Rect(points[0].x, 0, 0, points[0].y);
}
else
{
return new Rect(points[0].x, points[2].y, points[2].x, points[0].y);
}
}
public Point[] getPoints()
{
return points;
}
}
I have tried to change the formulas with TOUCH_MOVE and onDraw events by calculating the delta X, but I haven't got any success. Anyone can guide to modify this code will be highly appreciated.
Thank you.
I am going to relate to the code in the link you described to make it simple:
From what I understand, the balls in organized in these indexes:
0 3
1 2
To keep this simple, I will refer to each ball as their index.
Before setting X and Y of selected Ball, calculate Delta:
if (balID > -1) {
float deltaX = colorballs.get(ballID).getX() - X;
float deltaY = colorballs.get(ballID).getY() - Y;
colorballs.get(balID).setX(X);
colorballs.get(balID).setY(Y);
Now we use delta when we update balls coordinates:
if(balID == 0){ //Top Left ball
colorballs.get(1).setX(colorballs.get(balID).getX());
colorballs.get(2).setX(colorballs.get(2).getX() + deltaY);
colorballs.get(3).setY(colorballs.get(ballID).getY);
colorballs.get(3).setX(colorballs.get(2).getX());
}
If we move ball 0, this happens:
Dragging 0 up
^ ^
|deltaY | moves alongside 0
0 3 --> Expand by deltaY
1 2 --> Expand by deltaY
Dragging 0 right
deltaX
0 --> 3
^
| Reduce Y by delta X
1 --> 2 --> Expand by deltaY
Since moving down will result in negative delta, it will be handled automatically.
This is the basic concept,
You can do the same to rest of the balls as well by simply replacing the indexes in above snippet.
I have a problema animating a rectangle in Zxing, I am building a qr reader with this library, and my client asked me for an animated laser. No problem for this, laser animation works fine.
But he asked me a texture over laser :/ and I can't animate this.
Sincerely, code for laser is a copy past.
#Override
public void drawLaser(Canvas canvas) {
// Draw a red "laser scanner" line through the middle to show decoding is active
mLaserPaint.setAlpha(155);
int middle = mFramingRect.height() / 2 + mFramingRect.top;
middle = middle + cntr;
if ((cntr < 300) && (!goingup)) {
canvas.drawRect(mFramingRect.left + 2,
middle - 5,
mFramingRect.right - 1,
middle + 10,
mLaserPaint);
cntr = cntr + 4;
}
if ((cntr >= 300) && (!goingup)) goingup = true;
if ((cntr > -300) && (goingup)) {
canvas.drawRect(mFramingRect.left + 4,
middle - 5,
mFramingRect.right - 1,
middle + 10,
mLaserPaint);
cntr = cntr - 4;
}
if ((cntr <= -300) && (goingup)) goingup = false;
postInvalidateDelayed(ANIMATION_DELAY,
mFramingRect.left - POINT_SIZE,
mFramingRect.top - POINT_SIZE,
mFramingRect.right + POINT_SIZE,
mFramingRect.bottom + POINT_SIZE);
}
And my code for trying animate texture over laser
public void drawTexture (Canvas canvas) {
int a = 200;
int b = 220;
int c = 400;
int d = 440;
Paint paint = new Paint();
paint.setColor(Color.RED);
c = c + 100;
d = d + 100;
canvas.drawRect(a,b,c,d,paint);
invalidate();
}
both methods are called in method onDraw
public void onDraw(Canvas canvas) {
if (this.getFramingRect() != null) {
...
this.drawLaser(canvas);
this.drawTexture(canvas);
}
}
I don't know how to animate my rectangle :(, thanks for your help.
Here is my full code
https://gist.github.com/memoadian/2266fbfe6bdf5a3345f2776bdbe7bf2c
UPDATE
Ok, the animation was like this.
public void drawTexture (Canvas canvas) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.codi_texture);
BitmapShader fillBMPshader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
Paint paint = new Paint();
paint.setShader(fillBMPshader);
int middle = mFramingRect.height() / 2 + mFramingRect.top;
middle = middle + cntr;
if ((cntr < 300) && (!goingup)) {
canvas.drawRect(mFramingRect.left,
mFramingRect.top,
mFramingRect.right,
middle,
paint);
cntr = cntr + 4;
}
if ((cntr >= 300) && (!goingup)) goingup = true;
if ((cntr > -300) && (goingup)) {
canvas.drawRect(mFramingRect.left,
mFramingRect.top,
mFramingRect.right,
middle,
paint);
cntr = cntr - 4;
}
if ((cntr <= -300) && (goingup)) goingup = false;
}
And the result is
I'm use lottie for animation and disable laser and transparent rectangle, maybe this could be your next idea
mScannerView.setLaserColor(mContext.getResources().getColor(R.color.btn_color));
mScannerView.setLaserEnabled(false);
mScannerView.setBorderColor(mContext.getResources().getColor(android.R.color.transparent));
mScannerView.setMaskColor(mContext.getResources().getColor(android.R.color.transparent));
List<BarcodeFormat> enableFormat = new ArrayList<>();
enableFormat.add(BarcodeFormat.QR_CODE);
mScannerView.setFormats(enableFormat);
mScannerView.setAutoFocus(true);
try {
int padding = paddingInDp(100);
lottieView.setPadding(-padding, -padding, -padding, -padding);
lottieView.setAnimation("barcode_scan.json");
lottieView.setSpeed(0.5f);
lottieView.playAnimation();
lottieView.loop(true);
} catch (Exception e){
e.printStackTrace();
}
I have a class that creates a Star using Path this code is as follows:
public Star(int x, int y, int size) {
this.x = x;
this.y = y;
this.size = size;
rotateSpeed = 1.5f;
path = new Path();
path.moveTo(x, y-size);
for(int i = 1; i < 11; i++) {
double radius;
if(i % 2 == 0)
radius = size;
else
radius = size/2.2;
double angleRad = Math.toRadians(360/10) * i;
double cosY = y - (Math.cos(angleRad) * radius);
double tanX = x + (Math.sin(angleRad) * radius);
path.lineTo((int)tanX, (int)cosY);
path.moveTo((int)tanX, (int)cosY);
}
path.setLastPoint(x, y-size);
}
The problem is that it only draws an outline of it and won't actually fill it despite the fact that I explicitly state paint.setStyle(Paint.Style.FILL);
My drawing code is:
public void drawObject(Canvas canvas, boolean antialias) {
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
p.setAntiAlias(antialias);
p.setColor(color);
canvas.drawPath(path, p);
//Outline
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(5);
p.setColor(Color.BLACK);
canvas.drawPath(path, p);
}
I don't understand what I'm doing wrong. Why won't it fill?
Probably "color" in p.setColor(color); is transparent. Make sure the color is e.g. 0xffRRGGBB
Also path.moveTo((int)tanX, (int)cosY); is not needed. The preceeding lineTo() does the job.
whats the best way of drawing the below image on canvas in such a way that the tail should move independent of its body
Below is the code which I tried drawing it:
RectF rectf = new RectF(left, top, right, bottom);
path.arcTo(rectf, startAngle, sweepAngle);
path.lineTo(linex, liney);
Explanation :
I am drawing a circle inside a rectangle. I am not drawing a complete circle, I am starting from say 0 degree to 350 degree and then I am drawing a line from where the arc stops.
The image is coming perfect. The problem is with rotation, when I try to rotate the entire canvas rotates but I want only the tail to rotate.
For rotating I am using below code:
//This line is being called inside onDraw()
canvas.rotate(rotation, (left+right)/2, (top+bottom)/2);
I am basically trying to rotate the bubble onTouch event
View.OnTouchListener touch = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent e) {
if(e.getAction() == MotionEvent.ACTION_MOVE){
float x = e.getX();
float y = e.getY();
updateRotation(x,y);
invalidate();
}
return true;
}
};
public void updateRotation(float x, float y) {
double r = Math.atan2(x - (getWidth()/2), (getHeight()/2) - y);
rotation = (int) Math.toDegrees(r);
}
My complete onDraw() method is below:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
path.reset();
left = 50;
top = 50;
right = getWidth()- 50;
bottom = getHeight()-50;
startAngle = 100;
sweepAngle = 335;
linex = (left + right)/2;
liney = bottom + 50;
canvas.rotate(rotation, (left+right)/2, (top+bottom)/2);
RectF rectf = new RectF(left, top, right, bottom);
path.arcTo(rectf, startAngle, sweepAngle);
path.lineTo(linex, liney);
canvas.clipPath(path);
canvas.drawPath(path, paint);
canvas.restore();
}
Image Before rotation:
Image after rotation:
I spent way to much time on this, but it will come in useful for my app.
My idea was to set the point and draw around it, so all you have to do is change linex and liney
How else did you get on?
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
paint.setColor(mContext.getResources().getColor(R.color.black));
Path path = new Path();
int left = 50;
int top = 50;
int right = getWidth()- 50;
int bottom = getHeight()-50;
int linex = getWidth()/2;
int liney = getHeight();
int openSize = 20;
RectF rectf = new RectF(left, top, right, bottom);
int angle = (int) angle(new Point(getWidth()/2, getHeight()/2), new Point(linex,liney));
int startAngle = angle + (openSize/2);
int sweepAngle = 360 - openSize;
path.arcTo(rectf, startAngle, sweepAngle);
path.lineTo(linex, liney);
path.close();
canvas.drawPath(path, paint);
}
private double angle(Point point1, Point point2) {
double deltaX = (point1.x - point2.x);
double deltaY = (point1.y - point2.y);
double angle = Math.toDegrees(Math.atan2(deltaY, deltaX));
if (angle < 0) {
angle += 360;
}
if (angle > 360) {
angle -= 360;
}
// Compensate for 0
angle -= 180;
return angle;
}
I have window with 3 circles, they are rotating simultaneously. Everything is good until a Add text to the circles, then the rotation starts to lagging.
How can i optimize drawing on canvas ?
This is my code:
#Override
protected void onDraw(final Canvas canvas) {
if (mPaint == null) {
mPaint = new Paint();
mPaint.setTextSize(20f);
}
drawUpperCircle(canvas);
drawBottomCircle(canvas);
drawMainCircle(canvas);
try {
Thread.sleep(1, 1);
invalidate();
mRotation += 0.9;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
super.onDraw(canvas);
}
private void drawUpperCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mUpperCircleCentr);
mPaint.setColor(Color.CYAN);
canvas.drawCircle(0, mUpperCircleCentr, mUpperCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mUpperCircleCentr);
canvas.drawLine(0, mUpperCircleCentr, mUpperCirclRadius, mUpperCircleCentr, mPaint);
// canvas.drawText("my text" + String.valueOf(i), mUpperCirclRadius * 2 / 3, mUpperCircleCentr - 4, mPaint);
}
canvas.restore();
}
private void drawBottomCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mBottomCircleCentr);
mPaint.setColor(Color.RED);
canvas.drawCircle(0, mBottomCircleCentr, mBottomCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mBottomCircleCentr);
canvas.drawLine(0, mBottomCircleCentr, mBottomCirclRadius, mBottomCircleCentr, mPaint);
// canvas.drawText("my text" + String.valueOf(i), mBottomCirclRadius * 2 / 3, mBottomCircleCentr - 4, mPaint);
}
canvas.restore();
}
private void drawMainCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mMainCircleCentr);
mPaint.setColor(Color.argb(100, 100, 100, 100));
canvas.drawCircle(0, mMainCircleCentr, mMainCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mMainCircleCentr);
canvas.drawLine(0, mMainCircleCentr, mMainCirclRadius, mMainCircleCentr, mPaint);
canvas.drawText("my text" + String.valueOf(i), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
}
canvas.restore();
}
EDIT
To improve performance and remove drawing from UI thread I have Used Double Buffering With SurfaceView and implement #Morgans optimizations. That is how it realized.
DrawView.java
public class DrawView extends SurfaceView implements SurfaceHolder.Callback {
...............................................................
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float currentX = event.getX();
float currentY = event.getY();
float deltaX, deltaY;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// Modify rotational angles according to movement
deltaX = currentX - previousX;
deltaY = currentY - previousY;
mDrawThread.mRotation += deltaY * 180 / getHeight();
}
// Save current x, y
previousX = currentX;
previousY = currentY;
return true; // Event handled
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mDrawThread = new DrawThread(getHolder(), this);
mDrawThread.setRunning(true);
mDrawThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
boolean retry = true;
mDrawThread.setRunning(false);
while (retry) {
try {
mDrawThread.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
And the main work is done in the DrawThread.java
public class DrawThread extends Thread {
private ArrayList<Path> mMainCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mUpperCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mCenterCirclePaths = new ArrayList<Path>(SEG_COUNT);
private ArrayList<Path> mBottomCirclePaths = new ArrayList<Path>(SEG_COUNT);
private boolean mRun = false;
private SurfaceHolder mSurfaceHolder;
private DrawView mDrawView;
private Paint mPaint;
private CirclesModel mCirclesModel;
public float mRotation = 0;
public DrawThread(SurfaceHolder surfaceHolder, DrawView drawView) {
mSurfaceHolder = surfaceHolder;
mDrawView = drawView;
mCirclesModel = new CirclesModel(mDrawView.getHeight());
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(18f);
initPaths();
}
public void setRunning(boolean b) {
mRun = b;
}
#Override
public void run() {
while (mRun) {
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
drawMainCircle(canvas);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
mCirclesModel.mSmallCirclesRadius, mPaint);
drawCenterCircle(canvas);
drawUpperCircle(canvas);
drawBottomCircle(canvas);
//mRotation += 0.5f;
}
} finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
private void drawMainCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y]);
float rot = mRotation;
mPaint.setColor(Color.LTGRAY/* argb(100, 255, 255, 255) */);
canvas.drawCircle(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
mCirclesModel.mBigCirclesRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y]);
rot += SEG_IN_GRAD;
float absRot = Math.abs(rot % 360);
if (absRot > mCirclesModel.mMainCircleSegment[0] && absRot < mCirclesModel.mMainCircleSegment[1]) {
continue;
}
canvas.drawLine(mCirclesModel.mMainCircleCentr[CirclesModel.X], mCirclesModel.mMainCircleCentr[CirclesModel.Y],
mCirclesModel.mBigCirclesRadius, mCirclesModel.mMainCircleCentr[CirclesModel.Y], mPaint);
canvas.drawPath(mMainCirclePaths.get(i), mPaint);
// canvas.drawText("my text" + String.valueOf(i),
// mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
}
canvas.restore();
}
.................................................................
}
Double Buffering is implemented in the two lines of code
canvas = mSurfaceHolder.lockCanvas(null); here I take from surface view canvas in which i will draw next frame.
mSurfaceHolder.unlockCanvasAndPost(canvas); here I am overlaping current image on SurfaceView with new canwas, this is the moment where image changes. Be aware if you have transparent elements then the previous image will be still visible, Image is not replaced, but overlaped.
Below is a version of your code that contains a few optimizations.
First, I try not to draw the lines and text that currently offscreen. I do this by tracking the rotation angle, and skipping the drawing for net rotations between 90 and 270 degrees. On my 2.3 simulator this improved performance overall by 25%.
Second, I "cache" the strings I am going to draw by initializing an array (ArrayList<Path>) with one Path for each string I need to draw. I do this in the same place you were one-time initializing the mPaint. Then I draw the strings using canvas.drawPath(...). On my 2.3 simulator this improved performance by another 33%. The net effect was to about double the rotation speed. Also, it stopped the text from "wiggling around".
A few other notes:
I removed the Thread.sleep(1,1). Not sure exactly what you were trying to accomplish with that.
I changed rotation delta to 1.0 from 0.9. Not sure why you were using 0.9. Note that if you change to back, my "log time it takes to rotate 10 degrees" will not quite work since mRotation % 10 may seldom be 0.
On a 4.1 simulator, the rotation was generally much faster (about 4x) than on my 2.3 simulator. And a 4.1 device was faster yet.
public class AnimView extends View {
Paint mPaint;
ArrayList<Path> mTextPaths;
float mRotation = 0f;
float mUpperCircleCentr = 150f;
float mUpperCirclRadius = 150f;
private static final int SEG_COUNT = 60;
private static final float SEG_IN_GRAD = 360.0f / SEG_COUNT;
float mBottomCircleCentr = 450f;
float mBottomCirclRadius = 150f;
float mMainCircleCentr = 300f;
float mMainCirclRadius = 300f;
long mLastMillis = 0L;
// ctors removed
#Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (mPaint == null) {
mPaint = new Paint();
mPaint.setTextSize(20f);
// init text paths
mTextPaths = new ArrayList<Path>(SEG_COUNT);
for (int i = 0; i < SEG_COUNT; i++) {
Path path = new Path();
String s = "my text" + String.valueOf(i);
mPaint.getTextPath(s, 0, s.length(), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, path);
path.close(); // not required on 2.2/2.3 devices
mTextPaths.add(path);
}
}
if (mLastMillis == 0L) {
mLastMillis = System.currentTimeMillis();
}
drawUpperCircle(canvas);
drawBottomCircle(canvas);
drawMainCircle(canvas);
invalidate();
if (((int) mRotation) % 10 == 0) {
long millis = System.currentTimeMillis();
Log.w("AnimateCanvas", "OnDraw called with mRotation == " + mRotation);
Log.w("AnimateCanvas", "Last 10 degrees took millis: " + (millis - mLastMillis));
mLastMillis = millis;
}
}
private void drawUpperCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mUpperCircleCentr);
float rot = mRotation;
mPaint.setColor(Color.CYAN);
canvas.drawCircle(0, mUpperCircleCentr, mUpperCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mUpperCircleCentr);
rot += SEG_IN_GRAD;
if (rot % 360 > 90 && rot % 360 < 270)
continue;
canvas.drawLine(0, mUpperCircleCentr, mUpperCirclRadius, mUpperCircleCentr, mPaint);
}
canvas.restore();
}
private void drawBottomCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mBottomCircleCentr);
float rot = mRotation;
mPaint.setColor(Color.RED);
canvas.drawCircle(0, mBottomCircleCentr, mBottomCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mBottomCircleCentr);
rot += SEG_IN_GRAD;
if (rot % 360 > 90 && rot % 360 < 270)
continue;
canvas.drawLine(0, mBottomCircleCentr, mBottomCirclRadius, mBottomCircleCentr, mPaint);
}
canvas.restore();
}
private void drawMainCircle(Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, 0, mMainCircleCentr);
float rot = mRotation;
mPaint.setColor(Color.argb(100, 100, 100, 100));
canvas.drawCircle(0, mMainCircleCentr, mMainCirclRadius, mPaint);
mPaint.setColor(Color.BLACK);
for (int i = 0; i < SEG_COUNT; i++) {
canvas.rotate(SEG_IN_GRAD, 0, mMainCircleCentr);
rot += SEG_IN_GRAD;
if (rot % 360 > 90 && rot % 360 < 270)
continue;
canvas.drawLine(0, mMainCircleCentr, mMainCirclRadius, mMainCircleCentr, mPaint);
canvas.drawPath(mTextPaths.get(i), mPaint);
// canvas.drawText("my text" + String.valueOf(i), mMainCirclRadius * 2 / 3, mMainCircleCentr - 4, mPaint);
}
canvas.restore();
}
}
Your code is pretty nice and simple. You can optimize it by using less loops for instance, drawing things all together or combining variables, but this would quickly get messy.
I would recommend you to keep your drawing code more or less equal. You actually don't do the worst thing : instanciating objects, and it's clear and easy to maintain.
But you could maybe try to use a double buffer : drawing in a buffer in ram and flipping the buffer one shot on the screen. This generally performs quite well to get a constant animation pace. Use locking and unlocking of your canvas : Double buffering in Java on Android with canvas and surfaceview