December 29th, 2012

Android Tricks: A Karma Bar Inspired by YouTube

To represent the two competing “like” and “dislike” tallies on individual videos, YouTube uses an elegant solution—a partially filled bar that indicates both values:

2012-12-21_2355

I wanted to use a similar interface in one of my applications to represent the “given” and “received” counts for an item. To display the values, I created a custom Android control called KarmaMeter. The bar uses the color theme defined in the corresponding section in the Android design documentation.

karma-bar

The code for the control is given below. It’s implemented as a View and supports animating the changes between different values:

public class KarmaMeter : View
{
	const int DefaultHeight = 10;
	const int DefaultWidth = 120;

	double position = 0.5;
	float lastWidth = -1;
	Paint positivePaint;
	Paint negativePaint;

	public KarmaMeter (Context context, IAttributeSet attrs) :
		base (context, attrs)
	{
		Initialize ();
	}

	public KarmaMeter (Context context, IAttributeSet attrs, int defStyle) :
		base (context, attrs, defStyle)
	{
		Initialize ();
	}

	void Initialize ()
	{
		positivePaint = new Paint {
			AntiAlias = true,
			Color = Color.Rgb (0x99, 0xcc, 0),
		};
		positivePaint.SetStyle (Paint.Style.FillAndStroke);

		negativePaint = new Paint {
			AntiAlias = true,
			Color = Color.Rgb (0xff, 0x44, 0x44)
		};
		negativePaint.SetStyle (Paint.Style.FillAndStroke);
	}

	public void SetKarmaBasedOnValues (int totalGiven, int totalGotten, bool animate = true)
	{
		var value = (((totalGiven - totalGotten) / (float)(totalGiven + totalGotten)) + 1) / 2f;
		SetKarmaValue (value, animate);
	}

	public double KarmaValue {
		get {
			return position;
		}
		set {
			position = Math.Max (0f, Math.Min (value, 1f));
			Invalidate ();
		}
	}

	public void SetKarmaValue (double value, bool animate)
	{
		if (!animate) {
			KarmaValue = value;
			return;
		}

		var animator = ValueAnimator.OfFloat ((float)position, (float)Math.Max (0f, Math.Min (value, 1f)));
		animator.SetDuration (500);
		animator.Update += (sender, e) => KarmaValue = (double)e.Animation.AnimatedValue;
		animator.AnimationEnd += (sender, e) => animator.RemoveAllListeners ();
		animator.Start ();
	}

	protected override void OnMeasure (int widthMeasureSpec, int heightMeasureSpec)
	{
		var width = View.MeasureSpec.GetSize (widthMeasureSpec);
		SetMeasuredDimension (width < DefaultWidth ? DefaultWidth : width,
		                      DefaultHeight);
	}

	protected override void OnDraw (Canvas canvas)
	{
		base.OnDraw (canvas);

		float middle = canvas.Width * (float)position;
		canvas.DrawPaint (negativePaint);
		canvas.DrawRect (0,
		                 0,
		                 middle,
		                 canvas.Height,
		                 positivePaint);
	}
}

To set the value displayed by the bar (between `0.0f` and `1.0f`), you can either directly use the property KarmaValue/SetKarmaValue. Or, if you maintain a count of your positive/negatives values, you can use the SetKarmaBasedOnValues method.