Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Android: Create rotating Needle

4.94/5 (15 votes)
19 Sep 2014CPOL6 min read 37.6K  
This article include steps to create simple needle in android using View and Graphics. A needle like placed in Speedometers.

Introduction

Sometimes a scenario comes where android developers need to create gauges, compasses, speedometers etc. One component of them is Needle. In this tutorial, we will explain how to draw a simple needle and how to make it rotatable. We will use android graphics and View class.  

Background

Ofcourse you need to know a little bit about android graphics, its components/features/methods and View class. Although I will explain each step so that you don't need to visit android docs / Google now and then while reading this tutorial/article. You should also be familiar with how to create andorid project :)

Using the code

This article includes steps to create needle. In some steps, you may find a better approach or some hardocre values, but that is done delibrately just for the sake of understanding. And ofcourse you can code better than me. Your feedback/improvements are openly welcomed. Lets start:

Step 1:

Create a new android application project with default settings. Create a seperate package with name of your choice. I had named it in my project as com.needle.views. And now create a class in com.needle.views and name it of your choice. I named it as Needle. So, I have class Needle.java.

Step 2: 

When you create an empty  class in android, it is inherited from parent Object class. This is by default. You need to extend it from View.

Java
public class Needle extends View {

Note that, View class is located in package android.view.View. So import this package in your code. Intellisense will do this job for you. If not, press Ctrl+Shift+O.

Java
import android.view.View;

public class Needle extends View {

It will highlight an error on class name forcing to write contructor. It will show you three contructors. Atleast one constructor is required. I have written all three constructors.

Java
public <a id="constructor" name="constructor">Needle</a>(Context context) {
        super(context);
    }

public Needle(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

public Needle(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

Step 3:

When you are writing your own control or working on any graphics in View, the method onDraw(Canvas canvas) is required to be overriden. According to android docs, when you want to render your View, you must override this method. You must implement this method for your drawing. So, override this method. 

  • Right clicke any where in code editor and select Source -> Override/Implement Methods...
  • A popup window will open. Find onDraw(Canvas) method. Select it and click OK.

And it will place code snippet for you.

Java
@Override
protected void <a id="onDraw" name="onDraw"></a>onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

Canvas is a place where you will draw different shapes. It provides plenty of methods to draw different shapes. We will use it path method that will draw path for us. 

Step 4:

Now we will draw lines such that it takes the shape of speedometer needle. We need two main components to do this job. First, a Path which needs points to join lines, second, a Paint which will be used for coloring, style etc. 

 

The Paint class holds the style and color information about how to draw gemoetries, text and bitmaps.

So, lets instantiate two objects of type Path and Paint.

private Paint linePaint;
private Paint needleScrewPaint;
private Path linePath;

Initialize these objects. I will recommend you to write an init() method, and do all initializations in it. And call init() method in constructors.

Java
public Needle(Context context) {
        super(context);
        init(); // Also add call to this method in other overloaded constructors.
    }
Java
private void init() {
        linePaint = new Paint();

        linePath = new Path();

        needleScrewPaint = Paint();  
    }

Step 5:

First, set the color and style information. The object linePaint holds the information of color and style of our needle. 

Java
linePaint = new Paint();
linePaint.setColor(Color.RED); // Set the color
linePaint.setStyle(Paint.Style.STROKE); // We will change it at the end.
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(5.0f); // width of the border
linePaint.setShadowLayer(8.0f, 0.1f, 0.1f, Color.GRAY); // Shadow of the needle

You can change these values and play with them. And I will recommend to change the values and see the results and observe how to adjust these values right according to your requirements. 

Step 6:

Now initialize the linePath. We are going to draw lines which will end up in needle shape.

Java
linePath = new Path(); 
linePath.moveTo(50.0f, 50.0f); // line starting point

The method moveTo(float x, float y) is the starting point of our line. Note that I have hardcoded x,y coordinates just for the sake of simplicity and better understanding. You can adjust these values according to screen size. Just play with them. 

Now we need to define our next point. As soon as we define our next point, we will be able to see line in our device. A method lineTo(float x, float y) will do this job. Note that, here x,y are end points. Line will be drawn from starting point to this point.

Java
linePath.moveTo(50.0f, 50.0f);
linePath.lineTo(130.0f, 40.0f); // next end point
linePath.lineTo(600.0f, 50.0f); // next end point
linePath.lineTo(130.0f, 60.0f); // .....
linePath.lineTo(50.0f, 50.0f); // ...... 
linePath.close();

Note that I have made call to same method 4 times. Every time I call this method, end points are different and path keeps drawing.  you can check this on your device/emulator. Before testing it, you need to do two things. In onDraw(Canvas) method, call canvas.drawPath(Path, Paint) method as onDraw(Canvas) will render it on device screen.

Java
@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // draw path   
        canvas.drawPath(linePath, linePaint);
    }

In your Activity's xml layout, add this control: Note the package name and class name.

XML
<com.needle.views.Needle
        android:id="@+id/myNeedle"
        android:layout_marginTop="10dp"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"/>

When you run it on emulator/device, the output will be like this:

Step 7:

Now we will add circle. This circle will add make it look more elegant. To fill this needle shape, change linePaint.setStyle(Paint.Style.STROKE) to linePaint.setStyle(Paint.Style.FILL_AND_STROKE). The object linePath contains a method addCircle(x, y, radius, direction) which adds the circle in the line path. The method is pretty simple. But add it before close(); method.

Java
.....
linePath.addCircle(130.0f, 50.0f, 20.0f, Path.Direction.CW); 
linePath.close();

it will look like this:

TEST: Try setting linePaint.setStyle(Paint.Style.STROKE) and see the shape of needle.

We are almost done. Just need to modify this circle. Infact we will add new circle with radius less than current and same coordinates/center circle and will apply another paint properties. This is why we had taken paint object lineScrewPaint.

In init() method, initialize the lineScrewPaint and set its values.

Java
needleScrewPaint = new Paint();
needleScrewPaint.setColor(Color.BLACK);
needleScrewPaint.setAntiAlias(true);
needleScrewPaint.setShader(new RadialGradient(130.0f, 50.0f, 10.0f,
                Color.DKGRAY, Color.BLACK, Shader.TileMode.CLAMP));

It is same as linePaint but with different values and new things, Shader and RadialGradient. Shader is used to set gradient colors on your view. Try playing with these values and you will enjoy it. Now in onDraw() method, add following method call just after canvas.drawPath(linePath, linePaint);

Java
canvas.drawCircle(130.0f, 50.0f, 16.0f, needleScrewPaint);

It will look like this:

So, this is our needle and ready to use. It very much simple. No super technique is involve. 

Step 8:

Animation help taken from Stackoverflow.

Now we want to make it rotate. Take Matrix and three variables:

Java
private Matrix matrix;
private int framePerSeconds = 100;
private long animationDuration = 10000;
private long startTime;

Initialize them in init() or constructors. 

Java
public Line(Context context) {
        super(context);

        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
}

Now, change the onDraw(Canvas) method as following:

Java
long elapsedTime = System.currentTimeMillis() - startTime;

matrix.postRotate(1.0f, 130.0f, 50.0f); // rotate 1<sup>o</sup> every second
canvas.concat(matrix);
        
canvas.drawPath(linePath, linePaint);
        
canvas.drawCircle(130.0f, 50.0f, 16.0f, 

if(elapsedTime < animationDuration){
            this.postInvalidateDelayed(10000 / framePerSeconds);
        }
        
//this.postInvalidateOnAnimation();
invalidate();

Note that in matrix.postRotate(... , 130.0f, 50.0f) method, we have three parameters. First one is degree to rotate, and remaining two parameters are axis. These two parameters must have same axis values as your circle's center values that you just set while adding circle to line. e.g. linePath.addCircle(130.0f, 50.0f, ...).

canvas.concat(matrix) method just attaches the matrix with canvas. 

Finally, invalidate(), this method is required.

Here is the complete code of Needle.java class.

Java
package com.needle.views;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;

public class Needle extends View {

    private Paint linePaint;
    private Path linePath;
    private Paint needleScrewPaint;
    
    private Matrix matrix;
    private int framePerSeconds = 100;
    private long animationDuration = 10000;
    private long startTime;
    
    public Needle(Context context) {
        super(context);
        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
    }
    
    public Needle(Context context, AttributeSet attrs) {
        super(context, attrs);
        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
    }

    public Needle(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        matrix = new Matrix();
        this.startTime = System.currentTimeMillis();
        this.postInvalidate();
        init();
    }

    private void init(){
        
        linePaint = new Paint();
        linePaint.setColor(Color.RED); // Set the color
        linePaint.setStyle(Paint.Style.FILL_AND_STROKE); // set the border and fills the inside of needle
        linePaint.setAntiAlias(true);
        linePaint.setStrokeWidth(5.0f); // width of the border
        linePaint.setShadowLayer(8.0f, 0.1f, 0.1f, Color.GRAY); // Shadow of the needle
        
        linePath = new Path();
        linePath.moveTo(50.0f, 50.0f);
        linePath.lineTo(130.0f, 40.0f);
        linePath.lineTo(600.0f, 50.0f);
        linePath.lineTo(130.0f, 60.0f);
        linePath.lineTo(50.0f, 50.0f);
        linePath.addCircle(130.0f, 50.0f, 20.0f, Path.Direction.CW);
        linePath.close();
        
        needleScrewPaint = new Paint();
        needleScrewPaint.setColor(Color.BLACK);
        needleScrewPaint.setAntiAlias(true);
        needleScrewPaint.setShader(new RadialGradient(130.0f, 50.0f, 10.0f,
                Color.DKGRAY, Color.BLACK, Shader.TileMode.CLAMP));
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        
        long elapsedTime = System.currentTimeMillis() - startTime;

        matrix.postRotate(1.0f, 130.0f, 50.0f); // rotate 10 degree every second
        canvas.concat(matrix);
        
        canvas.drawPath(linePath, linePaint);
        
        canvas.drawCircle(130.0f, 50.0f, 16.0f, needleScrewPaint);
        
        if(elapsedTime < animationDuration){
            this.postInvalidateDelayed(10000 / framePerSeconds);
        }
        
        //this.postInvalidateOnAnimation();
        invalidate();
    }

}

Points of Interest

As we have seen that hwo it is easy to draw things from simple and scratch. It makes learning quickly. In this tutorial, I have made the needle with hardcoded values. You should apply dynamic values. You should also consider the onSizeChanged(int, int, int, int) method. You may find repeated code in constructo, just ignore it and put it in init() method. :)

Thanks for reading.

Send your feedback/suggestions and improvements. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)