package org.droidplanner.android.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Paint.Cap; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Shader.TileMode; import android.util.AttributeSet; import android.view.View; public class AttitudeIndicator extends View { private static final float INTERNAL_RADIUS = 0.85f; private static final float YAW_ARROW_SIZE = 1.2f; private static final float YAW_ARROW_ANGLE = 4.5f; private static final float PITCH_TICK_LINE_LENGTH = 0.4f; private static final int PITCH_RANGE = 45; private static final int PITCH_TICK_SPACING = 15; private static final int PITCH_TICK_PADDING = 2; private static final float PLANE_SIZE = 0.8f; private static final float PLANE_BODY_SIZE = 0.2f; private static final float PLANE_WING_WIDTH = 5f; private float halfWidth; private float halfHeight; private float radiusExternal; private float radiusInternal; private RectF internalBounds; private Paint yawPaint; private Paint skyPaint; private Paint groundPaint; private Paint planePaint; private Paint planeFinPaint; private Paint planeCenterPaint; private Path yawPath = new Path(); private Path groundPath = new Path(); private float yaw, roll, pitch; private Paint tickPaint; public AttitudeIndicator(Context context, AttributeSet attrs) { super(context, attrs); initialize(); setAttitude(-30, 20, 0); } private void initialize() { Paint fillPaint = new Paint(); fillPaint.setAntiAlias(true); fillPaint.setStyle(Style.FILL); yawPaint = new Paint(fillPaint); yawPaint.setColor(Color.GRAY); skyPaint = new Paint(fillPaint); groundPaint = new Paint(fillPaint); planePaint = new Paint(fillPaint); planePaint.setColor(Color.WHITE); planePaint.setStrokeWidth(PLANE_WING_WIDTH); planePaint.setStrokeCap(Cap.ROUND); planeCenterPaint = new Paint(planePaint); planeCenterPaint.setColor(Color.RED); planeFinPaint = new Paint(planePaint); planeFinPaint.setStrokeWidth(PLANE_WING_WIDTH / 2f); tickPaint = new Paint(fillPaint); tickPaint.setColor(Color.parseColor("#44ffffff")); tickPaint.setStrokeWidth(2); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); halfHeight = h / 2f; halfWidth = w / 2f; radiusExternal = Math.min(halfHeight, halfWidth) / YAW_ARROW_SIZE; radiusInternal = radiusExternal * INTERNAL_RADIUS; internalBounds = new RectF(-radiusInternal, -radiusInternal, radiusInternal, radiusInternal); skyPaint.setShader(new LinearGradient(0, -radiusInternal, 0, radiusInternal, Color .parseColor("#0082d6"), Color.parseColor("#2cb1e1"), TileMode.CLAMP)); groundPaint.setShader(new LinearGradient(0, radiusInternal, 0, radiusInternal, Color .parseColor("#4bbba1"), Color.parseColor("#008f63"), TileMode.CLAMP)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(halfWidth, halfHeight); drawYaw(canvas); drawSkyAndGround(canvas); drawPitchTicks(canvas); drawPlane(canvas); } private void drawYaw(Canvas canvas) { // Fill the background canvas.drawCircle(0, 0, radiusExternal, yawPaint); // Yaw Arrow float mathYaw = (float) Math.toRadians(180 - yaw); yawPath.reset(); yawPath.moveTo(0, 0); radialLineTo(yawPath, mathYaw + YAW_ARROW_ANGLE, radiusExternal); radialLineTo(yawPath, mathYaw, radiusExternal * YAW_ARROW_SIZE); radialLineTo(yawPath, mathYaw - YAW_ARROW_ANGLE, radiusExternal); canvas.drawPath(yawPath, yawPaint); } private void radialLineTo(Path path, float angle, float radius) { path.lineTo((float) Math.sin(angle) * radius, (float) Math.cos(angle) * radius); } private void drawSkyAndGround(Canvas canvas) { // Fill with the sky canvas.drawCircle(0, 0, radiusInternal, skyPaint); // Overlay the ground groundPath.reset(); float pitchProjection = (float) Math.toDegrees(Math.acos(pitch / PITCH_RANGE)); groundPath.addArc(internalBounds, 90 - pitchProjection - roll, pitchProjection * 2); canvas.drawPath(groundPath, groundPaint); } private void drawPitchTicks(Canvas canvas) { float lineX = (float) (Math.cos(Math.toRadians(-roll)) * radiusInternal) * PITCH_TICK_LINE_LENGTH; float lineY = (float) (Math.sin(Math.toRadians(-roll)) * radiusInternal) * PITCH_TICK_LINE_LENGTH; float dx = (float) (Math.cos(Math.toRadians(-roll - 90)) * radiusInternal / PITCH_RANGE); float dy = (float) (Math.sin(Math.toRadians(-roll - 90)) * radiusInternal / PITCH_RANGE); int i = (int) ((-PITCH_RANGE + pitch + PITCH_TICK_PADDING) / PITCH_TICK_SPACING); int loopEnd = (int) ((PITCH_RANGE + pitch - PITCH_TICK_PADDING) / PITCH_TICK_SPACING); for (; i <= loopEnd; i++) { float degree = -pitch + PITCH_TICK_SPACING * i; canvas.drawLine(lineX + dx * degree, lineY + dy * degree, -lineX + dx * degree, -lineY + dy * degree, tickPaint); } } private void drawPlane(Canvas canvas) { canvas.drawLine(radiusInternal * PLANE_SIZE, 0, -radiusInternal * PLANE_SIZE, 0, planePaint); canvas.drawLine(0, 0, 0, -radiusInternal * PLANE_SIZE * 5 / 12, planeFinPaint); canvas.drawCircle(0, 0, radiusInternal * PLANE_SIZE * PLANE_BODY_SIZE, planePaint); canvas.drawCircle(0, 0, radiusInternal * PLANE_SIZE * PLANE_BODY_SIZE / 2f, planeCenterPaint); } public void setAttitude(float roll, float pitch, float yaw) { this.roll = roll; this.pitch = pitch; this.yaw = yaw; invalidate(); } }