Creating simple smiley face in Android and IOS.


In this article i am going to show you how to draw a simple smiley face in Android using Canvas and in IOS using UIBezierPath.

Android

After following this article you should be able to create something like

desk desk

Step 1 - Planing the smiley

  1. Draw a circle with maximum radius, it will act as face.
  2. Draw two small circles inside the face circle, it will act as eyes
  3. Draw an arc under the eyes, it will act as the mouth.
  4. Make sure, it works even if the orientation changes (both landscape and portrait modes.)

Step 2 - Create Required files.

  1. Create Main Activity (Generate both xml and java)

  2. Create FaceView class and extend from View.

Step 3 - Initializing the FaceView.

  1. Create a FaceView constructor and initialize Paint object.

  2. Setup default values like color, stroke values, radius

Step 4 - Override onDraw Function.

  1. Set color property to paint object
  2. Set stroke width to the paint object
  3. Set drawing style as stroke
mPaint.setColor(Color.parseColor(COLOR_HEX));
mPaint.setStrokeWidth(strokeWidth);
mPaint.setStyle(Paint.Style.STROKE);

Step 5 - Draw Face circle.

  1. Get the total width, height taken by our FaceView using getMeasuredWidth and getMeasuredHeight function.
  2. DrawCircle function requires (x, y) points , radius and a paint object.
  3. Calculate the center x position by getMeasuredWidth / 2
  4. Calculate the center y position by getMeasuredHeight / 2
  5. Calculate the radius from the minimum of y position or x position to support both landscape and portrait modes.
  6. Call canvas.drawCircle function with the x position, y position, radius and paint object.
xPosition = getMeasuredWidth() / 2;
yPosition = getMeasuredHeight() / 2;
radius = xPosition < yPosition ? xPosition : yPosition ;
radius -= defaultMargin;
canvas.drawCircle(xPosition, yPosition, radius, mPaint);

Step 5 - Draw eyes.

  • Find the eyeYposition. eyeYPosition will remail same for both left and right. The eyeYposition should be a little more than the calculated yPosition of the face. The Face’s yPosition is half the height, but eyes should be placed a little above. so i use the following formula to calculate the eyeYPosition
eyeYPosition = (float) (yPosition / 1.2);
  • Find the leftEyeXPosition. xPosition is half the width, so the left eye position should take half the xPosition. i use the following formula to calculate leftEyeXPosition to make sure it works even the orientation changes.
leftEyeXPosition = xPosition < yPosition ? xPosition / 2 : (float) (xPosition / 1.3);
  • Find the rightEyeXPosition. xPosition is half the width, so the right eye position should take little more than the xPosition. i use the following formula to calculate rightEyeXPosition to make sure it works even the orientation changes.
rightEyeXPosition = xPosition < yPosition ? xPosition + xPosition / 2 : xPosition + xPosition / 4;
  • We have calculated the eye positions, now we can draw left, right eyes using drawCircle function.
canvas.drawCircle(leftEyeXPosition, eyeYPosition, eyeRadius, mPaint);
canvas.drawCircle(rightEyeXPosition, eyeYPosition, eyeRadius, mPaint);

Step 5 - Draw Mouth.

Drawing Mouth is little tricky.

1 Mark the boundaries using RectF

  • Find the top left - (leftEyeXPosition, yPosition + some value)
  • Find the bottom right - (rightEyeXPosition, yPosition + some value)
    RectF oval = new RectF(leftEyeXPosition, yPosition + yPosition / 8, rightEyeXPosition, (float) (yPosition + yPosition / 2.5)); // left top right bottom
    

2 Draw an arc within the boundaries.

  • For happy face start from 0 degree and draw another 180 degree (sweep angle).
  canvas.drawArc(oval, 10, 150, false, mPaint);
  
  • For sad face start from 180 degree and draw another 180 degree (sweep angle).
  canvas.drawArc(oval, 200, 140, false, mPaint);
  

The final code of FaceView.

public class FaceView extends View {

    private static final String COLOR_HEX = "#0000FF"; // RRGGBB - BLUE
    private final Paint mPaint;
    private float xPosition;
    private float yPosition;
    private float radius;
    private float strokeWidth = 10;
    private float defaultScale = 0.90f;
    private float eyeRadius = 60;
    private float eyeYPosition;
    private float leftEyeXPosition;
    private float rightEyeXPosition;

