Custom Light Drawing in an Android Smart Home app
TL;DR: In this article we are going to draw lights like this:
The Story Behind
I started to work on a smart home app to control my Philips Hue lights and Sonoff switches from a Raspberry Pi 3. The Pi communicates with the lights and switches. The companion app (what we can see above) sends commands to the Pi from the phone. Not directly to the Pi, only to Firebase, but the Pi syncs the data from there.
Since this app will only serve our apartment, why not make it “our” app? I opened Sketch and I did some drawing with the interiors and walls, but the real fun begins when I started to play with the lights…
We only do what’s absolutely necessary in a custom View. We don’t want to reinvent the wheel. The images, the static part should be done in an Android Widget. Our xml structure is the following:
As you see we have a hardcoded aspect ratio of 360:460. This is the size of the VectorDrawables. As long as we keep the views in this fixed aspect ratio we are good to go.
We’ll focus on the LightsShaderView today so lets get rid of the others:
The Data Classes
First we need to know where are the rooms, more precisely where are the walls. I don’t think the apartment structure will change in the near future, so I hardcoded the coordinates into Firebase for now. It’s fairly easy to extract the object coordinates from the VectorDrawable. We need to know where are the lights too, so at the end of the day we have something like this to use:
Managing the Coordinating Systems
Although the interior structure of the apartment won’t change, the drawing method or the View might, so be careful there. I use two well defined system for handling the coordinates.
In the object system every x and y coordinate is a Float value between [0,1]. This object system is used to store data in Firebase, because these coordinates won’t change on different screen sizes or Views.
The view system is the View’s coordinating system. The object coordinates are converted to a view system right before drawing and use the View’s current width and height to upscale it.
In the app I use these class extension functions to help me out:
Custom Drawing Setup
Before we dive into the custom drawing part, be sure we set up the layerType to LAYER_TYPE_SOFTWARE for our LightsShaderView:
The reason why we use it: “When the application is not using hardware acceleration, a software layer is useful to apply a specific color filter and/or blending mode and/or translucency to a view and all its children.”
Finally: Let’s Draw
What we do is simple. We just cut out the shadows cast by the walls.
Imagine a circle with a radius of 2 (remember: we are in the object system) around the light source (Lets call the center point L). For every wall (as a section with A, and B) we’ll calculate two points:
- C is the intersection point of the circle and the line defined by L and A
- D is the intersection point of the circle and the line defined by L and B
Lets create a mask Path object with a circle in it (center: L, radius: 0.5).
Iterate through every wall, and create a new Path with a quadrilateral defined by A, B, C and D.
Simply cut out (with the op method) this newly constructed path from the mask.
After we cut out the A,B,C,D quadrilateral for every wall, we draw a RadialGradient with the mask Path. (which now contains only the properly lighted areas)
The process looks like this for one wall:
The final onDraw method:
The more interesting createMask method:
As you see I left out some boring code, e.g. the light indicators, or the dragging mechanism. If you interested in my smart home app’s progress please leave a note here or ping me on Twitter. I’d be happy to write more about it.
Hello There, thanks for reading this far. If you have any questions or ideas, please don’t hesitate to contact me. You can reach me on Twitter or leave a comment here.