I'm answering my own question for anyone who wants to do something like this later.
You can create custom views in Android and draw whatever you want to display. There is good documentation here.
Here's a relevant code snippet. It's not perfect but it does the job.
public class AngleVisualization extends View {
private Paint textPaint;
private Paint arcPaint;
private Paint linePaint;
RectF oval;
private float extension;
private float flexion;
private int textColor;
private int arcColor;
private float extensionLabelX;
private float extensionLabelY;
private float flexionLabelX;
private float flexionLabelY;
private Rect extensionBounds = new Rect();
public AngleVisualization(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.AngleVisualization,
0, 0);
try {
extension = a.getFloat(R.styleable.AngleVisualization_extensionValue, 0);
flexion = a.getFloat(R.styleable.AngleVisualization_flexionValue, 0);
textColor = a.getColor(R.styleable.AngleVisualization_textColor, Color.BLACK);
arcColor = a.getColor(R.styleable.AngleVisualization_arcColor, context.getResources().getColor(R.color.green));
extensionLabelX = a.getDimension(R.styleable.AngleVisualization_extensionLabelX, 190);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_extensionLabelY, 150);
flexionLabelX = a.getDimension(R.styleable.AngleVisualization_flexionLabelX, 50);
extensionLabelY = a.getDimension(R.styleable.AngleVisualization_flexionLabelY, 190);
} finally {
a.recycle();
}
oval = new RectF();
init();
}
private void init() {
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(textColor);
textPaint.setTextSize(30);
arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
arcPaint.setColor(arcColor);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(arcColor);
linePaint.setStrokeWidth(3);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
canvas.drawArc(oval, extension, flexion - extension, true, arcPaint);
canvas.drawLine(0.0f, extensionBounds.height(), oval.right / 2, extensionBounds.height(), linePaint);
canvas.drawText(extensionString, extensionLabelX, extensionLabelY, textPaint);
canvas.drawText(decimalFormat.format(flexion) + "˚", flexionLabelX, flexionLabelY, textPaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// Account for padding
float xpad = (float)(getPaddingLeft() + getPaddingRight());
float ypad = (float)(getPaddingTop() + getPaddingBottom());
float ww = (float)w - xpad;
float hh = (float)h - ypad;
String extensionString = decimalFormat.format(extension) + "˚";
textPaint.getTextBounds(extensionString, 0, extensionString.length(), extensionBounds);
float diameter = Math.min(ww, (hh - extensionBounds.height()) * 2.0f) - extensionBounds.height();
oval = new RectF(
0,
diameter / -2.0f,
diameter,
diameter / 2.0f);
oval.offsetTo(getPaddingLeft(), getPaddingTop() - diameter / 2.0f + extensionBounds.height());
flexionLabelY = diameter / 2.0f + extensionBounds.height();
flexionLabelX = 0;
extensionLabelY = extensionBounds.height();
extensionLabelX = ww / 2;
}
}