    public FaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);
        mPaint.setColor(Color.parseColor(COLOR_HEX));
        mPaint.setStrokeWidth(strokeWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPaint(mPaint);

        // drawing outer circle
        // lets setup x cord, y cord, radius
        // x, y position should point to center.
        // radius should be half the width / height
        xPosition = getMeasuredWidth() / 2;
        yPosition = getMeasuredHeight() / 2;
        radius = xPosition < yPosition ? xPosition : yPosition ;
        radius * = defaultScale;
        canvas.drawCircle(xPosition, yPosition, radius, mPaint);

        // Drawing Eyes.

        // lets find eye y position
        eyeYPosition = (float) (yPosition / 1.2);

        // lets find eye x position
        leftEyeXPosition = xPosition < yPosition ? xPosition / 2 : (float) (xPosition / 1.3);

        // lets find right eye x position
        rightEyeXPosition = xPosition < yPosition ? xPosition + xPosition / 2 : xPosition + xPosition / 4;

        // left eye
        canvas.drawCircle(leftEyeXPosition, eyeYPosition, eyeRadius, mPaint);

        // right eye
        canvas.drawCircle(rightEyeXPosition, eyeYPosition, eyeRadius, mPaint);

        // lets draw mouth.
        RectF oval = new RectF(leftEyeXPosition, yPosition + yPosition / 8, rightEyeXPosition, (float) (yPosition + yPosition / 2.5)); // left top right bottom

//        canvas.drawArc(oval, 200, 140, false, mPaint); // sad face.
        canvas.drawArc(oval, 10, 150, false, mPaint); // happy face.
    }
}

Step 3 Add the FaceView in MainActivity’s xml file.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.laminin.happiness.FaceView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

IOS - swift

Now lets see how to do the same in IOS, I am going to use swift. The final output will be something like this.

desk desk

Step 1 - Planing the smiley

  1. Create the required files (Stroyboard, ViewController, FaceView)
  2. Set up the storyboard and view controller to draw FaceView
  3. Draw a circle with maximum radius, it will act as face.
  4. Draw two small circles inside the face circle, it will act as eyes
  5. Draw an arc under the eyes, it will act as the mouth.
  6. Make sure, it works even if the orientation changes (both landscape and portrait modes.)

Step 2 - Creating required files

  1. Open storyboard - navigate to object library, drag a View into the storyboard view controller.
  2. Stretch the view to fit on the view controller.
  3. Select the view and click the Reset to suggested constrains from the resolve auto layout button.
  4. Create FaceView as a subclass of CocoaTouch UIView class.
  5. Select the view from storyboard by shift-ctrl-click on the storyboard.
  6. Click identity inspector and change the class to FaceView

Step 3 - Draw the Face

  • Setting up computed property to get the face center.
var faceCenter: CGPoint {
    // we want the center of the face view, not the super view.
    return convertPoint(center, fromView: superview)
}
  • Setting up computed property for face radius. Face should take as maximum space as possible.
var faceRadius: CGFloat {
    // return the minimum value of width and height.
    return min(bounds.size.width, bounds.size.height) / 2 // converting diameter into radius.
}
  • Create a facePath inside drawRect function using faceCenter, faceRadius.
