Gradient / Transparency

App UI / UX – Part 3

In the previous article we applied some custom typefaces to our app which displayed temperature and humidity information obtained from a Texas Instruments SensorTag over Bluetooth LE. One disappointing thing was that we were displaying the rather lofty temperature of 27°C (in UK terms, anyway) against a blue background, which rather implies cold rather than warm. In this article we’ll look at changing the background colour according to the measured temperature.

Before we begin, let’s define a few parameters. I live in the UK, and I’m going to target a temperature range of 0-30°C. These values will be defined as constants, so the code can easily be adapted to work with other ranges which may suit different climates. For values which appear within these bounds, we will change the background colour to range from blue at 0°C, to red at 30°C. To make it a little more interesting, we’re not going to simply transition from blue to red (passing through muddy purple on the way), we’re actually going to use a rather more dynamic range of colours. To achieve this we’re going to use an HSV gradient to calculate the colour for any given temperature value. This may sound a little scary, but in reality is pretty simple, and only requires a few lines of code to implement.

Let’s start with a quick introduction to HSV. HSV stands for Hue, Saturation, Value, and is a colour-space (i.e. a model for representing colour values) just like the RGB colour-space that we’re all familiar with. HSV is simply an alternative model for defining colour values which works in a way makes our temperature to colour mapping really easy. I’m not going to provide a full description of HSV because it is both quite complex, and there are plenty of definitions already available (such as this). But in short, the Hue represents the colour itself; the Saturation represents how much of that colour is used; and the Value represents the luminance of the colour used. For now let’s ignore the saturation and value parameters, it’s the way that hue works that will enable us to do what we need.

To understand the Hue, we need a colour wheel (this one was borrowed from here):

RGB_color_circle

In HSV the hue is the angle as defined on the colour wheel, the saturation and value are both floating point values ranging from 0.0 to 1.0.

By specifying different angles around the colour wheel, we’ll get different hues. In our case we want to traverse from blue at 240° on the wheel round to red at 360° / 0° on the wheel. So, by simply changing the value of the hue we go from blue through purple to red if we go clockwise. However, if we go anticlockwise around the wheel we’ll go from blue through cyan, then green, then yellow, and finally red. That will be the colour progression that we’ll use.

We’ll simply calculate a ratio within the range: (temperature - lowerLimit) / (upperLimit - lowerLimit) and use this to calculate the angle for the hue that we need.

Let’s bring the saturation and value parameters back in to the picture. For the purposes of keeping our description simple we ignored them. But now that the concept of changing the hue is understood, we simply need to transition from the start saturation to the end saturation using the same ratio as we’re using for hue, and do the same with value.

That’s all well and good, but how do we get HSV values in the first place? android.graphics.Color is our friend here, as it contains utility methods to convert RGB colours to HSV and vice versa.

So, we first define our start and end temperature bounds and their respective colours:

private static final float[] mStartColour = new float[3];
private static final float mStartTemperature = 0.0f;
private static final float[] mEndColour = new float[3];
private static final float mEndTemperature = 30.0f;

static {
	Color.RGBToHSV(0x00, 0x00, 0xFF, mStartColour);
	Color.RGBToHSV(0xFF, 0x00, 0x00, mEndColour);
	if (mStartColour[0] > mEndColour[0] && 
		mStartColour[0] - mEndColour[0] <= 180) {
		mEndColour[0] += 360;
	} else if (mEndColour[0] > mStartColour[0] && 
		mEndColour[0] - mStartColour[0] <= 180) {
		mStartColour[0] += 360;
	}
}

Lines 7 & 8 generate the start and end colours in HSV form from the raw red, green and blue values. Lines 9-15 are ensuring that we go anticlockwise around the colour wheel. To use our actual values, the start hue will be 270° and the end hue will be 360°. Following this check, we’ll add 360° to the start value giving 630°. The range will then transition from 630° to 360° going from blue through cyan, then green, then yellow, to red.

So to use this, first we need to get a reference to the FrameLayout which we defined in our layout:

@Override
public View onCreateView(LayoutInflater inflater, 
	ViewGroup container, Bundle savedInstanceState) {
	View v = inflater.inflate(R.layout.fragment_display, container, false);
	if (v != null) {
		mTemperature = (TextView) v.findViewById(R.id.temperature);
		mHumidity = (TextView) v.findViewById(R.id.humidity);
		mParentContainer = (ViewGroup) v.findViewById(R.id.parent_container);
	}
	.
	.
	.
	return v;
}

Next we need to change the background colour when the temperature changes:

public void setData(float temperature, float humidity) {
	if (mParentContainer != null) {
		mParentContainer.setBackgroundColor(
			getColour(temperature));
	}
	.
	.
	.
}

Finally we need to implement getColour() which will return the appropriate colour value from the temperature:

private int getColour(float temperature) {
	float[] result;

	if (temperature <= mStartTemperature) {
		result = mStartColour;
	} else if (temperature >= mEndTemperature) {
		result = mEndColour;
	} else {
		result = new float[3];
		float ratio = (temperature - mStartTemperature) / 
			(mEndTemperature - mStartTemperature);
		for (int i = 0; i < 3; i++) {
			result[i] = (mEndColour[i] - mStartColour[i]) * 
				(ratio) + mStartColour[i];
		}
	}
	return Color.HSVToColor(result);
}

The first two conditional blocks handle temperature values which are outside of our range, and clamp them to the appropriate start or end colour. The final conditional block actually does the work, it first calculates the ratio of where the temperature value is within our range – where the start temperature is 0.0 and the end temperature is 1.0. It then applies this ratio to each of the hue, saturation and value components.

Finally the HSV colour is converted back to an RGB colour and this is returned.

And that’s it! The theory took quite a bit longer to explain that it did to actually implement and, as I promised earlier, it really didn’t take much code to implement.

If we run this, we can see different background colours being used for different temperatures:

display_04degrees

display_14degrees

display_22degrees

display_28degrees

One thing worth pointing our is the decision to use a darker, semi-transparent background for the panel containing the text, plus a lighter text colour. This is to ensure that the text remains visible over varying background colours as was discussed in the article on Transparency.

The thing that I really like about this feature is that the user will not be aware of it, initially. Some users will never be conscious of it, but they may find it jarring if they see a high temperate against a blue background, or a low temperature against a red background. Sometimes it’s the subtle things that the user doesn’t consciously notice which build engagement.

That’s it for the UI of our DisplayFragment – it certainly looks a lot different to when we started. If anything it still looks a little sparse, but if we were developing a full app, we’d possibly get more data from the SensorTag such as barometric pressure, and IR temperature and display those as well.

In the next article in this series, we’ll turn our attention to the overall UX, and look at ways that we can improve things for our user.

The source code for this article is available here.

© 2014, Mark Allison. All rights reserved.

Copyright © 2014 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.