# Draw a Lamp on Canvas

In this tutorial we will learn a little bit about Canvas. We will draw a lamp on the screen and for this we will have a button which can turn on and turn off our lamp.

#### Android Canvas

First of all we have to speak about the Android Canvas. With Canvas we can perform 2D drawing onto the screen. Basically it is an empty space to draw onto.

The Canvas class is not a new concept, this class is actually wrapping a SKCanvas under the hood. The SKCanvas comes from SKIA, which is a 2D Graphics Library that is used on many different platforms. SKIA is used on platforms such as Google Chrome, Firefox OS, Flutter, Fuschia etc. Once you understand how the Canvas works on Android, the same drawing concepts apply to many other different platforms.

##### Shapes to draw on Canvas

There are many different things you can draw onto a Canvas. One of the most common drawing operations is to draw a bitmap (image), a line, a circle, arc or recangle.

On our tutorial we will use the drawArc, drawCircle  and drawLine.

###### drawArc

There are two Canvas.drawArc method implementations, but we will use the simpler method which has 5 arguments.

• oval: RectF – According to the docs, this parameter represents “the bounds of oval used to define the shape and size of the arc”. This is important because it helps to remind us that when we are drawing an arc, we are really drawing a section of an oval.
• startAngle: float – Now we are getting into the land of confusion. The docs define this parameter as the “starting angle (in degrees) where the arc begins”. If you’re asking yourself, “starting from what!?” then you are a mind reader and have accurately guessed exactly what I thought when reading that. But before I help answer your woes, let’s continue with the other parameters.
• sweepAngle: float – Another parameter with the word “angle” in it! Not a great start. The docs define this parameter as the “sweep angle (in degrees) measured clockwise”. At this point if you’re like me you’re probably wondering “what on earth is a sweep angle”. But don’t worry, all shall soon be revealed.
• useCenter: boolean – Now this one, surprisingly, has a super helpful description! According to the docs, “if true, include the center of the oval in the arc, and close it if it is being stroked. This will draw a wedge.” Finally! A visual clue for a visual method!
• paint: Paint – And lastly, the Paint object used to draw our arc.
``````oval.set(
left,
top,
right,
bottom
)

canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)

draw an arc``````
###### drawCircle

It draws a simple circle. It only requires the center coordinate and the radius.

``````canvas.drawCircle(
centerCoordinateX, centerCoordinateY,
paint)

draw an circle``````
###### drawLine

With drawLine we can draw on the canvas a simple line. For this we need just two points on our canvas. One for the start and one for the end point.

``````canvas.drawLine(startX, startY, endX, endY, paint)

draw a line``````

After a short prologue, we can start coding 🙂

#### Step 1 – activity_main.xml

In the first step we will create our user interface. It will be again very simple. It will contain our LampView and a Button, what will turn on an off our lamp.

We will align the Views on our screen with ConstarintLayout.

``````<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.inspirecoding.lampapp.LampView
android:id="@+id/lampView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:state="_off"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/btn_setState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@string/turn_on"
android:textSize="40sp"
android:textColor="@android:color/white"
android:background="@drawable/lightblue_button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lampView"/>
</androidx.constraintlayout.widget.ConstraintLayout>

activity_main.xml``````

Our LampView has a “state” argument with the value “_off”. This will be responsible of the state of the lamp. With the button we will change this state. The implenetation cames later.

#### Step 2 – LampView::class

To have our custom view, we have to extend the LampView::class with the View class.

###### View:class

This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.).

###### onDraw

After the extend our LampView::class, we have to implement the onDraw method. This method will run at the first time, so we will implement here the methods separetly and draw the shapes on the canvas.

###### onMeasure

Measure the view and its content to determine the measured width and the measured height. This method is invoked by measure(int, int). When overriding this method, we must call setMeasuredDimension(int, int) to store the measured width and height of this view.

###### drawGlassBulb

We will draw in this method the border of the bulb. As we talked about it earlier, we will use drawArc.

###### initStartAndEndPoints

