Android Developer

Los Angeles Freelance Developer

Android Developer header image 2

ImageViews and Stars and Other Unique Shapes

December 30th, 2012 · 5 Comments · Android, Drawables

Romain Guy is doing a series on Android performance and drawing. In his first post, he demonstrates an ImageView with rounded corners by using a custom Drawable. I wanted to learn more about drawing and paints and shaders so I did some of my own experiments drawing star shapes using pictures of kittens. (Unicorns will be next…) You can grab the full sources here.

There are a couple of important objects used in drawing:

Canvas
The Canvas object is passed to us and this is where we make our drawing commands. You can think of this as like the canvas to an oil painting.

Paint
The Paint object is kind of like it sounds. It supplies the source color for the thing drawn. It may be a single color or in the case of bitmaps we set a BitmapShader which Paint will use as its source.

Path/Rect/etc….
These objects define the shape of what is drawn to the canvas. In my stars sample, I used a Path object to draw the 5 points of a star connected by lines.  And if you looked as Romain Guy’s example of rounded corners (included in my source code as RoundedCornersBitmapDrawable) he uses a Rect object.

The Drawing
To draw you just need 3 things: a canvas, a shape, a color. It’s actually quite simple. In StarsBitmapDrawable it’s this:

@Override
public void draw(Canvas canvas) {
   canvas.drawPath(path, paint);
}

Note: When you make a custom drawable, do override the methods getIntrinsicWidth and getIntrinsicHeight. The defaults return -1 and is generally not what you want.

A Little Trig
I’m not going to go too heavily into the math used to draw a star. The basic idea is to find the coordinates of each point of the star and connect them with lines. The Path object will fill the space. The image below shows how with just a little trigonometry and algebra I was able to come up with the points.

Which gives us code like this:

// truncated code...full code in sources
public class StarBitmapDrawable extends Drawable {
 
public StarBitmapDrawable(Bitmap bitmap) {
    super();
    this.bitmap = bitmap;
    paint = new Paint();
    paint.setAntiAlias(true);
    shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    paint.setShader(shader);
    path = new Path();
//        paint.setStyle(Paint.Style.STROKE);
}
 
@Override
protected void onBoundsChange(Rect bounds) {
    super.onBoundsChange(bounds);
 
    int minDim = Math.min(bounds.width() - paddingLeft - paddingRight,
    bounds.height() - paddingTop - paddingBottom);
 
// b = |
// a = _
// hyp = \
 
// bigHypot = height / cos(18)
    double bigHypot = (minDim / Math.cos(Math.toRadians(STAR_ANGLE_HALF)));
    double bigB = minDim;
    double bigA = Math.tan(Math.toRadians(18)) * bigB;
 
// lengths of the little triangles.
// littleC = littleC + littleC + littleA + littleA
// cos(72)*C = A
    double littleHypot = bigHypot / (2 + Math.cos(Math.toRadians(STAR_OPP_ANGLE)) + Math.cos(Math.toRadians(STAR_OPP_ANGLE)));
    double littleA = Math.cos(Math.toRadians(STAR_OPP_ANGLE)) * littleHypot;
    double littleB = Math.sin(Math.toRadians(STAR_OPP_ANGLE)) * littleHypot;
 
    int topXPoint = (bounds.width() - paddingLeft - paddingRight)/2;
    int topYPoint = paddingTop;
 
// start at the top point
    path.moveTo(topXPoint, topYPoint);
 
// top to bottom right point
    path.lineTo((int)(topXPoint + bigA), (int)(topYPoint + bigB));
 
// bottom right to middle left point
    path.lineTo((int)(topXPoint - littleA - littleB), (int)(topYPoint + littleB));
 
// middle left to middle right point
    path.lineTo((int)(topXPoint + littleA + littleB), (int)(topYPoint + littleB));
 
//        // middle right to bottom left point
    path.lineTo((int)(topXPoint - bigA), (int)(topYPoint + bigB));
 
//        // bottom left to top point
    path.lineTo(topXPoint, topYPoint);
    path.close();
}
 
@Override
public void draw(Canvas canvas) {
    canvas.drawPath(path, paint);
}
// truncated....more code in sources

Tags: ···

5 responses so far ↓

  • 1 Jack Le // Jan 4, 2013 at 12:43 am

    good job.
    love to reader your blog.
    how do you think about a pattern to draw any shape? I mean that how can we change the shape in a flexible way.
    thanks

  • 2 musselwhizzle // Jan 4, 2013 at 2:47 am

    Jack, if the shape is difficult to draw programmatically you could use a mask to create the shape you need. Check out Romain Guy’s second post of this subject: http://www.curious-creature.org/2012/12/13/android-recipe-2-fun-with-shaders/

  • 3 Jack Le // Jan 5, 2013 at 9:21 pm

    Thanks for your reply. This link is vert usefull.
    But I have another concern. Is there a design pattern for drawing shape. For example, today, I have draw image in star shape and tomorrow, my customer need to draw in cycle shape, ect. So How we can change it easily?

    p/s I really love your seria post about android architecture. I cannot stop thinking about it. Thanks

  • 4 musselwhizzle // Jan 6, 2013 at 8:50 pm

    Hi Jack, What you are asking about can be solved with a Factory Pattern. Something like:
    ImageDrawableFactory.createDrawable(Object something) {
    if (something == myCase) return new StarDrawable(bitmap);
    else if (something == myOtherCase) return new CycleShapeDrawable(bitmap)
    else return new ImageBitmapDrawable(bitmap);
    }
    and you get all of your drawables through the factory. Also read this.

  • 5 Jack Le // Jan 7, 2013 at 9:09 pm

    Thanks for your suggestion.
    You help me alot.

Leave a Comment