4

I currently have this snippet generating the ticks around the outside of and android wear watchface

float innerMainTickRadius = mCenterX - 35;
            for(int tickIndex = 0; tickIndex < 12; tickIndex++) {
                float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
                float innerX = (float) Math.sin(tickRot) * innerMainTickRadius;
                float innerY = (float) -Math.cos(tickRot) * innerMainTickRadius;
                float outerX = (float) Math.sin(tickRot) * mCenterX;
                float outerY = (float) -Math.cos(tickRot) * mCenterX;
                canvas.drawLine(mCenterX + innerX, mCenterY + innerY, mCenterX + outerX, mCenterY + outerY, mTickPaint);
            }

Which generates the ticks well on a round watchface but on a square it turns out like this: Round ticks on Square but I'd like them to not be circular, but instead fit the shape a bit more suitably, e.g: Square watchface

Is there a standard way to do this? I'm guessing I can't use trig again...

nuggetbram
  • 345
  • 1
  • 12

2 Answers2

3

Of course you use geometry and trig. For example any line you put on the clock face you want to point to the center so one part will be the given (x,y) and the other will be arctan2(cy-y,cx-x) giving you the angle from the point you have towards the center (cx,cy) then simply draw the line in the direction of the center of a given length r, by drawing the line from x,y to cos(angle) * r, sin(angle) * r.

However, given your sample image you might want to draw the line from x,y to x+r,y then rotate the canvas by angle so that you can draw those numbers tweaked like that. Be sure to do canvas.save() before tweaking the canvas' matrix and canvas.restore() after the tweak.

This leaves the math of whatever shape you want to draw your ticks from and the positions thereto. You can do this within a Path. So define the path for a rounded rectangle and then use the PathMeasure class to get the getPosTan() and then ignore the tangent and just use the position it gives you to find your position around a rounded rectangle. That or simply calculate those positions as the positions through either a line segment or a bezier section depending on the decided shape.

For example:

static final int TICKS = 12;
static final float TICKLENGTH = 20;

In the draw routine,

    float left = cx - 50;
    float top = cy - 50;
    float right = cx + 50;
    float bottom = cy + 50;
    float ry = 20;
    float rx = 20;
    float width = right-left;
    float height = bottom-top;
    Path path = new Path();
    path.moveTo(right, top + ry);
    path.rQuadTo(0, -ry, -rx, -ry);
    path.rLineTo(-(width - (2 * rx)), 0);
    path.rQuadTo(-rx, 0, -rx, ry);
    path.rLineTo(0, (height - (2 * ry)));
    path.rQuadTo(0, ry, rx, ry);
    path.rLineTo((width - (2 * rx)), 0);
    path.rQuadTo(rx, 0, rx, -ry);
    path.rLineTo(0, -(height - (2 * ry)));
    path.close();

    PathMeasure pathMeasure = new PathMeasure();
    pathMeasure.setPath(path,true);
    float length = pathMeasure.getLength();
    float[] pos = new float[2];
    float r = TICKLENGTH;
    for (int i = 0; i < TICKS; i++) {
        pathMeasure.getPosTan(i * (length/TICKS),pos,null);
        double angle = Math.atan2(cy - pos[1], cx - pos[0]); //yes, y then x.
        double cos = Math.cos(angle);
        double sin = Math.sin(angle);
        canvas.drawLine(pos[0], pos[1], (float)(pos[0] + cos * r), (float)(pos[1] + sin * r), paint);
    }

Admittedly it looks like:

clock picture

So it would take a lot more work to get it looking like your image. But, it's totally doable. The path measure trick thing will work for any shape. I avoided using path.addRoundRect because of the Lollipop+ restriction. You can see my answer to that question here. And the other answers which are plenty fine to how to draw a rounded rectangle-esque shape. You can, if you would like to write an envelope function simply scale your current picture to the envelope of the rectangle according to the factor t, as it goes around the clock.

Community
  • 1
  • 1
Tatarize
  • 10,238
  • 4
  • 58
  • 64
  • If you have that mockup in vector, you could just use vector graphics and blow that icon up that big during the draw. Draw what you want in an SVG convert it to android vector graphics and just draw the drawable on to the canvas, it would be quite small and non-ambiguous. – Tatarize Apr 17 '16 at 18:12
  • That's actually not a bad idea. I used your first answer, and it worked pretty well in the end with a bit of tweaking – nuggetbram Apr 18 '16 at 12:34
1

The angle is a function of the position now. I'm not immediately seeing the trick for getting a closed form in this case. But in the most general case, you could end up just storing the position of each tickmark, then you're just drawing the line that goes through that point and the center. so the angle at second i is just

theta(i)=arctan(y_pos(i) / x_pos(i))

assuming the center has coordinates (0,0). In this case, you only need to store the positions for 8 consecutive ticks because the face is periodic every 90 degrees and symmetric about the diagonals as well.

CasualScience
  • 661
  • 1
  • 8
  • 19