In the next method we have to specify the start and the end point of the lines from the arc of the bulb. For this we will call on the help of mathematics 😉 We will have here some cosine calculations.

###### drawSocket

Next phase of our drawing is to draw the socket of the lamp. It will draw 5 lines, the left and the right lines and the 3 lines in the middle.

###### drawSocketFill

The final step is to fill the socket with color. To do this the area must be defined. We will identify there two areas. A rectangle and at the bottom of it a trapeze.

###### Save the state

As we navigate through, out of, and back to our app, the View instances in our app transition through different states in thier lifecycle.

onSaveInstanceState is called to retrieve per-instance state from a view before being killed so that the state can be restored in onCreate(Bundle) or onRestoreInstanceState(Bundle).

In the onSaveInstanceState we will save the state of the LampView and when we rotate our device or send it to the background, then the lamp will have the same state.

###### LampView::class

After the introduction here is the whole source code for the LampView.

``````import android.content.Context
import android.graphics.*
import android.os.Bundle
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin

class LampView(context: Context, attrs: AttributeSet): View(context, attrs)
{
companion object {
const val turnedOff = 0L
const val turnedOn = 1L
}

private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

private var borderColor = Color.BLACK

private var size = 0

private var center_x: Float = 0f
private var center_y: Float = 0f

private var startY = 0f
private var endY = 0f
private var startX_leftVeticalLine = 0f
private var endX_leftVeticalLine = 0f
private var startX_rightVeticalLine = 0f
private var endX_rightVeticalLine = 0f

private var radius: Float = 0f
val border: Float = 10f

//BORDER
val paintBorder = Paint(Paint.ANTI_ALIAS_FLAG)
//FILL
val paintFill = Paint()

var state = turnedOff
set(state) {
field = state
invalidate()
}

init {
paint.isAntiAlias = true
setXmlAttributes(attrs)
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

drawGlassBulb(canvas)
initStartAndEndPoints()
drawSocketFill(canvas)
drawSocket(canvas)
}
private fun setXmlAttributes(attrs: AttributeSet) {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.LampView, 0, 0)

state = typedArray.getInt(R.styleable.LampView_state, turnedOn.toInt()).toLong()

typedArray.recycle()
}
private fun initStartAndEndPoints() {
endY = (size*0.9).toFloat()

}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
size = min(measuredWidth, measuredHeight)
setMeasuredDimension(size, size)
}
private fun drawGlassBulb(canvas: Canvas) {
paintBorder.color = borderColor
paintBorder.strokeWidth = border
paintBorder.style = Paint.Style.STROKE

if(state == turnedOff)
{
paintFill.color = Color.WHITE
paintFill.strokeWidth = border
paintFill.style = Paint.Style.FILL
}
else
{
paintFill.color = Color.YELLOW
paintFill.strokeWidth = border
paintFill.style = Paint.Style.FILL
}

val oval = RectF()

center_x = size / 2f
center_y = size / 3f

oval.set(
)

//FILL

// Circle Arc Line
canvas.drawArc(oval, 125f, 290f, false, paintBorder)
}
private fun drawSocket(canvas: Canvas) {
paintBorder.color = borderColor
paintBorder.strokeWidth = border
paintBorder.style = Paint.Style.STROKE

//BORDER
//Left vertical line
canvas.drawLine(
startX_leftVeticalLine,
startY,
endX_leftVeticalLine,
endY, paintBorder)
//Right vertical line
canvas.drawLine(
startX_rightVeticalLine,
startY,
endX_rightVeticalLine,
endY, paintBorder)

//Diagonal line left
canvas.drawLine(
endX_leftVeticalLine,
endY,
endX_leftVeticalLine*1.2f,
size.toFloat(), paintBorder)
//Diagonal line right
canvas.drawLine(
endX_rightVeticalLine,
endY,
(endX_rightVeticalLine - endX_leftVeticalLine*0.2f),
size.toFloat(), paintBorder)

//Horizontal top line
canvas.drawLine(
(startX_leftVeticalLine),
(endY - ((endY - startY)/2)), //The half of the left vertical line
(startX_rightVeticalLine),
(endY - ((endY - startY)/2)), paintBorder) //The half of the right vertical line
//Horizontal middle line - 1
canvas.drawLine(
startX_leftVeticalLine,
(endY - ((endY - startY)/4)), //The half of the left vertical line
startX_rightVeticalLine,
(endY - ((endY - startY)/4)), paintBorder) //The half of the right vertical line
//Horizontal middle line - 2
canvas.drawLine(
startX_leftVeticalLine,
endY,
startX_rightVeticalLine,
endY, paintBorder)
//Horizontal bottom line
canvas.drawLine(
endX_leftVeticalLine*1.2f,
size.toFloat(),
(endX_rightVeticalLine - endX_leftVeticalLine*0.2f),
size.toFloat(), paintBorder)
}
private fun drawSocketFill(canvas: Canvas) {
paintFill.color = Color.GRAY
paintFill.strokeWidth = border
paintFill.style = Paint.Style.FILL

val path = Path()
path.moveTo(startX_leftVeticalLine, startY)
path.lineTo(startX_rightVeticalLine, startY)
path.lineTo((endX_rightVeticalLine), endY)
path.lineTo(endX_leftVeticalLine, endY)
path.lineTo(startX_leftVeticalLine, startY)
canvas.drawPath(path, paintFill)

path.reset()
path.moveTo(endX_leftVeticalLine, endY)
path.lineTo(startX_rightVeticalLine, endY)
path.lineTo((endX_rightVeticalLine - endX_leftVeticalLine*0.2f), size.toFloat())
path.lineTo(endX_leftVeticalLine*1.2f, size.toFloat())
path.lineTo(endX_leftVeticalLine, endY)
canvas.drawPath(path, paintFill)
}

