App 2.21.0: Overview style Dashboard, an instrument cluster
Power, speed and battery as gauges and a strip of gear, climate, clock, usage and battery; Settings picks Tiles or Dashboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A round instrument for the dashboard Overview: a 240° scale open at the bottom, major and minor
|
||||
* ticks, numbers, a needle, and up to three lines of text in the middle — the unit, the value as
|
||||
* digits, and a caption.
|
||||
*
|
||||
* Drawn on a Canvas with colours resolved from the accent theme, so it needs no gradient
|
||||
* drawables (which crash Android 9 with theme colours). A null value draws no needle and a dash:
|
||||
* an unread value is never shown as zero. The needle eases towards a new value instead of
|
||||
* jumping, and stops redrawing once it is there.
|
||||
*/
|
||||
public final class GaugeView extends View {
|
||||
|
||||
private static final float START_DEG = 150f;
|
||||
private static final float SWEEP_DEG = 240f;
|
||||
|
||||
private final Paint arc = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint glow = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint tick = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint warnTick = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint number = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint unitPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint digits = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint caption = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint needle = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint hub = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final RectF oval = new RectF();
|
||||
|
||||
private float min = 0f, max = 100f, major = 20f;
|
||||
private int minorPerMajor = 4;
|
||||
private boolean numbers = true;
|
||||
private String lowLabel, highLabel;
|
||||
private Float warnBelow;
|
||||
private String unit = "", valueText = "—", captionText;
|
||||
private Float target;
|
||||
private float shown = Float.NaN;
|
||||
|
||||
public GaugeView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
int accent = AccentColor.resolve(context, R.attr.evAccent);
|
||||
int light = AccentColor.resolve(context, R.attr.evAccentLight);
|
||||
arc.setStyle(Paint.Style.STROKE);
|
||||
arc.setStrokeCap(Paint.Cap.ROUND);
|
||||
arc.setColor(accent);
|
||||
glow.setStyle(Paint.Style.STROKE);
|
||||
glow.setStrokeCap(Paint.Cap.ROUND);
|
||||
glow.setColor(AccentColor.withAlpha(accent, 0x40));
|
||||
tick.setColor(accent);
|
||||
tick.setStrokeCap(Paint.Cap.ROUND);
|
||||
warnTick.setColor(0xFFFFB4AB);
|
||||
warnTick.setStrokeCap(Paint.Cap.ROUND);
|
||||
number.setColor(0xFFF4F3EF);
|
||||
number.setTextAlign(Paint.Align.CENTER);
|
||||
unitPaint.setColor(0xFFBDC4C6);
|
||||
unitPaint.setTextAlign(Paint.Align.CENTER);
|
||||
digits.setColor(light);
|
||||
digits.setTextAlign(Paint.Align.CENTER);
|
||||
digits.setFontFeatureSettings("tnum");
|
||||
caption.setColor(0xFFBDC4C6);
|
||||
caption.setTextAlign(Paint.Align.CENTER);
|
||||
caption.setFontFeatureSettings("tnum");
|
||||
needle.setColor(0xFFF4F3EF);
|
||||
needle.setStrokeCap(Paint.Cap.ROUND);
|
||||
hub.setColor(accent);
|
||||
}
|
||||
|
||||
/** The scale: range, spacing of the numbered ticks, and minor ticks between two of them. */
|
||||
void configure(float min, float max, float major, int minorPerMajor, String unit) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.major = major;
|
||||
this.minorPerMajor = minorPerMajor;
|
||||
this.unit = unit;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/** Letters at the two ends (E and F) instead of numbers along the scale. */
|
||||
void setEndLabels(String low, String high) {
|
||||
lowLabel = low;
|
||||
highLabel = high;
|
||||
numbers = false;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/** Ticks below this value drawn in the warning colour (a low battery). */
|
||||
void setWarnBelow(Float value) {
|
||||
warnBelow = value;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* The needle's value, null when unread, and its digits ("—" then) and caption. Drawing is
|
||||
* left to the next frame, and only the needle animates.
|
||||
*/
|
||||
void setValue(Float value, String valueText, String captionText) {
|
||||
target = value == null || !Float.isFinite(value) ? null : value;
|
||||
this.valueText = valueText == null ? "—" : valueText;
|
||||
this.captionText = captionText;
|
||||
if (target != null && Float.isNaN(shown)) shown = target;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private float angleOf(float v) {
|
||||
float f = (Math.max(min, Math.min(max, v)) - min) / (max - min);
|
||||
return START_DEG + SWEEP_DEG * f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas c) {
|
||||
super.onDraw(c);
|
||||
float w = getWidth(), h = getHeight();
|
||||
// Below the centre: the unit, the digits and two caption lines, down to 0.9 r.
|
||||
float r = Math.min(w / 2f, h / 1.92f) * 0.95f;
|
||||
if (r <= 0f) return;
|
||||
float cx = w / 2f, cy = (h - 1.9f * r) / 2f + r;
|
||||
|
||||
float stroke = r * 0.035f;
|
||||
arc.setStrokeWidth(stroke);
|
||||
glow.setStrokeWidth(stroke * 2.5f);
|
||||
oval.set(cx - r, cy - r, cx + r, cy + r);
|
||||
c.drawArc(oval, START_DEG, SWEEP_DEG, false, glow);
|
||||
c.drawArc(oval, START_DEG, SWEEP_DEG, false, arc);
|
||||
|
||||
// Ticks: every minor step; the numbered ones longer and thicker.
|
||||
number.setTextSize(r * 0.105f);
|
||||
float minor = major / Math.max(1, minorPerMajor);
|
||||
int steps = Math.round((max - min) / minor);
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float v = min + i * minor;
|
||||
boolean isMajor = i % Math.max(1, minorPerMajor) == 0;
|
||||
double a = Math.toRadians(angleOf(v));
|
||||
float outer = r * 0.90f, inner = isMajor ? r * 0.76f : r * 0.83f;
|
||||
Paint p = warnBelow != null && v < warnBelow ? warnTick : tick;
|
||||
p.setStrokeWidth(isMajor ? r * 0.022f : r * 0.012f);
|
||||
float cos = (float) Math.cos(a), sin = (float) Math.sin(a);
|
||||
c.drawLine(cx + outer * cos, cy + outer * sin, cx + inner * cos, cy + inner * sin, p);
|
||||
if (isMajor && numbers) {
|
||||
float lr = r * 0.62f;
|
||||
c.drawText(String.format(Locale.US, "%.0f", v),
|
||||
cx + lr * cos, cy + lr * sin + number.getTextSize() * 0.36f, number);
|
||||
}
|
||||
}
|
||||
if (!numbers) {
|
||||
drawEndLabel(c, cx, cy, r, min, lowLabel, warnBelow != null ? warnTick.getColor() : number.getColor());
|
||||
drawEndLabel(c, cx, cy, r, max, highLabel, number.getColor());
|
||||
}
|
||||
|
||||
// Under the hub, clear of the needle's sweep: unit, digits, caption.
|
||||
unitPaint.setTextSize(r * 0.095f);
|
||||
c.drawText(unit, cx, cy + r * 0.25f, unitPaint);
|
||||
digits.setTextSize(r * 0.22f);
|
||||
c.drawText(valueText, cx, cy + r * 0.50f, digits);
|
||||
if (captionText != null) {
|
||||
caption.setTextSize(r * 0.09f);
|
||||
float y = cy + r * 0.68f;
|
||||
for (String line : captionText.split("\n")) {
|
||||
c.drawText(line, cx, y, caption);
|
||||
y += caption.getTextSize() * 1.25f;
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
shown += (target - shown) * 0.25f;
|
||||
if (Math.abs(target - shown) < (max - min) * 0.001f) shown = target;
|
||||
double a = Math.toRadians(angleOf(shown));
|
||||
needle.setStrokeWidth(r * 0.03f);
|
||||
c.drawLine(cx, cy, cx + r * 0.80f * (float) Math.cos(a), cy + r * 0.80f * (float) Math.sin(a), needle);
|
||||
c.drawCircle(cx, cy, r * 0.065f, hub);
|
||||
if (shown != target) postInvalidateOnAnimation();
|
||||
} else {
|
||||
c.drawCircle(cx, cy, r * 0.065f, hub);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawEndLabel(Canvas c, float cx, float cy, float r, float v, String label, int color) {
|
||||
if (label == null) return;
|
||||
double a = Math.toRadians(angleOf(v));
|
||||
float lr = r * 0.62f;
|
||||
int old = number.getColor();
|
||||
number.setColor(color);
|
||||
number.setTextSize(r * 0.16f);
|
||||
c.drawText(label, cx + lr * (float) Math.cos(a), cy + lr * (float) Math.sin(a) + number.getTextSize() * 0.36f, number);
|
||||
number.setColor(old);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user