let facePath = UIBezierPath(arcCenter: faceCenter, radius: faceRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
  • Setting up line width using property observer
    var linewidth: CGFloat = 3 { didSet { setNeedsDisplay() } } // every time some one change the value we need to re draw .
  • Setting up color using property absorber.
    var color : UIColor = UIColor.blueColor() { didSet { setNeedsDisplay() } } // when somebody changes the color we have to redraw.
  • Set the color inside your drawRect function
    color.set()
  • Draw the face. using facePath.stroke()
    facePath.stroke()
  • The drawRect function looks something like
override func drawRect(rect: CGRect) {
    let facePath = UIBezierPath(arcCenter: faceCenter, radius: faceRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
    facePath.lineWidth = lineWidth
    color.set()
    facePath.stroke()
}

Step 4 - Some more changes

1 Redraw when the bounds changes. - To avoid the stretching while changing the orientation.

Click the view, select the Attribute inspector, change the mode to Redraw.

2 Reduce the radius to 90% of the width / height

// setup property obsover to take 90% scale
  var scale : CGFloat = 0.90 { didSet { setNeedsDisplay() } } // redraw when a change occure.

3 Multiply 0.90 with the computed radius value as follows.

var faceRadius: CGFloat {
    // return the minimum value of width and height.
    return min(bounds.size.width, bounds.size.height) / 2  * scale // converting diameter into radius.
}

Step 5 - Drawing eyes.

  • To create eye we need some constants. we put them inside a struct.
private struct Scaling {
    static let FaceRadiusToEyeRadiousRatio: CGFloat = 10
    static let FaceRadiusToEyeOffsetRatio: CGFloat = 3
    static let FaceRadiusToEyeSeparationRatio: CGFloat = 1.5
    static let FaceRadiusToMouthWidthRatio: CGFloat = 1
    static let FaceRadiusToMouthHeightRatio: CGFloat = 3
    static let FaceRadiusToMouthOffsetRatio: CGFloat = 3
}
  • To draw eye.
  let eyeRadius = faceRadius / Scaling.FaceRadiusToEyeRadiousRatio
  let eyeVerticalOffset = faceRadius / Scaling.FaceRadiusToEyeOffsetRatio
  let eyeHorizontalSeparation = faceRadius / Scaling.FaceRadiusToEyeSeparationRatio
  var eyeCenter = faceCenter
  eyeCenter.y -= eyeVerticalOffset
  • Drawing left eye
  eyeCenter.x -= eyeHorizontalSeparation / 2
  let path = UIBezierPath(arcCenter: eyeCenter, radius: eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
  path.lineWidth = lineWidth
  path.stroke()
  • Drawing right eye
  eyeCenter.x += eyeHorizontalSeparation / 2
  let path = UIBezierPath(arcCenter: eyeCenter, radius: eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
  path.lineWidth = lineWidth
  path.stroke()

Step 5 - Drawing Mouth.

  let mouthWidth = faceRadius / Scaling.FaceRadiusToMouthWidthRatio
  let mouthHeight = faceRadius / Scaling.FaceRadiusToMouthHeightRatio
  let mouthVerticalOffset = faceRadius / Scaling.FaceRadiusToMouthOffsetRatio

  let smileHeight = CGFloat(max(min(fractionOfMaximumSmile, 1), -1)) * mouthHeight

  let start = CGPoint(x: faceCenter.x - mouthWidth / 2, y: faceCenter.y + mouthVerticalOffset)
  let end = CGPoint(x: start.x + mouthWidth, y: start.y)
  let cp1 = CGPoint(x: start.x + mouthWidth / 3, y: start.y + smileHeight)
  let cp2 = CGPoint(x: end.x - mouthWidth / 3 , y: cp1.y)

  let path = UIBezierPath()
  path.moveToPoint(start)
  path.addCurveToPoint(end, controlPoint1: cp1, controlPoint2: cp2)
  path.lineWidth = lineWidth
  path.stroke()

Step 5 - Final FaceView class.

The final FaceView class looks something like this.

import UIKit

@IBDesignable
class FaceView: UIView {

    @IBInspectable
    var lineWidth: CGFloat = 3 { didSet { setNeedsDisplay() } }
    @IBInspectable
    var color =  UIColor.blueColor() { didSet { setNeedsDisplay() } }
    @IBInspectable
    var scale: CGFloat = 0.90 { didSet { setNeedsDisplay() } }

    private struct Scaling {
        static let FaceRadiusToEyeRadiousRatio: CGFloat = 10
        static let FaceRadiusToEyeOffsetRatio: CGFloat = 3
        static let FaceRadiusToEyeSeparationRatio: CGFloat = 1.5
        static let FaceRadiusToMouthWidthRatio: CGFloat = 1
        static let FaceRadiusToMouthHeightRatio: CGFloat = 3
        static let FaceRadiusToMouthOffsetRatio: CGFloat = 3
    }

    private enum Eye { case Left, Right }

    var faceCenter: CGPoint {
        return convertPoint(center, fromView: superview)
    }

    var faceRadius: CGFloat {
        return min(bounds.size.width, bounds.size.height) / 2 * scale
    }

    private func bezierPathForEye(whichEye: Eye) -> UIBezierPath {
        let eyeRadius = faceRadius / Scaling.FaceRadiusToEyeRadiousRatio
        let eyeVerticalOffset = faceRadius / Scaling.FaceRadiusToEyeOffsetRatio
        let eyeHorizontalSeparation = faceRadius / Scaling.FaceRadiusToEyeSeparationRatio

        var eyeCenter = faceCenter
        eyeCenter.y -= eyeVerticalOffset
        switch whichEye{
        case .Left: eyeCenter.x -= eyeHorizontalSeparation / 2
        case .Right: eyeCenter.x += eyeHorizontalSeparation / 2
        }

        let path = UIBezierPath(arcCenter: eyeCenter, radius: eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
        path.lineWidth = lineWidth

        return path
    }

    private func bezierPathForSmile(fractionOfMaximumSmile: Double) -> UIBezierPath {
        let mouthWidth = faceRadius / Scaling.FaceRadiusToMouthWidthRatio
        let mouthHeight = faceRadius / Scaling.FaceRadiusToMouthHeightRatio
        let mouthVerticalOffset = faceRadius / Scaling.FaceRadiusToMouthOffsetRatio

        let smileHeight = CGFloat(max(min(fractionOfMaximumSmile, 1), -1)) * mouthHeight

        let start = CGPoint(x: faceCenter.x - mouthWidth / 2, y: faceCenter.y + mouthVerticalOffset)
        let end = CGPoint(x: start.x + mouthWidth, y: start.y)
        let cp1 = CGPoint(x: start.x + mouthWidth / 3, y: start.y + smileHeight)
        let cp2 = CGPoint(x: end.x - mouthWidth / 3 , y: cp1.y)

        let path = UIBezierPath()
        path.moveToPoint(start)
        path.addCurveToPoint(end, controlPoint1: cp1, controlPoint2: cp2)
        path.lineWidth = lineWidth

        return path
    }

    override func drawRect(rect: CGRect) {
        let facePath = UIBezierPath(arcCenter: faceCenter, radius: faceRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
        facePath.lineWidth = lineWidth
        color.set()
        facePath.stroke()

        bezierPathForEye(Eye.Left).stroke()
        bezierPathForEye(Eye.Right).stroke()

        let smileness = 0.75 // range starts form -1 to 1, positive values for happy, negative for sand
        let smilePath = bezierPathForSmile(smileness)
        smilePath.stroke()
    }
}

Get the source code

You can clone this project form github

or clone the project using following command.

  git clone git@github.com:Franklin2412/smiley-part1.git