override fun onSaveInstanceState(): Parcelable {
val bundle = Bundle()

bundle.putLong("state", state)
bundle.putParcelable("superState", super.onSaveInstanceState())

return bundle
}
override fun onRestoreInstanceState(_state: Parcelable) {
var viewState = _state

if (viewState is Bundle) {
state = viewState.getLong("state", turnedOff)
viewState = viewState.getParcelable("superState")!!
}

super.onRestoreInstanceState(viewState)
}
}

LampView::class``````

#### Step 2 – Creating Custom XML Attributes

As we talked earlier, we will have the “state” attribute, which can set the state of our app. If the “state” has the value “_on” then the lamp is turned on, if it is “_off”, then our lamp is off.

To create a new XML attribute go to res/values and create new values resource file named attrs.xml.

Add the following lines to the file.

``````<resources/>
<declare-styleable name="LampView"/>
<attr name="state" format="enum"/>
<enum name="_off" value="0"//>
<enum name="_on" value="1"//>
</attr/>
</declare-styleable/>
</resources>

attrs.xml``````

#### Step 3 – MainActivity::class

The last step in our LampViewApp is the veeery simple MainActivity::class.

``````import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

btn_setState.setOnClickListener {
if (lampView.state == LampView.turnedOff)
{
lampView.state = LampView.turnedOn
btn_setState.text = getString(R.string.turn_off)
}
else
{
lampView.state = LampView.turnedOff
btn_setState.text = getString(R.string.turn_on)
}
}
}
}

MainActivity::class``````

In the MainActivity::class we have only the setOnClickListener of our btn_setState Button. Whit it we can set the state of the lamp, and we will do it with a simple if-else statement.

#### Questions

I hope the description was understandable and clear. But if you have still questions, then leave me comments below! 😉

Have a nice a day! 🙂

Click to rate this post!
[Total: 0 Average: 0]

### 4 thoughts on “Draw a Lamp on Canvas”

1. hello!,I really like your writing very so much! percentage we be in contact more approximately your article on AOL? I need a specialist on this house to solve my problem. May be that is you! Having a look ahead to see you. | а

• Hello!

Thank you. 🙂

You can find me during the social media.
The links are at the left side.

Have a nice day. 🙂

2. Its like you read my mind! You appear to know a lot about this, like you wrote the book in it or something.
I think that you could do with a few pics to drive the message home a little bit,