Android | Draw Happy Face!

draw-happy-face Here is an interesting example of drawing using Android canvas. We will draw a happy face using native android Canvas.

 

 

 

 

 

 

 

 


Environment & Tools

  • Windows 7
  • Eclipse ADT
  • Nexus 5

( 1 ) Create new Android Project

  • Application Name: DrawFace
  • Project Name: android-draw-happy-face
  • Package Name: com.hmkcode.drawing

( 2 ) Happy Face Custom View

  • We will create a new custom view that can be plugged directly into our layout XML file to add a new happy face.
  • The view will have one custom attribute radius that specifies the radius of the face.

At the end we will be able to add happy face using FaceView to our application as following:

FaceView custom view XML tag


<com.hmkcode.drawing.FaceView
    android:id="@+id/fvHappy"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    faceview:radius="50dp"
    android:layout_margin="5dp"
    android:layout_gravity="center"
    android:background="#80000000"/>

( 3 ) Create a new FaceView Class

  • To create a custom view you need to extend View class.
  • We will create a new FaceView class that extends View class.
  • The custom view has one custom attribute radius which will be defined in attrs.xml under values folder.
  • FaceView constructor gets the value of radius attribute & initiates Face class.
  • Face class is a class where we write our drawing logic.
  • Override onDraw() method to call Face.draw() method.
  • Override onMeasure(), this method adjust the size of the view "width & hieght". This method works based on the pass values to layoutwidth & layouthieght.

package com.hmkcode.drawing;


import com.hmkcode.drawing.shapes.Face;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class FaceView extends View {  
     private float radius;
     Face face;
    public FaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.FaceView,
                0, 0
        );
        
        try {
            radius = a.getDimension(R.styleable.FaceView_radius, 20.0f);
        } finally {
            a.recycle();
        }
        
        face = new Face(radius);
    }
    
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        face.draw(canvas);
        
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        int desiredWidth = (int) radius*2+(int) Math.ceil((radius/1.70));
        int desiredHeight = (int) radius*2+(int)Math.ceil((radius/1.70));

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        //Measure Width
        if (widthMode == MeasureSpec.EXACTLY) {
            //Must be this size
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //Can't be bigger than...
            width = Math.min(desiredWidth, widthSize);
            Log.d("Width AT_MOST", "width: "+width);
        } else {
            //Be whatever you want
            width = desiredWidth;
            Log.d("Width ELSE", "width: "+width);

        }

        //Measure Height
        if (heightMode == MeasureSpec.EXACTLY) {
            //Must be this size
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //Can't be bigger than...
            height = Math.min(desiredHeight, heightSize);
        } else {
            //Be whatever you want
            height = desiredHeight;
        }

        //MUST CALL THIS
        setMeasuredDimension(width, height);
    }
        public float getRadius() {
            return radius;
        }

        public void setRadius(float radius) {
            this.radius = radius;
        }
    
}
  • Create a new XML file attrs.xml under values folder with the following content.

<resources>
    <declare-styleable name="FaceView">
        <attr name="radius" format="dimension"/>
    </declare-styleable>
</resources>

( 4 ) Draw Happy Face

  • Actual drawing will take place a sperate class 'Face.java
  • Here we need to do some math work to draw the face.
  • We need to define locations of eyes & mouth based on face size.

package com.hmkcode.drawing.shapes;

import android.graphics.Canvas;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;

public class Face {

    Paint facePaint;
    Paint mePaint;
    
    
    float radius; 
    float adjust;
    
    float mouthLeftX, mouthRightX, mouthTopY, mouthBottomY;
    RectF mouthRectF; 
    Path mouthPath;
    
    RectF eyeLeftRectF, eyeRightRectF;
    float eyeLeftX, eyeRightx, eyeTopY, eyeBottomY;
    
    
     public Face(float radius){
        this.radius= radius;
        
        facePaint = new Paint();
        facePaint.setColor(0xfffed325); // yellow 
        facePaint.setDither(true);                  
        facePaint.setStrokeJoin(Paint.Join.ROUND);   
        facePaint.setStrokeCap(Paint.Cap.ROUND);      
        facePaint.setPathEffect(new CornerPathEffect(10) );   
        facePaint.setAntiAlias(true);  
        facePaint.setShadowLayer(4, 2, 2, 0x80000000);
        
        mePaint = new Paint();
        mePaint.setColor(0xff2a2a2a);
        mePaint.setDither(true);       
        mePaint.setStyle(Paint.Style.STROKE); 
        mePaint.setStrokeJoin(Paint.Join.ROUND); 
        mePaint.setStrokeCap(Paint.Cap.ROUND);     
        mePaint.setPathEffect(new CornerPathEffect(10) );   
        mePaint.setAntiAlias(true); 
        mePaint.setStrokeWidth(radius / 14.0f);
        
        
        
        adjust = radius / 3.2f;

        
        // Left Eye
        eyeLeftX = radius-(radius*0.43f);
        eyeRightx = eyeLeftX+ (radius*0.3f);
        eyeTopY = radius-(radius*0.5f);
        eyeBottomY = eyeTopY + (radius*0.4f);
        
        eyeLeftRectF = new RectF(eyeLeftX+adjust,eyeTopY+adjust,eyeRightx+adjust,eyeBottomY+adjust);

        // Right Eye
        eyeLeftX = eyeRightx + (radius*0.3f);
        eyeRightx = eyeLeftX + (radius*0.3f);

        eyeRightRectF = new RectF(eyeLeftX+adjust,eyeTopY+adjust,eyeRightx+adjust,eyeBottomY+adjust);
        
        
        // Smiley Mouth
        mouthLeftX = radius-(radius/2.0f);
        mouthRightX = mouthLeftX+ radius;
        mouthTopY = radius - (radius*0.2f);
        mouthBottomY = mouthTopY + (radius*0.5f);

        mouthRectF = new RectF(mouthLeftX+adjust,mouthTopY+adjust,mouthRightX+adjust,mouthBottomY+adjust);
        mouthPath = new Path();
        
        mouthPath.arcTo(mouthRectF, 30, 120, true);
    }
    
    public void draw(Canvas canvas) {
        
        // 1. draw face
        canvas.drawCircle(radius+adjust, radius+adjust, radius, facePaint);
     
        // 2. draw mouth
        mePaint.setStyle(Paint.Style.STROKE); 

        canvas.drawPath(mouthPath, mePaint);
        
        // 3. draw eyes
        mePaint.setStyle(Paint.Style.FILL); 
        canvas.drawArc(eyeLeftRectF, 0, 360, true, mePaint);
        canvas.drawArc(eyeRightRectF, 0, 360, true, mePaint);
 
    }
}

( 5 ) Activity Layout & Class

  • In activity_main.xml is a simple layout with one FaceView view.

<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.hmkcode.faces.MainActivity"
    xmlns:faceview="http://schemas.android.com/apk/res/com.hmkcode.drawing"
    android:background="@drawable/woodgraybg"
    android:gravity="center"
     >

   <com.hmkcode.drawing.FaceView
            android:id="@+id/fvHappy"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            faceview:radius="50dp"
            android:layout_margin="5dp"
            android:layout_gravity="center"
            android:background="#80000000"
            
         />

</LinearLayout>
  • MainActivity.java is as following.

package com.hmkcode.drawing;


import android.app.Activity;

public class MainActivity extends Activity  {

    protected void onCreate(android.os.Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);      
        setContentView(R.layout.activity_main);
    }
    
}

Source Code @ GitHub